diff --git a/luprex/.gitattributes b/luprex/.gitattributes new file mode 100644 index 00000000..c6b57328 --- /dev/null +++ b/luprex/.gitattributes @@ -0,0 +1,15 @@ +* text=auto +* text eol=lf +*.bat text eol=crlf +*.lib filter=lfs diff=lfs merge=lfs -text +*.exe filter=lfs diff=lfs merge=lfs -text +*.a filter=lfs diff=lfs merge=lfs -text +*.o filter=lfs diff=lfs merge=lfs -text +*.obj filter=lfs diff=lfs merge=lfs -text +*.dll filter=lfs diff=lfs merge=lfs -text +*.pdb filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.psd filter=lfs diff=lfs merge=lfs -text +*.so filter=lfs diff=lfs merge=lfs -text diff --git a/luprex/.gitignore b/luprex/.gitignore new file mode 100644 index 00000000..595b3783 --- /dev/null +++ b/luprex/.gitignore @@ -0,0 +1,27 @@ +a.out +gprof.out +gmon.out +luprex + +*~ +\#*# +.#* +*.obj +*.o +*.dll +*.exe +*.pdb +*.so +*.a +*.lib +*.ilk +*.exp +*.pdb + +build/** +.vscode/** +ext/eris-master/src/lua +ext/eris-master/src/luac +ext/eris-master/test/persist +ext/eris-master/test/unpersist + diff --git a/luprex/.vscode/tasks.json b/luprex/.vscode/tasks.json new file mode 100644 index 00000000..490cdcbe --- /dev/null +++ b/luprex/.vscode/tasks.json @@ -0,0 +1,30 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build luprex", + "type": "shell", + "command": "make", + "problemMatcher": { + "base": "$gcc", + "fileLocation": [ + "relative", + "${workspaceFolder}" + ] + }, + "options": { + "cwd": "${workspaceFolder}" + }, + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "clear": true, + "reveal": "always", + "panel": "shared", + "revealProblems": "never" + } + } + ] +} \ No newline at end of file diff --git a/luprex/Makefile b/luprex/Makefile new file mode 100644 index 00000000..6880314a --- /dev/null +++ b/luprex/Makefile @@ -0,0 +1,221 @@ +####################################################################### +## +## Auto detect Operating System +## +####################################################################### + +ifneq "" "$(findstring -linux-,$(MAKE_HOST))" + OS=linux +else ifneq "" "$(VSINSTALLDIR)" + OS=visual +else ifneq "" "$(findstring cmd.exe,$(COMSPEC))" + OS=mingw +else + OS="" +endif + +ifeq "$(OS)" "" + $(error Cannot figure out which OS to build for." +endif +$(info Building for $(OS)...) + + +####################################################################### +## +## List of all OBJ files +## +####################################################################### + +OBJ_ERIS=\ + build/$(OS)/eris/lapi.obj \ + build/$(OS)/eris/lcode.obj \ + build/$(OS)/eris/lctype.obj \ + build/$(OS)/eris/ldebug.obj \ + build/$(OS)/eris/ldo.obj \ + build/$(OS)/eris/ldump.obj \ + build/$(OS)/eris/lfunc.obj \ + build/$(OS)/eris/lgc.obj \ + build/$(OS)/eris/llex.obj \ + build/$(OS)/eris/lmem.obj \ + build/$(OS)/eris/lobject.obj \ + build/$(OS)/eris/lopcodes.obj \ + build/$(OS)/eris/lparser.obj \ + build/$(OS)/eris/lstate.obj \ + build/$(OS)/eris/lstring.obj \ + build/$(OS)/eris/ltable.obj \ + build/$(OS)/eris/ltm.obj \ + build/$(OS)/eris/lundump.obj \ + build/$(OS)/eris/lvm.obj \ + build/$(OS)/eris/lzio.obj \ + build/$(OS)/eris/lauxlib.obj \ + build/$(OS)/eris/lbaselib.obj \ + build/$(OS)/eris/lbitlib.obj \ + build/$(OS)/eris/lcorolib.obj \ + build/$(OS)/eris/ldblib.obj \ + build/$(OS)/eris/liolib.obj \ + build/$(OS)/eris/lmathlib.obj \ + build/$(OS)/eris/loslib.obj \ + build/$(OS)/eris/lstrlib.obj \ + build/$(OS)/eris/ltablib.obj \ + build/$(OS)/eris/loadlib.obj \ + build/$(OS)/eris/linit.obj \ + build/$(OS)/eris/eris.obj \ + +OBJ_CORE=\ + build/$(OS)/core/invocation.obj\ + build/$(OS)/core/spookyv2.obj\ + build/$(OS)/core/eng-malloc.obj\ + build/$(OS)/core/debugcollector.obj\ + build/$(OS)/core/drivenengine.obj\ + build/$(OS)/core/util.obj\ + build/$(OS)/core/luastack.obj\ + build/$(OS)/core/traceback.obj\ + build/$(OS)/core/planemap.obj\ + build/$(OS)/core/pprint.obj\ + build/$(OS)/core/luaconsole.obj\ + build/$(OS)/core/idalloc.obj\ + build/$(OS)/core/globaldb.obj\ + build/$(OS)/core/sched.obj\ + build/$(OS)/core/http.obj\ + build/$(OS)/core/json.obj\ + build/$(OS)/core/table.obj\ + build/$(OS)/core/luasnap.obj\ + build/$(OS)/core/animqueue.obj\ + build/$(OS)/core/streambuffer.obj\ + build/$(OS)/core/source.obj\ + build/$(OS)/core/world-core.obj\ + build/$(OS)/core/world-accessor.obj\ + build/$(OS)/core/world-difftab.obj\ + build/$(OS)/core/world-diffxmit.obj\ + build/$(OS)/core/world-pairtab.obj\ + build/$(OS)/core/world-testing.obj\ + build/$(OS)/core/lpxserver.obj\ + build/$(OS)/core/lpxclient.obj\ + build/$(OS)/core/eng-tests.obj\ + build/$(OS)/core/printbuffer.obj\ + build/$(OS)/core/serializelua.obj\ + + +OBJ_DRV=\ + build/$(OS)/drv/driver.obj\ + build/$(OS)/drv/drvutil.obj\ + build/$(OS)/drv/osdrvutil.obj\ + build/$(OS)/drv/sslutil.obj\ + build/$(OS)/drv/readline.obj\ + + +####################################################################### +## +## Make rules for linux +## +####################################################################### + +ifeq "$(OS)" "linux" + OPT=-g -O0 + LUPREX_EXE=luprex + LUPREXLIB_DLL=luprexlib.so + LUPREXSTATIC_EXE=luprexstatic + COMPILE=g++ -Wall $(OPT) -std=c++17 -fvisibility=hidden -c -MMD -fPIC -o + LINKDLL=g++ -Wall $(OPT) -std=c++17 -export-dynamic -Wl,--no-allow-shlib-undefined -Wl,-z,defs -shared -o + LINKEXE=g++ -Wall $(OPT) -std=c++17 -o + MAKEDEPS=true + OPENSSL_INCLUDE=-I./ext/openssl-3.0.1/inc + LUA_FLAGS=-DLUA_USE_APICHECK -DLUA_USE_POSIX + LIBS=-L./ext/openssl-3.0.1/lib/linux -lssl -lcrypto -ldl +endif + + +####################################################################### +## +## Make rules for mingw +## +####################################################################### + +ifeq "$(OS)" "mingw" + OPT=-g -O0 + LUPREX_EXE=luprex.exe + LUPREXLIB_DLL=luprexlib.dll + LUPREXSTATIC_EXE=luprexstatic.exe + COMPILE=g++ -Wall $(OPT) -std=c++17 -fvisibility=hidden -c -MMD -fPIC -o + LINKDLL=g++ -Wall $(OPT) -std=c++17 -Wl,--no-allow-shlib-undefined -shared -o + LINKEXE=g++ -Wall $(OPT) -std=c++17 -o + MAKEDEPS=true + OPENSSL_INCLUDE=-I./ext/openssl-3.0.1/inc + LUA_FLAGS=-DLUA_USE_APICHECK -DLUA_COMPAT_ALL + LIBS=-L./ext/openssl-3.0.1/lib/mingw -lssl -lcrypto -lws2_32 -lcrypt32 -lcryptui +endif + + +####################################################################### +## +## Make rules for visual +## +####################################################################### + +ifeq "$(OS)" "visual" + ifeq "" "$(VSINSTALLDIR)" + $(error You must use vcvars64.bat to set up the visual studio environment variables) + endif + OPT=/Od /Zi + LUPREX_EXE=luprex.exe + LUPREXLIB_DLL=luprexlib.dll + LUPREXSTATIC_EXE=luprexstatic.exe + COMPILE=CL $(OPT) /std:c++17 /EHsc /nologo /MD /TP /c /Fo: + LINKDLL=CL $(OPT) /std:c++17 /EHsc /nologo /LDd /Fe: + LINKEXE=CL $(OPT) /std:c++17 /EHsc /nologo /Fe: + MAKEDEPS=g++ -Wall -std=c++17 -MMD -E -o + OPENSSL_INCLUDE=-I./ext/openssl-3.1.0/inc + LUA_FLAGS=-DLUA_USE_APICHECK -DLUA_COMPAT_ALL + LIBS=ext/openssl-3.1.0/lib/visual/libcrypto.lib ext/openssl-3.1.0/lib/visual/libssl.lib ws2_32.lib crypt32.lib cryptui.lib user32.lib advapi32.lib +endif + +####################################################################### +## +## Make Rules +## +####################################################################### + +all: build/$(OS)/$(LUPREX_EXE) build/$(OS)/$(LUPREXSTATIC_EXE) build/$(OS)/$(LUPREXLIB_DLL) + +build/$(OS)/DIRECTORY: + mkdir -p build/$(OS)/core build/$(OS)/eris build/$(OS)/drv + touch build/$(OS)/DIRECTORY + +build/$(OS)/$(LUPREX_EXE): build/$(OS)/$(LUPREXLIB_DLL) $(OBJ_DRV) + $(LINKEXE) $@ $(OBJ_DRV) $(LIBS) + +build/$(OS)/$(LUPREXSTATIC_EXE): $(OBJ_DRV) $(OBJ_ERIS) $(OBJ_CORE) + $(LINKEXE) $@ $^ $(LIBS) + +build/$(OS)/$(LUPREXLIB_DLL): $(OBJ_ERIS) $(OBJ_CORE) + $(LINKDLL) $@ $^ + +build/$(OS)/eris/%.obj: ext/eris-master/src/%.c build/$(OS)/DIRECTORY + $(MAKEDEPS) $@d $(LUA_FLAGS) $< + $(COMPILE) $@ $(LUA_FLAGS) $< + +build/$(OS)/core/%.obj: cpp/core/%.cpp build/$(OS)/DIRECTORY + $(MAKEDEPS) $@d -I./ext/eris-master/src -I./cpp/wrap -I./cpp/core -I./ext $< + $(COMPILE) $@ -I./ext/eris-master/src -I./cpp/wrap -I./cpp/core -I./ext $< + +build/$(OS)/drv/%.obj: cpp/drv/%.cpp build/$(OS)/DIRECTORY + $(MAKEDEPS) $@d $(OPENSSL_INCLUDE) -I./src/drv -I./ext $< + $(COMPILE) $@ $(OPENSSL_INCLUDE) -I./src/drv -I./ext $< + +clean: + rm -f luprex* luprex*.* *.pdb + rm -rf build + +clean-os: + rm -f luprex* luprex*.* *.pdb build/$(OS)/* build/$(OS)/*/* + +####################################################################### +## +## Automatically generated Make Dependencies +## +####################################################################### + +-include $(OBJ_ERIS:%.obj=%.d) +-include $(OBJ_CORE:%.obj=%.d) +-include $(OBJ_DRV:%.obj=%.d) + diff --git a/luprex/README.md b/luprex/README.md new file mode 100644 index 00000000..ab61937f --- /dev/null +++ b/luprex/README.md @@ -0,0 +1,28 @@ +"# luprex" + +Directory Structure: + +cpp - C++ source code + + cpp/core - C++ source code for the engine proper + cpp/drv - C++ source code for the driver + cpp/wrap - C++ STL classes reconfigured to use eng-malloc + +ext - external third-party libraries + + ext/dlmalloc.c - Doug Lea's malloc (used by eng-malloc) + ext/openssl - Header files for our version of openssl + ext/openssl-linux - Library files for openssl, compiled for linux + ext/openssl-mingw - Library files for openssl, compiled for mingw + ext/eris-master - The C code for the lua interpreter, eris lua. + +obj - a place to store compiled C++ object code + + obj/core - Compiled from cpp/core + obj/drv - Compiled from cpp/drv + obj/eris - Compiled from ext/eris-master + +lua - game code written in lua + +misc - a place for tinkering, not part of the main project + diff --git a/luprex/cpp/core/animqueue.cpp b/luprex/cpp/core/animqueue.cpp new file mode 100644 index 00000000..8f628cf8 --- /dev/null +++ b/luprex/cpp/core/animqueue.cpp @@ -0,0 +1,746 @@ +#define _USE_MATH_DEFINES + +#include "wrap-sstream.hpp" +#include "wrap-map.hpp" +#include "util.hpp" +#include "animqueue.hpp" +#include "luastack.hpp" +#include "streambuffer.hpp" + +#include +#include +#include + + +util::SharedStdString AnimQueue::blankqueue_; + +void AnimQueue::initialize_module() { + AnimQueue queue; + blankqueue_ = queue.get_encoded_queue(); +} + +static uint64_t hash_encstep(uint64_t prev, std::string_view s) { + return util::hash_string(util::HashValue(123, prev), s).first; +} + +// Parse a value. This is meant for unit testing only. The +// parser isn't powerful enough to express all possible values. +static void parse_value(std::string_view vstr, AnimValue *v) { + // Try to interpret vstr as a boolean. + bool is_true = (vstr == "true"); + bool is_false = (vstr == "false"); + if (is_true || is_false) { + v->set_boolean(is_true); + return; + } + // Try to interpret vstr as a number. + if (sv::valid_number(vstr, true, true, true, false)) { + v->set_number(std::atof(std::string(vstr).c_str())); + return; + } + // Try to interpret vstr as a vector. + eng::vector parts = util::split(eng::string(vstr), ','); + if ((parts.size() == 3) && + (sv::valid_number(parts[0], true, true, true, false)) && + (sv::valid_number(parts[1], true, true, true, false)) && + (sv::valid_number(parts[2], true, true, true, false))) { + double x = std::atof(parts[0].c_str()); + double y = std::atof(parts[1].c_str()); + double z = std::atof(parts[2].c_str()); + v->set_dxyz(util::DXYZ(x,y,z)); + return; + } + // If it doesn't parse as any of the above, it's a string. + v->set_string(vstr); +} + + +static AnimValue parse_anim_value(LuaCoreStack &LS, LuaSlot val, LuaSlot tmp) { + AnimValue result; + auto tboolean = LS.tryboolean(val); + if (tboolean) { + result.set_boolean(*tboolean); + return result; + } + auto tnumber = LS.trynumber(val); + if (tnumber) { + result.set_number(*tnumber); + return result; + } + auto tstring = LS.trystringview(val); + if (tstring) { + result.set_string(*tstring); + return result; + } + auto txyz = LS.tryxyz(val); + if (txyz) { + result.set_dxyz(*txyz); + return result; + } + if (LS.rawequal(val, LuaToken("auto"))) { + result.set_auto(); + return result; + } + result.set_uninitialized(); + return result; +} + +void AnimState::set_persistent(const eng::string &name) { + map_[name].persistent = true; +} + +void AnimState::set_boolean(const eng::string &name, bool v) { + AnimValue &value = map_[name]; + value.set_boolean(v); +} + +void AnimState::set_number(const eng::string &name, double v) { + AnimValue &value = map_[name]; + value.set_number(v); +} + +void AnimState::set_dxyz(const eng::string &name, const util::DXYZ &v) { + AnimValue &value = map_[name]; + value.set_dxyz(v); +} + +void AnimState::set_string(const eng::string &name, std::string_view v) { + AnimValue &value = map_[name]; + value.set_string(v); +} + +void AnimState::print_debug_string(eng::ostringstream &oss) { + bool first = true; + if (map_.empty()) { + oss << "[empty]"; + } + for (const auto &pair : map_) { + if (!first) oss << " "; + const eng::string &name = pair.first; + const AnimValue &value = pair.second; + oss << name; + if (value.persistent) { + oss << "="; + } else { + oss << ":"; + } + switch (value.type) { + case SimpleDynamicTag::UNINITIALIZED: oss << "UNINITIALIZED"; break; + case SimpleDynamicTag::AUTO: oss << "AUTO"; break; + case SimpleDynamicTag::NUMBER: oss << value.x; break; + case SimpleDynamicTag::BOOLEAN: oss << ((value.x == 1.0) ? "true":"false"); break; + case SimpleDynamicTag::VECTOR: oss << value.x << "," << value.y << "," << value.z; break; + case SimpleDynamicTag::STRING: oss << value.s; break; + default: assert(false); + } + first = false; + } +} + +eng::string AnimState::debug_string() { + eng::ostringstream oss; + print_debug_string(oss); + return oss.str(); +} + +eng::string AnimState::encode() const { + StreamBuffer sb; + for (const auto &pair : map_) { + const eng::string &name = pair.first; + const AnimValue &value = pair.second; + sb.write_string(name); + sb.write_bool(value.persistent); + sb.write_simple_dynamic(value); + } + return eng::string(sb.view()); +} + +void AnimState::decode(std::string_view s) { + map_.clear(); + StreamBuffer sb(s); + while (!sb.empty()) { + eng::string name = sb.read_string(); + AnimValue &value = map_[name]; + value.persistent = sb.read_bool(); + sb.read_simple_dynamic(&value); + } +} + +void AnimState::decode_persistent(std::string_view s) { + map_.clear(); + StreamBuffer sb(s); + AnimValue dummy; + while (!sb.empty()) { + eng::string name = sb.read_string(); + bool persistent = sb.read_bool(); + if (persistent) { + AnimValue &value = map_[name]; + value.persistent = persistent; + sb.read_simple_dynamic(&value); + } else { + sb.read_simple_dynamic(&dummy); + } + } +} + +eng::string AnimState::add_default(const eng::string &name, const AnimValue &def, const AnimState *other) { + AnimValue &value = map_[name]; + value.persistent = true; + if (value.type == SimpleDynamicTag::UNINITIALIZED) { + if (other != nullptr) { + auto otheriter = other->map_.find(name); + if (otheriter != other->map_.end()) { + if (otheriter->second.persistent && otheriter->second.type == def.type) { + value.copy_value(otheriter->second); + return ""; + } + } + } + value.copy_value(def); + return ""; + } + if (value.type != def.type) { + return util::ss("Animation key ", name, " must be a ", def.type_name()); + } + return ""; +} + +eng::string AnimState::add_defaults(const AnimState *other) { + eng::string err; + AnimValue defval; + + defval.set_dxyz(util::DXYZ(0,0,0)); + err = add_default("xyz", defval, other); + if (!err.empty()) return err; + + defval.set_string("nowhere"); + err = add_default("plane", defval, other); + if (!err.empty()) return err; + + defval.set_number(0.0); + err = add_default("facing", defval, other); + if (!err.empty()) return err; + + defval.set_string("unknown"); + err = add_default("bp", defval, other); + if (!err.empty()) return err; + + return ""; +} + + +eng::string AnimState::from_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent, bool allowauto) { + LuaVar key, val, tmp; + LuaExtStack LS(LS0.state(), key, val, tmp); + util::DXYZ xyz; + + clear(); + if (!LS.istable(tab)) { + return "A lua animstate must be a table."; + } + LS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + if (!LS.isstring(key)) { + return "in animation key-value pairs, key must be a string."; + } + eng::string name = LS.ckstring(key); + if (!sv::is_lua_id(name)) { + return "in animation key-value pairs, key must be a valid lua identifier."; + } + AnimValue parsedvalue = parse_anim_value(LS, val, tmp); + if (parsedvalue.type == SimpleDynamicTag::UNINITIALIZED) { + return "in animation key-value pairs, value must be number, string, boolean, or xyz"; + } + if ((parsedvalue.type == SimpleDynamicTag::AUTO) && !allowauto) { + return "in animation key-value pairs, value must not be AUTO here."; + } + AnimValue &mapentry = map_[name]; + mapentry.copy_value(parsedvalue); + mapentry.persistent = persistent; + } + return ""; +} + +eng::string AnimState::merge(const AnimState &previous, const AnimState &update) { + // Copy everything over from the previous entry. + map_ = previous.map_; + + for (const auto &pair : update.map_) { + const eng::string &name = pair.first; + AnimValue &dst = map_[name]; + const AnimValue &src = pair.second; + + // Handle autocalculation rules. + if (src.type == SimpleDynamicTag::AUTO) { + if (name == "facing") { + if (!dst.persistent || dst.type != SimpleDynamicTag::NUMBER) { + return "Cannot auto-calculate facing because facing has not been specified as a persistent number"; + } + const auto xyz_previous = previous.map_.find("xyz"); + const auto xyz_update = update.map_.find("xyz"); + if ((xyz_previous == previous.map_.end()) || + (xyz_update == update.map_.end()) || + (xyz_previous->second.type != SimpleDynamicTag::VECTOR) || + (xyz_update->second.type != SimpleDynamicTag::VECTOR)) { + return "Cannot auto-calculate facing because before/after xyz coordinates are not present"; + } + double dx = xyz_update->second.x - xyz_previous->second.x; + double dy = xyz_update->second.y - xyz_previous->second.y; + // If dx and dy are both zero, leave the facing unmodified. + if ((dx != 0.0) || (dy != 0.0)) { + double facing = atan2(dy, dx) * 180.0 / M_PI; + dst.set_number(facing); + } + } else { + return util::ss("No rule to automatically calculate ", name); + } + continue; + } + + if (dst.persistent && (src.type != dst.type)) { + return util::ss("Wrong data type for ", name, ", should be ", dst.type_name()); + } + dst.copy_value(src); + } + return ""; +} + + +void AnimState::to_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent) { + LuaVar name, val; + LuaExtStack LS(LS0.state(), name, val); + LS.newtable(tab); + for (const auto &pair : map_) { + if (pair.second.persistent != persistent) continue; + LS.set(name, pair.first); + const AnimValue &value = pair.second; + if (value.type == SimpleDynamicTag::BOOLEAN) { + LS.set(val, (value.x == 1.0)); + } else if (value.type == SimpleDynamicTag::NUMBER) { + LS.set(val, value.x); + } else if (value.type == SimpleDynamicTag::STRING) { + LS.set(val, std::string_view(value.s)); + } else if (value.type == SimpleDynamicTag::VECTOR) { + LS.newtable(val); + LS.rawset(val, 1, value.x); + LS.rawset(val, 2, value.y); + LS.rawset(val, 3, value.z); + } + LS.rawset(tab, name, val); + } +} + + +// The syntax used by this parser is not general enough to represent all +// possible strings. That's OK, though, since it's just for unit testing. +void AnimState::parse(std::string_view config) { + while (true) { + config = sv::ltrim(config); + if (config.empty()) break; + eng::string name(sv::read_ascii_identifier(config)); + assert(!name.empty()); + AnimValue &value = map_[name]; + bool has_equal = sv::has_prefix(config, "="); + bool has_colon = sv::has_prefix(config, ":"); + assert(has_equal || has_colon); + config.remove_prefix(1); + value.persistent = has_equal; + eng::string vstr(sv::read_to_space(config)); + parse_value(vstr, &value); + } +} + +void AnimState::clear_and_parse(std::string_view config) { + map_.clear(); + parse(config); +} + +void AnimCoreState::decode(std::string_view s) { + plane.clear(); + xyz = 0.0; + StreamBuffer sb(s); + AnimValue value; + while (!sb.empty()) { + eng::string name = sb.read_string(); + bool persistent = sb.read_bool(); + sb.read_simple_dynamic(&value); + if (persistent) { + if ((name == "xyz") && (value.type == SimpleDynamicTag::VECTOR)) xyz = util::DXYZ(value.x, value.y, value.z); + if ((name == "plane") && (value.type == SimpleDynamicTag::STRING)) plane = value.s; + } + } +} + +int AnimQueue::get_size_limit() const { + if (encqueue_ == nullptr) return 0; + StreamBuffer sb(*encqueue_); + return sb.read_uint8(); +} + +int AnimQueue::get_actual_size() const { + if (encqueue_ == nullptr) return 0; + StreamBuffer sb(*encqueue_); + sb.read_bytes(1); + return sb.read_uint8(); +} + +uint64_t AnimQueue::get_final_hash() const { + if (encqueue_ == nullptr) return 0; + StreamBuffer sb(*encqueue_); + sb.read_bytes(2); + return sb.read_uint64(); +} + +std::string_view AnimQueue::get_final_encstep() const { + if (encqueue_ == nullptr) return std::string_view(); + StreamBuffer sb(*encqueue_); + sb.read_bytes(10); + return sb.read_string_view(); +} + +AnimQueue::QueueRange AnimQueue::get_range(int lo, int hi) { + // Clamp lo and hi to the valid range (0 to actual_size). + // + int actual_size = get_actual_size(); + if (lo < 0) lo = 0; + if (hi > actual_size) hi = actual_size; + + // Abort early if the range is empty. This avoids several edge cases. + // + if (lo >= hi) return QueueRange(0, std::string_view()); + + // Get the entries. + // + std::string_view queueview(*encqueue_); + StreamBuffer sb(queueview); + sb.read_bytes(2); // Skip over the header. + for (int i = 0; i < lo; i++) { + sb.read_uint64(); + sb.read_string_view(); + } + int pos1 = sb.total_reads(); + for (int i = lo; i < hi; i++) { + sb.read_uint64(); + sb.read_string_view(); + } + int pos2 = sb.total_reads(); + return QueueRange(hi-lo, queueview.substr(pos1, pos2 - pos1)); +} + +uint64_t AnimQueue::hash_encstep(const QueueRange &prev, std::string_view s) { + uint64_t prev_hash = 0; + if (prev.size > 0) { + StreamBuffer retsb(prev.entries); + prev_hash = retsb.read_uint64(); + } + return ::hash_encstep(prev_hash, s); +} + +void AnimQueue::update_encqueue(int limit, bool add, std::string_view add_enc, int keeplo, int keephi) { + // Get the retained entries. + QueueRange keeprange = get_range(keeplo, keephi); + + // Encode everything into a binary blob. + StreamBuffer result; + result.write_uint8(limit); + result.write_uint8(keeprange.size + (add ? 1:0)); + if (add) { + uint64_t add_hash = hash_encstep(keeprange, add_enc); + result.write_uint64(add_hash); + result.write_string(add_enc); + } + result.write_bytes(keeprange.entries); + + // Replace the shared string. + encqueue_ = std::make_shared(result.view()); +} + +AnimQueue::AnimQueue() { + update_encqueue(10, true, AnimState().encode(), 0, 0); +} + +void AnimQueue::clear(const AnimState &state) { + update_encqueue(get_size_limit(), true, state.encode(), 0, 0); +} + +void AnimQueue::clear() { + update_encqueue(get_size_limit(), true, AnimState().encode(), 0, 0); +} + +void AnimQueue::set_limit(int limit) { + assert((limit >= 2) && (limit <= 250)); + update_encqueue(limit, false, std::string_view(), 0, limit); +} + +void AnimQueue::add(const AnimState &state) { + int limit = get_size_limit(); + update_encqueue(limit, true, state.encode(), 0, limit - 1); +} + +void AnimQueue::replace(const AnimState &state) { + int limit = get_size_limit(); + update_encqueue(limit, true, state.encode(), 1, limit); +} + +void AnimQueue::serialize(StreamBuffer *sb) const { + sb->write_string(*encqueue_); +} + +void AnimQueue::deserialize(StreamBuffer *sb) { + encqueue_ = std::make_shared(sb->read_string_view()); +} + +bool AnimQueue::diff(const AnimQueue &auth, StreamBuffer *sb) const { + // Fast check for exactly equivalent. If equivalent, skip all the work. + if (exactly_equal_fast(auth)) { + assert(exactly_equal(auth)); + sb->write_bool(false); + return false; + } + + // TODO: maybe send less data? + sb->write_bool(true); + sb->write_string(*auth.encqueue_); + return true; +} + +void AnimQueue::patch(StreamBuffer *sb, DebugCollector *dbc) { + bool changed = sb->read_bool(); + if (!changed) { + return; + } + DebugLine(dbc) << "AnimQueue modified"; + encqueue_ = std::make_shared(sb->read_string_view()); +} + +bool AnimQueue::exactly_equal(const AnimQueue &other) const { + if (*encqueue_ != *other.encqueue_) return false; + return true; +} + +bool AnimQueue::exactly_equal_fast(const AnimQueue &other) const { + if (encqueue_->size() != other.encqueue_->size()) return false; + if (encqueue_->compare(0, 10, *other.encqueue_) != 0) return false; + return true; +} + +void AnimQueue::print_debug_string(eng::ostringstream &oss, bool full) const { + bool first = true; + // Break out the steps. + eng::vector encsteps; + StreamBuffer sb(*encqueue_); + int size_limit = sb.read_uint8(); + int actual_size = sb.read_uint8(); + if (full) { + oss << "limit=" << size_limit; + first = false; + } + for (int i = 0; i < actual_size; i++) { + sb.read_uint64(); + encsteps.push_back(sb.read_string_view()); + } + assert(sb.empty()); + for (int i = encsteps.size() - 1; i >= 0; i --) { + if (!first) oss << "; "; + AnimState state(encsteps[i]); + state.print_debug_string(oss); + first = false; + } +} + +eng::string AnimQueue::steps_debug_string() const { + eng::ostringstream oss; + print_debug_string(oss, false); + return oss.str(); +} + +eng::string AnimQueue::full_debug_string() const { + eng::ostringstream oss; + print_debug_string(oss, true); + return oss.str(); +} + +AnimCoreState AnimQueue::get_final_core_state() const { + std::string_view encstep = get_final_encstep(); + AnimCoreState result; + result.decode(encstep); + return result; +} + +AnimState AnimQueue::get_final_persistent() const { + std::string_view encstep = get_final_encstep(); + AnimState result; + result.decode_persistent(encstep); + return result; +} + +AnimState AnimQueue::get_final_everything() const { + std::string_view encstep = get_final_encstep(); + AnimState result; + result.decode(encstep); + return result; +} + +LuaDefine(unittests_animqueue, "", "some unit tests") { + // Useful objects. + AnimQueue aq, aqs; + StreamBuffer sb; + AnimState astate; + eng::string enc; + AnimCoreState core; + + // Debug string of a newly initialized queue + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; [empty]"); + + // Test AnimState simple setters. + astate.set_string("color", "blue"); + astate.set_dxyz("xyz", util::DXYZ(1,2,3)); + astate.set_number("half", 0.5); + astate.set_boolean("nice", true); + LuaAssertStrEq(L, astate.debug_string(), "color:blue half:0.5 nice:true xyz:1,2,3"); + + // // Test AnimState simple getters. + // LuaAssert(L, astate.get_string("color") == "blue"); + // LuaAssert(L, astate.get_xyz("xyz") == util::DXYZ(1,2,3)); + // LuaAssert(L, astate.get_number("half") == 0.5); + // LuaAssert(L, astate.get_boolean("nice") == true); + + // // Test AnimState simple getters on nonexistent data. + // LuaAssert(L, astate.get_string("q") == ""); + // LuaAssert(L, astate.get_xyz("q") == util::DXYZ(0,0,0)); + // LuaAssert(L, astate.get_number("q") == 0.0); + // LuaAssert(L, astate.get_boolean("q") == false); + + // // Test AnimState simple getters on wrong-type data. + // LuaAssert(L, astate.get_string("half") == ""); + // LuaAssert(L, astate.get_xyz("half") == util::DXYZ(0,0,0)); + // LuaAssert(L, astate.get_number("color") == 0.0); + // LuaAssert(L, astate.get_boolean("color") == false); + + // Test AnimState persistence manipulation. + astate.set_persistent("color"); + astate.set_persistent("nice"); + LuaAssertStrEq(L, astate.debug_string(), "color=blue half:0.5 nice=true xyz:1,2,3"); + + // Test AnimState parser. + astate.clear_and_parse("color:green mean=true pos=3,4,5 ok:false"); + LuaAssertStrEq(L, astate.debug_string(), "color:green mean=true ok:false pos=3,4,5"); + + // Test animstate encoding and decoding. + astate.clear_and_parse("color:green mean=true pos=3,4,5 ok:false"); + enc = astate.encode(); + astate.clear(); + astate.decode(enc); + LuaAssertStrEq(L, astate.debug_string(), "color:green mean=true ok:false pos=3,4,5"); + astate.decode_persistent(enc); + LuaAssertStrEq(L, astate.debug_string(), "mean=true pos=3,4,5"); + + // Test AnimCoreState.decode + // + astate.clear_and_parse("color=blue xyz=1,2,3 plane=banana chicken=3"); + core.decode(astate.encode()); + LuaAssert(L, core.plane == "banana"); + LuaAssert(L, core.xyz == util::DXYZ(1,2,3)); + + // Verify that a newly-constructed AnimQueue is in a reasonable default state. + // + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; [empty]"); + + // Clear an AnimQueue to a specified initial state. + // + astate.clear_and_parse("color=blue xyz=1,2,3 plane=somewhere"); + aq.clear(astate); + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; color=blue plane=somewhere xyz=1,2,3"); + + // Add animation steps to animation queue. + // Note: each step is independent of the previous one, no composition is being done. + // + astate.clear_and_parse("xyz=1,2,3 plane=earth"); + aq.clear(astate); + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3"); + astate.clear_and_parse("xyz=4,5,6 action:jump"); + aq.add(astate); + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6"); + astate.clear_and_parse("plane=moon airline:southwest"); + aq.add(astate); + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon"); + astate.clear_and_parse("color=blue"); + aq.add(astate); + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue"); + + // Try reducing the animation queue size limit. + // + aq.set_limit(2); + LuaAssertStrEq(L, aq.full_debug_string(), "limit=2; airline:southwest plane=moon; color=blue"); + + // Test get_final_persistent, get_final_everything, get_final_core_state + // + astate.clear_and_parse("action:jump plane=earth xyz=1,2,3 bouncy:true"); + aq.clear(astate); + astate = aq.get_final_persistent(); + LuaAssertStrEq(L, astate.debug_string(), "plane=earth xyz=1,2,3"); + astate = aq.get_final_everything(); + LuaAssertStrEq(L, astate.debug_string(), "action:jump bouncy:true plane=earth xyz=1,2,3"); + core = aq.get_final_core_state(); + LuaAssert(L, core.plane == "earth"); + LuaAssert(L, core.xyz == util::DXYZ(1,2,3)); + + // Serialize a queue. + // + aq.set_limit(10); + astate.clear_and_parse("xyz=1,2,3 plane=earth"); + aq.clear(astate); + astate.clear_and_parse("xyz=4,5,6 action:jump"); + aq.add(astate); + astate.clear_and_parse("plane=moon airline:southwest"); + aq.add(astate); + astate.clear_and_parse("color=blue"); + aq.add(astate); + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue"); + aq.serialize(&sb); + + // Deserialize a queue. + // + aqs.set_limit(7); + aqs.clear(); + LuaAssert(L, !aqs.exactly_equal(aq)); + aqs.deserialize(&sb); + LuaAssert(L, aqs.exactly_equal(aq)); + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue"); + + // Test diff and patch. + // + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue"); + aqs.set_limit(7); + aqs.clear(); + sb.clear(); + aqs.diff(aq, &sb); + //int difflen1 = sb.fill(); + LuaAssert(L, !aqs.exactly_equal(aq)); + aqs.patch(&sb, nullptr); + LuaAssert(L, aqs.exactly_equal(aq)); + LuaAssertStrEq(L, aqs.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue"); + + // Test that diff and patch are more efficient when the two queues contain some shared steps. + // + LuaAssertStrEq(L, aq.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue"); + astate.clear_and_parse("xyz=4,5,6 action:jump"); + aqs.clear(astate); + astate.clear_and_parse("plane=earth xyz=1,2,3"); + aqs.add(astate); + astate.clear_and_parse("plane=moon airline:southwest"); + aqs.add(astate); + LuaAssertStrEq(L, aqs.full_debug_string(), "limit=10; action:jump xyz=4,5,6; plane=earth xyz=1,2,3; airline:southwest plane=moon"); + sb.clear(); + aqs.diff(aq, &sb); + //int difflen2 = sb.fill(); + LuaAssert(L, !aqs.exactly_equal(aq)); + aqs.patch(&sb, nullptr); + LuaAssert(L, aqs.exactly_equal(aq)); + LuaAssertStrEq(L, aqs.full_debug_string(), "limit=10; plane=earth xyz=1,2,3; action:jump xyz=4,5,6; airline:southwest plane=moon; color=blue"); + + // TODO: if we make the diff routine more efficient, this should be true. + // LuaAssert(L, difflen2 < (difflen1 / 2)); + + return 0; +} diff --git a/luprex/cpp/core/animqueue.hpp b/luprex/cpp/core/animqueue.hpp new file mode 100644 index 00000000..13910233 --- /dev/null +++ b/luprex/cpp/core/animqueue.hpp @@ -0,0 +1,403 @@ +/////////////////////////////////////////////////////////////////// +// +// ANIMATION QUEUES +// +// An animation queue is a fifo queue of animation steps. New animations are +// pushed on the back, and old ones are popped from the front. +// +// An animation step is a set of key-value pairs, where each key is an +// identifier, and each value is either a number, a boolean, an XYZ coordinate, +// or a string. A key-value pair can be either persistent or nonpersistent. +// So a typical animation step might be: +// +// action=walk [nonpersistent] +// xyz=3,4,5 [persistent] +// facing=320 [persistent] +// plane=earth [persistent] +// +// Persistent values are retained from one animation step to the next, +// nonpersistent values exist for one animation step only. +// +// Each animation step has a hash value. The hash value is generated +// by mixing the hash value of the previous step with the hash value +// of the encoded string of key-value pairs. +// +/////////////////////////////////////////////////////////////////// +// +// SERIALIZED STORAGE +// +// The entired animation queue is stored in a serialized format, +// as a shared string. This means that the animation queue can be +// passed to the graphics engine as a single string. This can be +// accomplished by the function EngineWrapper.get_animation_queues. +// +// The fact that the queue is stored in a serialized format means +// that when manipulating the animation queue, we have to decode it, +// manipulate it, and then reencode it. +// +// From an efficiency perspective, this means that manipulation is +// slower, but passing the strings to the graphics engine is faster +// and simpler. This is a good tradeoff: we manipulate animation +// queues rarely, compared to how often we pass them to the graphics +// engine. +// +/////////////////////////////////////////////////////////////////// +// +// THE SERIALIZED REPRESENTATION +// +// So first, you need to know how to serialize a single animation +// step. Remember, an animation step consists of a list of key-value +// pairs (see above). The key-value pairs are serialized as follows: +// +// for all key-value pairs do: +// write_string(key) +// write_bool(persistent) +// write_uint8(type) // T_STRING, T_NUMBER, T_BOOL, or T_XYZ +// switch type: +// T_STRING: write_string(value) +// T_NUMBER: write_double(value) +// T_BOOL: write_bool(value) +// T_XYZ: write_xyz(value) +// +// The encoded string produced by the loop above is called an "encstep". +// That's short for "encoded animation step". The encstep has a hash +// value, which is a function that accepts the encstep and also the hash +// of the previous encstep. Note that the hash is not part of the encstep. +// +// A serialized animation queue consists of the following information: +// +// write_uint8(size_limit); +// write_uint8(actual_size); +// for all animation steps, starting with the most recent, do: +// write_uint64(hash) +// write_string(encstep) +// +// The encoded string produced by the loop above is called an "encqueue", +// because it encodes everything in the animation queue. +// +// Note that the 'serialize' routine for animation queues just returns +// the encqueue string, which is the whole thing. +// +// Since the steps in an encqueue are stored most-recent first, if you +// want some information about the most recent animation entry, you +// don't need to decode the entire encqueue. You only need to +// decode the first step. +// +/////////////////////////////////////////////////////////////////// + +#ifndef ANIMQUEUE_HPP +#define ANIMQUEUE_HPP + +#include "wrap-set.hpp" +#include "wrap-string.hpp" +#include "wrap-deque.hpp" +#include "wrap-unordered-map.hpp" + +#include "base-buffer.hpp" +#include "luastack.hpp" +#include "streambuffer.hpp" +#include "debugcollector.hpp" +#include "util.hpp" + +#include +#include + +struct AnimValue : public SimpleDynamicValue { + bool persistent; + + AnimValue() { persistent = false; } + + void set_dxyz(const util::DXYZ &xyz) { + type = SimpleDynamicTag::VECTOR; + s.clear(); x = xyz.x; y = xyz.y; z = xyz.z; + } +}; + + +class AnimState +{ +private: + using Map = eng::map; + Map map_; + + // Set the default value, internal + // + eng::string add_default(const eng::string &name, const AnimValue &v, const AnimState *other); + +public: + // Clear everything + // + void clear() { map_.clear(); } + + // Set the persistent flag on a single variable. + // If the variable isn't present, add it. + // + void set_persistent(const eng::string &name); + + // Return if it contains a value for the specified name. + // + bool contains(const eng::string &name) { return map_.find(name) != map_.end(); } + + // Set a single variable to a value of a specified type. + // If the variable isn't present, add it. + // + void set_boolean(const eng::string &name, bool v); + void set_number(const eng::string &name, double v); + void set_dxyz(const eng::string &name, const util::DXYZ &value); + void set_string(const eng::string &name, std::string_view value); + + // Print a debug string into the stringstream. + // + void print_debug_string(eng::ostringstream &oss); + + // Return the debug string. + eng::string debug_string(); + + // Constructs an empty state. + // + AnimState() {} + + // Convert to an encoded string. + // + eng::string encode() const; + + // Decode an encoded string. + // + void decode(std::string_view enc); + + // Decode an encoded string, discarding non-persistent data. + // + void decode_persistent(std::string_view enc); + + // Add default values for all builtin persistent variables. + // + // For each builtin default (plane, xyz, facing, bp) + // + // - Will generate an error if a value is already present, + // but the present value is of the wrong type. + // + // - If 'other' is not nullptr, then we look in 'other' for a default + // value. If a valid value of the correct type is present, it is + // used as the default value. + // + // - If no default value can be found in 'other', then a hardwired + // default value is provided. + // + eng::string add_defaults(const AnimState *other); + + // Parse an animstate from a Lua Table. + // + // Table keys must be valid lua identifiers. Table values may be string, + // floats, bool, or coordinates. Persistent flags are all set the same, + // from the persistent parameter. Returns empty string on success, or an + // error message on failure. + // + // If 'allowauto' is true, then the lua table may contain a key-value + // pair of the form (key, math.auto). These keys will be stored in the + // AnimState map with mapentry.type == SimpleDynamicTag::AUTO. + // This is done to express an intent that the value should be + // automatically computer later. + // + eng::string from_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent, bool allowauto); + + // Generate a merged animstate using a previous state and an update. + // + // Keys from both previous and update are combined to create this AnimState. + // Values from 'update' override values from 'previous'. Persistent flags + // are taken from 'previous'. If a key exists in both previous and update, + // and the key is persistent in 'previous', then the types must match, + // otherwise an error is generated. Returns empty string on success or + // an error message on failure. + // + // If a key in the 'update' map has type AUTO, then we will attempt to find + // a rule to compute that value automatically. Failure to find a rule + // results in an error. + // + eng::string merge(const AnimState &previous, const AnimState &update); + + // Convert an animstate to a lua table. + // + // You can either convert the persistent key-value pairs, or the + // nonpersistent. So you'll need two lua tables to store both. + // + void to_lua(LuaCoreStack &LS, LuaSlot tab, bool persistent); + + // Parse a string, for unit testing. + // + // This parses a simple notation designed to facilitate writing unit tests. + // The notation looks like this: + // + // parse("plane=earth xyz=1,2,3 action:jump"); + // + // Determining the type of the value is done as follows: First, try + // interpreting it as boolean true or false. If that fails, + // try interpreting it as a number. If that fails, try interpreting it as + // a coordinate. If all else fails, it's a string. Obviously, this is a + // limited approach: for example, there's no way to express the string "123" + // because "123" will get interpreted as a number. But that's ok, since this + // is purely intended for unit testing. + // + // Key-value pairs can have either an equal sign or a colon. If it's an equal + // sign, the persistent bit is set, colon means not persistent. + // + void parse(std::string_view s); + void clear_and_parse(std::string_view s); + + // Constructor from an encoded string. + // + AnimState(std::string_view s) { decode(s); } +}; + +struct AnimCoreState +{ + util::DXYZ xyz; + eng::string plane; + + void decode(std::string_view enc); +}; + +class AnimQueue : public eng::nevernew { +public: + // Construct an empty animation queue. + // clears the state to a valid state. + // + AnimQueue(); + + // Clear the steps to an initial state. + // + void clear(); + void clear(const AnimState &initial); + + // Change the size limit. + // + void set_limit(int limit); + + // Add an animation step. + // + // Note: add does not automatically compose the step with the previous + // step, you have to do that yourself. + // + void add(const AnimState &state); + + // Replace the most recent animation step. + // + // Note: replace does not automatically compose the step with the previous + // step, you have to do that yourself. + // + void replace(const AnimState &state); + + // Serialize or deserialize to a StreamBuffer + // + void serialize(StreamBuffer *sb) const; + void deserialize(StreamBuffer *sb); + + // Difference transmission + // + bool diff(const AnimQueue &auth, StreamBuffer *sb) const; + void patch(StreamBuffer *sb, DebugCollector *dbc); + + // Check for exactly equal. This does the full check of all + // fields, it is used exclusively for debugging. + // + bool exactly_equal(const AnimQueue &aq) const; + + // Check for exactly equal (fast). Compares the size, limit, + // and hash of the last entry. If these are equal, then the whole + // thing should be equal. + // + bool exactly_equal_fast(const AnimQueue &aq) const; + + // Debug strings. + // + void print_debug_string(eng::ostringstream &oss, bool full) const; + eng::string steps_debug_string() const; + eng::string full_debug_string() const; + + // Get the final entry, xyz and plane only. + // + AnimCoreState get_final_core_state() const; + + // Get the final entry, all persistent variables. + // + AnimState get_final_persistent() const; + + // Get the final entry, everything persistent and non-persistent. + // + AnimState get_final_everything() const; + + // Get a serialized representation of the animation queue. + // + // Get the entire animation queue in a serialized format (encqueue). + // The string returned is a shared string. No string copy + // is made during this process. + // + util::SharedStdString get_encoded_queue() const { return encqueue_; } + + // Get a serialized representation of a blank queue. + // + // Since an animqueue must have at least one step, the blank queue + // contains a single default step. + // + static util::SharedStdString get_encoded_blank_queue() { return blankqueue_; } + + // Initialize the animqueue module. + // + static void initialize_module(); + +private: + struct QueueRange { + int size; + std::string_view entries; + QueueRange(int sz, std::string_view ent) : size(sz), entries(ent) {} + }; + + // Get a range of entries from the queue. + // + // You must specify a range (lo-hi) of steps. In this numbering, 0 is the + // most recent entry in the queue. The indices lo and hi are automatically + // clamped to the valid range (0 to actual_size). If lo >= hi, then an + // empty range is returned. + // + QueueRange get_range(int lo, int hi); + + // Hash a new step given the range of steps that precede it. + // + static uint64_t hash_encstep(const QueueRange &prev, std::string_view step); + + // Update the animation queue. + // + // The range (keeplo to keephi) specifies which old steps should be + // retained. The numbers keephi and keeplo are automatically clamped + // so that they lie inside the actual size of the queue. + // + // If add is true, then an additional step is added to the queue. + // The hash of the new step is calculated automatically. + // + // There is no enforcement that you respected the size limit that you + // specified. For example, you could say "keep 0-5, and add 1." That + // would make 6 entries in the queue. It is up to the caller to respect + // the size limit. The value passed in is just for reporting. + // + void update_encqueue(int limit, bool add, std::string_view add_enc, int keeplo, int keephi); + + // Read values from the header of the encqueue. + // + int get_size_limit() const; + int get_actual_size() const; + uint64_t get_final_hash() const; + std::string_view get_final_encstep() const; + +private: + // Note: this is stored as a std::string, not an eng::string, because the + // ownership ends up being shared between us and the graphics engine. We + // can't have the graphics engine affecting the behavior of the engine heap. + // + util::SharedStdString encqueue_; + + // The blank animation queue. + // + static util::SharedStdString blankqueue_; +}; + +#endif // ANIMQUEUE_HPP + diff --git a/luprex/cpp/core/bytell-hash-map.hpp b/luprex/cpp/core/bytell-hash-map.hpp new file mode 100644 index 00000000..19c4b8be --- /dev/null +++ b/luprex/cpp/core/bytell-hash-map.hpp @@ -0,0 +1,1260 @@ +// Copyright Malte Skarupke 2017. +// Distributed under the Boost Software License, Version 1.0. +// (See http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "flat-hash-map.hpp" +#include +#include + +namespace ska +{ + +namespace detailv8 +{ +using ska::detailv3::functor_storage; +using ska::detailv3::KeyOrValueHasher; +using ska::detailv3::KeyOrValueEquality; +using ska::detailv3::AssignIfTrue; +using ska::detailv3::HashPolicySelector; + +template +struct sherwood_v8_constants +{ + static constexpr int8_t magic_for_empty = int8_t(0b11111111); + static constexpr int8_t magic_for_reserved = int8_t(0b11111110); + static constexpr int8_t bits_for_direct_hit = int8_t(0b10000000); + static constexpr int8_t magic_for_direct_hit = int8_t(0b00000000); + static constexpr int8_t magic_for_list_entry = int8_t(0b10000000); + + static constexpr int8_t bits_for_distance = int8_t(0b01111111); + inline static int distance_from_metadata(int8_t metadata) + { + return metadata & bits_for_distance; + } + + static constexpr int num_jump_distances = 126; + // jump distances chosen like this: + // 1. pick the first 16 integers to promote staying in the same block + // 2. add the next 66 triangular numbers to get even jumps when + // the hash table is a power of two + // 3. add 44 more triangular numbers at a much steeper growth rate + // to get a sequence that allows large jumps so that a table + // with 10000 sequential numbers doesn't endlessly re-allocate + static constexpr size_t jump_distances[num_jump_distances] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + + 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231, + 253, 276, 300, 325, 351, 378, 406, 435, 465, 496, 528, 561, 595, 630, + 666, 703, 741, 780, 820, 861, 903, 946, 990, 1035, 1081, 1128, 1176, + 1225, 1275, 1326, 1378, 1431, 1485, 1540, 1596, 1653, 1711, 1770, 1830, + 1891, 1953, 2016, 2080, 2145, 2211, 2278, 2346, 2415, 2485, 2556, + + 3741, 8385, 18915, 42486, 95703, 215496, 485605, 1091503, 2456436, + 5529475, 12437578, 27986421, 62972253, 141700195, 318819126, 717314626, + 1614000520, 3631437253, 8170829695, 18384318876, 41364501751, + 93070021080, 209407709220, 471167588430, 1060127437995, 2385287281530, + 5366895564381, 12075513791265, 27169907873235, 61132301007778, + 137547673121001, 309482258302503, 696335090510256, 1566753939653640, + 3525196427195653, 7931691866727775, 17846306747368716, + 40154190394120111, 90346928493040500, 203280588949935750, + 457381324898247375, 1029107980662394500, 2315492957028380766, + 5209859150892887590, + }; +}; +template +constexpr int8_t sherwood_v8_constants::magic_for_empty; +template +constexpr int8_t sherwood_v8_constants::magic_for_reserved; +template +constexpr int8_t sherwood_v8_constants::bits_for_direct_hit; +template +constexpr int8_t sherwood_v8_constants::magic_for_direct_hit; +template +constexpr int8_t sherwood_v8_constants::magic_for_list_entry; + +template +constexpr int8_t sherwood_v8_constants::bits_for_distance; + +template +constexpr int sherwood_v8_constants::num_jump_distances; +template +constexpr size_t sherwood_v8_constants::jump_distances[num_jump_distances]; + +template +struct sherwood_v8_block +{ + sherwood_v8_block() + { + } + ~sherwood_v8_block() + { + } + int8_t control_bytes[BlockSize]; + union + { + T data[BlockSize]; + }; + + static sherwood_v8_block * empty_block() + { + static std::array empty_bytes = [] + { + std::array result; + result.fill(sherwood_v8_constants<>::magic_for_empty); + return result; + }(); + return reinterpret_cast(&empty_bytes); + } + + int first_empty_index() const + { + for (int i = 0; i < BlockSize; ++i) + { + if (control_bytes[i] == sherwood_v8_constants<>::magic_for_empty) + return i; + } + return -1; + } + + void fill_control_bytes(int8_t value) + { + std::fill(std::begin(control_bytes), std::end(control_bytes), value); + } +}; + +template +class sherwood_v8_table : private ByteAlloc, private Hasher, private Equal +{ + using AllocatorTraits = std::allocator_traits; + using BlockType = sherwood_v8_block; + using BlockPointer = BlockType *; + using BytePointer = typename AllocatorTraits::pointer; + struct convertible_to_iterator; + using Constants = sherwood_v8_constants<>; + +public: + + using value_type = T; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using hasher = ArgumentHash; + using key_equal = ArgumentEqual; + using allocator_type = ByteAlloc; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = value_type *; + using const_pointer = const value_type *; + + sherwood_v8_table() + { + } + explicit sherwood_v8_table(size_type bucket_count, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) + : ByteAlloc(alloc), Hasher(hash), Equal(equal) + { + if (bucket_count) + rehash(bucket_count); + } + sherwood_v8_table(size_type bucket_count, const ArgumentAlloc & alloc) + : sherwood_v8_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v8_table(size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) + : sherwood_v8_table(bucket_count, hash, ArgumentEqual(), alloc) + { + } + explicit sherwood_v8_table(const ArgumentAlloc & alloc) + : ByteAlloc(alloc) + { + } + template + sherwood_v8_table(It first, It last, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) + : sherwood_v8_table(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + template + sherwood_v8_table(It first, It last, size_type bucket_count, const ArgumentAlloc & alloc) + : sherwood_v8_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + template + sherwood_v8_table(It first, It last, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) + : sherwood_v8_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v8_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) + : sherwood_v8_table(bucket_count, hash, equal, alloc) + { + if (bucket_count == 0) + rehash(il.size()); + insert(il.begin(), il.end()); + } + sherwood_v8_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc & alloc) + : sherwood_v8_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v8_table(std::initializer_list il, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) + : sherwood_v8_table(il, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v8_table(const sherwood_v8_table & other) + : sherwood_v8_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) + { + } + sherwood_v8_table(const sherwood_v8_table & other, const ArgumentAlloc & alloc) + : ByteAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor) + { + rehash_for_other_container(other); + try + { + insert(other.begin(), other.end()); + } + catch(...) + { + clear(); + deallocate_data(entries, num_slots_minus_one); + throw; + } + } + sherwood_v8_table(sherwood_v8_table && other) noexcept + : ByteAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)) + , _max_load_factor(other._max_load_factor) + { + swap_pointers(other); + } + sherwood_v8_table(sherwood_v8_table && other, const ArgumentAlloc & alloc) noexcept + : ByteAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)) + , _max_load_factor(other._max_load_factor) + { + swap_pointers(other); + } + sherwood_v8_table & operator=(const sherwood_v8_table & other) + { + if (this == std::addressof(other)) + return *this; + + clear(); + if (AllocatorTraits::propagate_on_container_copy_assignment::value) + { + if (static_cast(*this) != static_cast(other)) + { + reset_to_empty_state(); + } + AssignIfTrue()(*this, other); + } + _max_load_factor = other._max_load_factor; + static_cast(*this) = other; + static_cast(*this) = other; + rehash_for_other_container(other); + insert(other.begin(), other.end()); + return *this; + } + sherwood_v8_table & operator=(sherwood_v8_table && other) noexcept + { + if (this == std::addressof(other)) + return *this; + else if (AllocatorTraits::propagate_on_container_move_assignment::value) + { + clear(); + reset_to_empty_state(); + AssignIfTrue()(*this, std::move(other)); + swap_pointers(other); + } + else if (static_cast(*this) == static_cast(other)) + { + swap_pointers(other); + } + else + { + clear(); + _max_load_factor = other._max_load_factor; + rehash_for_other_container(other); + for (T & elem : other) + emplace(std::move(elem)); + other.clear(); + } + static_cast(*this) = std::move(other); + static_cast(*this) = std::move(other); + return *this; + } + ~sherwood_v8_table() + { + clear(); + deallocate_data(entries, num_slots_minus_one); + } + + const allocator_type & get_allocator() const + { + return static_cast(*this); + } + const ArgumentEqual & key_eq() const + { + return static_cast(*this); + } + const ArgumentHash & hash_function() const + { + return static_cast(*this); + } + + template + struct templated_iterator + { + private: + friend class sherwood_v8_table; + BlockPointer current = BlockPointer(); + size_t index = 0; + + public: + templated_iterator() + { + } + templated_iterator(BlockPointer entries, size_t index) + : current(entries) + , index(index) + { + } + + using iterator_category = std::forward_iterator_tag; + using value_type = ValueType; + using difference_type = ptrdiff_t; + using pointer = ValueType *; + using reference = ValueType &; + + friend bool operator==(const templated_iterator & lhs, const templated_iterator & rhs) + { + return lhs.index == rhs.index; + } + friend bool operator!=(const templated_iterator & lhs, const templated_iterator & rhs) + { + return !(lhs == rhs); + } + + templated_iterator & operator++() + { + do + { + if (index % BlockSize == 0) + --current; + if (index-- == 0) + break; + } + while(current->control_bytes[index % BlockSize] == Constants::magic_for_empty); + return *this; + } + templated_iterator operator++(int) + { + templated_iterator copy(*this); + ++*this; + return copy; + } + + ValueType & operator*() const + { + return current->data[index % BlockSize]; + } + ValueType * operator->() const + { + return current->data + index % BlockSize; + } + + operator templated_iterator() const + { + return { current, index }; + } + }; + using iterator = templated_iterator; + using const_iterator = templated_iterator; + + iterator begin() + { + size_t num_slots = num_slots_minus_one ? num_slots_minus_one + 1 : 0; + return ++iterator{ entries + num_slots / BlockSize, num_slots }; + } + const_iterator begin() const + { + size_t num_slots = num_slots_minus_one ? num_slots_minus_one + 1 : 0; + return ++iterator{ entries + num_slots / BlockSize, num_slots }; + } + const_iterator cbegin() const + { + return begin(); + } + iterator end() + { + return { entries - 1, std::numeric_limits::max() }; + } + const_iterator end() const + { + return { entries - 1, std::numeric_limits::max() }; + } + const_iterator cend() const + { + return end(); + } + + inline iterator find(const FindKey & key) + { + size_t index = hash_object(key); + size_t num_slots_minus_one = this->num_slots_minus_one; + BlockPointer entries = this->entries; + index = hash_policy.index_for_hash(index, num_slots_minus_one); + bool first = true; + for (;;) + { + size_t block_index = index / BlockSize; + int index_in_block = index % BlockSize; + BlockPointer block = entries + block_index; + int8_t metadata = block->control_bytes[index_in_block]; + if (first) + { + if ((metadata & Constants::bits_for_direct_hit) != Constants::magic_for_direct_hit) + return end(); + first = false; + } + if (compares_equal(key, block->data[index_in_block])) + return { block, index }; + int8_t to_next_index = metadata & Constants::bits_for_distance; + if (to_next_index == 0) + return end(); + index += Constants::jump_distances[to_next_index]; + index = hash_policy.keep_in_range(index, num_slots_minus_one); + } + } + inline const_iterator find(const FindKey & key) const + { + return const_cast(this)->find(key); + } + size_t count(const FindKey & key) const + { + return find(key) == end() ? 0 : 1; + } + std::pair equal_range(const FindKey & key) + { + iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + std::pair equal_range(const FindKey & key) const + { + const_iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + + + template + inline std::pair emplace(Key && key, Args &&... args) + { + size_t index = hash_object(key); + size_t num_slots_minus_one = this->num_slots_minus_one; + BlockPointer entries = this->entries; + index = hash_policy.index_for_hash(index, num_slots_minus_one); + bool first = true; + for (;;) + { + size_t block_index = index / BlockSize; + int index_in_block = index % BlockSize; + BlockPointer block = entries + block_index; + int8_t metadata = block->control_bytes[index_in_block]; + if (first) + { + if ((metadata & Constants::bits_for_direct_hit) != Constants::magic_for_direct_hit) + return emplace_direct_hit({ index, block }, std::forward(key), std::forward(args)...); + first = false; + } + if (compares_equal(key, block->data[index_in_block])) + return { { block, index }, false }; + int8_t to_next_index = metadata & Constants::bits_for_distance; + if (to_next_index == 0) + return emplace_new_key({ index, block }, std::forward(key), std::forward(args)...); + index += Constants::jump_distances[to_next_index]; + index = hash_policy.keep_in_range(index, num_slots_minus_one); + } + } + + std::pair insert(const value_type & value) + { + return emplace(value); + } + std::pair insert(value_type && value) + { + return emplace(std::move(value)); + } + template + iterator emplace_hint(const_iterator, Args &&... args) + { + return emplace(std::forward(args)...).first; + } + iterator insert(const_iterator, const value_type & value) + { + return emplace(value).first; + } + iterator insert(const_iterator, value_type && value) + { + return emplace(std::move(value)).first; + } + + template + void insert(It begin, It end) + { + for (; begin != end; ++begin) + { + emplace(*begin); + } + } + void insert(std::initializer_list il) + { + insert(il.begin(), il.end()); + } + + void rehash(size_t num_items) + { + num_items = std::max(num_items, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); + if (num_items == 0) + { + reset_to_empty_state(); + return; + } + auto new_prime_index = hash_policy.next_size_over(num_items); + if (num_items == num_slots_minus_one + 1) + return; + size_t num_blocks = num_items / BlockSize; + if (num_items % BlockSize) + ++num_blocks; + size_t memory_requirement = calculate_memory_requirement(num_blocks); + unsigned char * new_memory = &*AllocatorTraits::allocate(*this, memory_requirement); + + BlockPointer new_buckets = reinterpret_cast(new_memory); + + BlockPointer special_end_item = new_buckets + num_blocks; + for (BlockPointer it = new_buckets; it <= special_end_item; ++it) + it->fill_control_bytes(Constants::magic_for_empty); + using std::swap; + swap(entries, new_buckets); + swap(num_slots_minus_one, num_items); + --num_slots_minus_one; + hash_policy.commit(new_prime_index); + num_elements = 0; + if (num_items) + ++num_items; + size_t old_num_blocks = num_items / BlockSize; + if (num_items % BlockSize) + ++old_num_blocks; + for (BlockPointer it = new_buckets, end = new_buckets + old_num_blocks; it != end; ++it) + { + for (int i = 0; i < BlockSize; ++i) + { + int8_t metadata = it->control_bytes[i]; + if (metadata != Constants::magic_for_empty && metadata != Constants::magic_for_reserved) + { + emplace(std::move(it->data[i])); + AllocatorTraits::destroy(*this, it->data + i); + } + } + } + deallocate_data(new_buckets, num_items - 1); + } + + void reserve(size_t num_elements) + { + size_t required_buckets = num_buckets_for_reserve(num_elements); + if (required_buckets > bucket_count()) + rehash(required_buckets); + } + + // the return value is a type that can be converted to an iterator + // the reason for doing this is that it's not free to find the + // iterator pointing at the next element. if you care about the + // next iterator, turn the return value into an iterator + convertible_to_iterator erase(const_iterator to_erase) + { + LinkedListIt current = { to_erase.index, to_erase.current }; + if (current.has_next()) + { + LinkedListIt previous = current; + LinkedListIt next = current.next(*this); + while (next.has_next()) + { + previous = next; + next = next.next(*this); + } + AllocatorTraits::destroy(*this, std::addressof(*current)); + AllocatorTraits::construct(*this, std::addressof(*current), std::move(*next)); + AllocatorTraits::destroy(*this, std::addressof(*next)); + next.set_metadata(Constants::magic_for_empty); + previous.clear_next(); + } + else + { + if (!current.is_direct_hit()) + find_parent_block(current).clear_next(); + AllocatorTraits::destroy(*this, std::addressof(*current)); + current.set_metadata(Constants::magic_for_empty); + } + --num_elements; + return { to_erase.current, to_erase.index }; + } + + iterator erase(const_iterator begin_it, const_iterator end_it) + { + if (begin_it == end_it) + return { begin_it.current, begin_it.index }; + if (std::next(begin_it) == end_it) + return erase(begin_it); + if (begin_it == begin() && end_it == end()) + { + clear(); + return { end_it.current, end_it.index }; + } + std::vector> depth_in_chain; + for (const_iterator it = begin_it; it != end_it; ++it) + { + LinkedListIt list_it(it.index, it.current); + if (list_it.is_direct_hit()) + depth_in_chain.emplace_back(0, list_it); + else + { + LinkedListIt root = find_direct_hit(list_it); + int distance = 1; + for (;;) + { + LinkedListIt next = root.next(*this); + if (next == list_it) + break; + ++distance; + root = next; + } + depth_in_chain.emplace_back(distance, list_it); + } + } + std::sort(depth_in_chain.begin(), depth_in_chain.end(), [](const auto & a, const auto & b) { return a.first < b.first; }); + for (auto it = depth_in_chain.rbegin(), end = depth_in_chain.rend(); it != end; ++it) + { + erase(it->second.it()); + } + + if (begin_it.current->control_bytes[begin_it.index % BlockSize] == Constants::magic_for_empty) + return ++iterator{ begin_it.current, begin_it.index }; + else + return { begin_it.current, begin_it.index }; + } + + size_t erase(const FindKey & key) + { + auto found = find(key); + if (found == end()) + return 0; + else + { + erase(found); + return 1; + } + } + + void clear() + { + if (!num_slots_minus_one) + return; + size_t num_slots = num_slots_minus_one + 1; + size_t num_blocks = num_slots / BlockSize; + if (num_slots % BlockSize) + ++num_blocks; + for (BlockPointer it = entries, end = it + num_blocks; it != end; ++it) + { + for (int i = 0; i < BlockSize; ++i) + { + if (it->control_bytes[i] != Constants::magic_for_empty) + { + AllocatorTraits::destroy(*this, std::addressof(it->data[i])); + it->control_bytes[i] = Constants::magic_for_empty; + } + } + } + num_elements = 0; + } + + void shrink_to_fit() + { + rehash_for_other_container(*this); + } + + void swap(sherwood_v8_table & other) + { + using std::swap; + swap_pointers(other); + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + if (AllocatorTraits::propagate_on_container_swap::value) + swap(static_cast(*this), static_cast(other)); + } + + size_t size() const + { + return num_elements; + } + size_t max_size() const + { + return (AllocatorTraits::max_size(*this)) / sizeof(T); + } + size_t bucket_count() const + { + return num_slots_minus_one ? num_slots_minus_one + 1 : 0; + } + size_type max_bucket_count() const + { + return (AllocatorTraits::max_size(*this)) / sizeof(T); + } + size_t bucket(const FindKey & key) const + { + return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + } + float load_factor() const + { + return static_cast(num_elements) / (num_slots_minus_one + 1); + } + void max_load_factor(float value) + { + _max_load_factor = value; + } + float max_load_factor() const + { + return _max_load_factor; + } + + bool empty() const + { + return num_elements == 0; + } + +private: + BlockPointer entries = BlockType::empty_block(); + size_t num_slots_minus_one = 0; + typename HashPolicySelector::type hash_policy; + float _max_load_factor = 0.9375f; + size_t num_elements = 0; + + size_t num_buckets_for_reserve(size_t num_elements) const + { + return static_cast(std::ceil(num_elements / static_cast(_max_load_factor))); + } + void rehash_for_other_container(const sherwood_v8_table & other) + { + rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count())); + } + bool is_full() const + { + if (!num_slots_minus_one) + return true; + else + return num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor); + } + + void swap_pointers(sherwood_v8_table & other) + { + using std::swap; + swap(hash_policy, other.hash_policy); + swap(entries, other.entries); + swap(num_slots_minus_one, other.num_slots_minus_one); + swap(num_elements, other.num_elements); + swap(_max_load_factor, other._max_load_factor); + } + + struct LinkedListIt + { + size_t index = 0; + BlockPointer block = nullptr; + + LinkedListIt() + { + } + LinkedListIt(size_t index, BlockPointer block) + : index(index), block(block) + { + } + + iterator it() const + { + return { block, index }; + } + int index_in_block() const + { + return index % BlockSize; + } + bool is_direct_hit() const + { + return (metadata() & Constants::bits_for_direct_hit) == Constants::magic_for_direct_hit; + } + bool is_empty() const + { + return metadata() == Constants::magic_for_empty; + } + bool has_next() const + { + return jump_index() != 0; + } + int8_t jump_index() const + { + return Constants::distance_from_metadata(metadata()); + } + int8_t metadata() const + { + return block->control_bytes[index_in_block()]; + } + void set_metadata(int8_t metadata) + { + block->control_bytes[index_in_block()] = metadata; + } + + LinkedListIt next(sherwood_v8_table & table) const + { + int8_t distance = jump_index(); + size_t next_index = table.hash_policy.keep_in_range(index + Constants::jump_distances[distance], table.num_slots_minus_one); + return { next_index, table.entries + next_index / BlockSize }; + } + void set_next(int8_t jump_index) + { + int8_t & metadata = block->control_bytes[index_in_block()]; + metadata = (metadata & ~Constants::bits_for_distance) | jump_index; + } + void clear_next() + { + set_next(0); + } + + value_type & operator*() const + { + return block->data[index_in_block()]; + } + bool operator!() const + { + return !block; + } + explicit operator bool() const + { + return block != nullptr; + } + bool operator==(const LinkedListIt & other) const + { + return index == other.index; + } + bool operator!=(const LinkedListIt & other) const + { + return !(*this == other); + } + }; + + template + SKA_NOINLINE(std::pair) emplace_direct_hit(LinkedListIt block, Args &&... args) + { + using std::swap; + if (is_full()) + { + grow(); + return emplace(std::forward(args)...); + } + if (block.metadata() == Constants::magic_for_empty) + { + AllocatorTraits::construct(*this, std::addressof(*block), std::forward(args)...); + block.set_metadata(Constants::magic_for_direct_hit); + ++num_elements; + return { block.it(), true }; + } + else + { + LinkedListIt parent_block = find_parent_block(block); + std::pair free_block = find_free_index(parent_block); + if (!free_block.first) + { + grow(); + return emplace(std::forward(args)...); + } + value_type new_value(std::forward(args)...); + for (LinkedListIt it = block;;) + { + AllocatorTraits::construct(*this, std::addressof(*free_block.second), std::move(*it)); + AllocatorTraits::destroy(*this, std::addressof(*it)); + parent_block.set_next(free_block.first); + free_block.second.set_metadata(Constants::magic_for_list_entry); + if (!it.has_next()) + { + it.set_metadata(Constants::magic_for_empty); + break; + } + LinkedListIt next = it.next(*this); + it.set_metadata(Constants::magic_for_empty); + block.set_metadata(Constants::magic_for_reserved); + it = next; + parent_block = free_block.second; + free_block = find_free_index(free_block.second); + if (!free_block.first) + { + grow(); + return emplace(std::move(new_value)); + } + } + AllocatorTraits::construct(*this, std::addressof(*block), std::move(new_value)); + block.set_metadata(Constants::magic_for_direct_hit); + ++num_elements; + return { block.it(), true }; + } + } + + template + SKA_NOINLINE(std::pair) emplace_new_key(LinkedListIt parent, Args &&... args) + { + if (is_full()) + { + grow(); + return emplace(std::forward(args)...); + } + std::pair free_block = find_free_index(parent); + if (!free_block.first) + { + grow(); + return emplace(std::forward(args)...); + } + AllocatorTraits::construct(*this, std::addressof(*free_block.second), std::forward(args)...); + free_block.second.set_metadata(Constants::magic_for_list_entry); + parent.set_next(free_block.first); + ++num_elements; + return { free_block.second.it(), true }; + } + + LinkedListIt find_direct_hit(LinkedListIt child) const + { + size_t to_move_hash = hash_object(*child); + size_t to_move_index = hash_policy.index_for_hash(to_move_hash, num_slots_minus_one); + return { to_move_index, entries + to_move_index / BlockSize }; + } + LinkedListIt find_parent_block(LinkedListIt child) + { + LinkedListIt parent_block = find_direct_hit(child); + for (;;) + { + LinkedListIt next = parent_block.next(*this); + if (next == child) + return parent_block; + parent_block = next; + } + } + + std::pair find_free_index(LinkedListIt parent) const + { + for (int8_t jump_index = 1; jump_index < Constants::num_jump_distances; ++jump_index) + { + size_t index = hash_policy.keep_in_range(parent.index + Constants::jump_distances[jump_index], num_slots_minus_one); + BlockPointer block = entries + index / BlockSize; + if (block->control_bytes[index % BlockSize] == Constants::magic_for_empty) + return { jump_index, { index, block } }; + } + return { 0, {} }; + } + + void grow() + { + rehash(std::max(size_t(10), 2 * bucket_count())); + } + + size_t calculate_memory_requirement(size_t num_blocks) + { + size_t memory_required = sizeof(BlockType) * num_blocks; + memory_required += BlockSize; // for metadata of past-the-end pointer + return memory_required; + } + + void deallocate_data(BlockPointer begin, size_t num_slots_minus_one) + { + if (begin == BlockType::empty_block()) + return; + + ++num_slots_minus_one; + size_t num_blocks = num_slots_minus_one / BlockSize; + if (num_slots_minus_one % BlockSize) + ++num_blocks; + size_t memory = calculate_memory_requirement(num_blocks); + unsigned char * as_byte_pointer = reinterpret_cast(begin); + AllocatorTraits::deallocate(*this, typename AllocatorTraits::pointer(as_byte_pointer), memory); + } + + void reset_to_empty_state() + { + deallocate_data(entries, num_slots_minus_one); + entries = BlockType::empty_block(); + num_slots_minus_one = 0; + hash_policy.reset(); + } + + template + size_t hash_object(const U & key) + { + return static_cast(*this)(key); + } + template + size_t hash_object(const U & key) const + { + return static_cast(*this)(key); + } + template + bool compares_equal(const L & lhs, const R & rhs) + { + return static_cast(*this)(lhs, rhs); + } + + struct convertible_to_iterator + { + BlockPointer it; + size_t index; + + operator iterator() + { + if (it->control_bytes[index % BlockSize] == Constants::magic_for_empty) + return ++iterator{it, index}; + else + return { it, index }; + } + operator const_iterator() + { + if (it->control_bytes[index % BlockSize] == Constants::magic_for_empty) + return ++iterator{it, index}; + else + return { it, index }; + } + }; +}; +template +struct AlignmentOr8Bytes +{ + static constexpr size_t value = 8; +}; +template +struct AlignmentOr8Bytes= 1>::type> +{ + static constexpr size_t value = alignof(T); +}; +template +struct CalculateBytellBlockSize; +template +struct CalculateBytellBlockSize +{ + static constexpr size_t this_value = AlignmentOr8Bytes::value; + static constexpr size_t base_value = CalculateBytellBlockSize::value; + static constexpr size_t value = this_value > base_value ? this_value : base_value; +}; +template<> +struct CalculateBytellBlockSize<> +{ + static constexpr size_t value = 8; +}; +} + +template, typename E = std::equal_to, typename A = std::allocator > > +class bytell_hash_map + : public detailv8::sherwood_v8_table + < + std::pair, + K, + H, + detailv8::KeyOrValueHasher, H>, + E, + detailv8::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc, + detailv8::CalculateBytellBlockSize::value + > +{ + using Table = detailv8::sherwood_v8_table + < + std::pair, + K, + H, + detailv8::KeyOrValueHasher, H>, + E, + detailv8::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc, + detailv8::CalculateBytellBlockSize::value + >; +public: + + using key_type = K; + using mapped_type = V; + + using Table::Table; + bytell_hash_map() + { + } + + inline V & operator[](const K & key) + { + return emplace(key, convertible_to_value()).first->second; + } + inline V & operator[](K && key) + { + return emplace(std::move(key), convertible_to_value()).first->second; + } + V & at(const K & key) + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + const V & at(const K & key) const + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + + using Table::emplace; + std::pair emplace() + { + return emplace(key_type(), convertible_to_value()); + } + template + std::pair insert_or_assign(const key_type & key, M && m) + { + auto emplace_result = emplace(key, std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + std::pair insert_or_assign(key_type && key, M && m) + { + auto emplace_result = emplace(std::move(key), std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type & key, M && m) + { + return insert_or_assign(key, std::forward(m)).first; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type && key, M && m) + { + return insert_or_assign(std::move(key), std::forward(m)).first; + } + + friend bool operator==(const bytell_hash_map & lhs, const bytell_hash_map & rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const typename Table::value_type & value : lhs) + { + auto found = rhs.find(value.first); + if (found == rhs.end()) + return false; + else if (value.second != found->second) + return false; + } + return true; + } + friend bool operator!=(const bytell_hash_map & lhs, const bytell_hash_map & rhs) + { + return !(lhs == rhs); + } + +private: + struct convertible_to_value + { + operator V() const + { + return V(); + } + }; +}; + +template, typename E = std::equal_to, typename A = std::allocator > +class bytell_hash_set + : public detailv8::sherwood_v8_table + < + T, + T, + H, + detailv8::functor_storage, + E, + detailv8::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc, + detailv8::CalculateBytellBlockSize::value + > +{ + using Table = detailv8::sherwood_v8_table + < + T, + T, + H, + detailv8::functor_storage, + E, + detailv8::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc, + detailv8::CalculateBytellBlockSize::value + >; +public: + + using key_type = T; + + using Table::Table; + bytell_hash_set() + { + } + + template + std::pair emplace(Args &&... args) + { + return Table::emplace(T(std::forward(args)...)); + } + std::pair emplace(const key_type & arg) + { + return Table::emplace(arg); + } + std::pair emplace(key_type & arg) + { + return Table::emplace(arg); + } + std::pair emplace(const key_type && arg) + { + return Table::emplace(std::move(arg)); + } + std::pair emplace(key_type && arg) + { + return Table::emplace(std::move(arg)); + } + + friend bool operator==(const bytell_hash_set & lhs, const bytell_hash_set & rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const T & value : lhs) + { + if (rhs.find(value) == rhs.end()) + return false; + } + return true; + } + friend bool operator!=(const bytell_hash_set & lhs, const bytell_hash_set & rhs) + { + return !(lhs == rhs); + } +}; + +} // end namespace ska diff --git a/luprex/cpp/core/debugcollector.cpp b/luprex/cpp/core/debugcollector.cpp new file mode 100644 index 00000000..95404a23 --- /dev/null +++ b/luprex/cpp/core/debugcollector.cpp @@ -0,0 +1,89 @@ + + +#include +#include + +#include "debugcollector.hpp" +#include "util.hpp" + +void DebugCollector::flush() { + if (need_flush_) { + eng::string str = oss_.str(); + if (!str.empty()) { + if (is_regular_) n_regular_ += 1; + lines_.push_back(str); + oss_.str(""); + } + need_flush_ = false; + } +} + +DebugCollector::DebugCollector() : n_regular_(0), need_flush_(false), is_regular_(true), active_(false) { +} + +DebugCollector::DebugCollector(const eng::string &targets) + : n_regular_(0), need_flush_(false), is_regular_(true), active_(false) { + targets_ = util::split(targets, ','); + std::sort(targets_.begin(), targets_.end()); +} + +bool DebugCollector::use(const char *target) { + int lo = 0; + int hi = targets_.size(); + while (hi > lo) { + int mid = (hi + lo) >> 1; + int cmp = strcmp(targets_[mid].c_str(), target); + if (cmp == 0) return true; + if (cmp > 0) hi = mid; + else lo = mid + 1; + } + return false; +} + +bool DebugCollector::do_line() { + if (active_) { + flush(); + need_flush_ = true; + is_regular_ = true; + return true; + } else return false; +} + +bool DebugCollector::do_header() { + flush(); + need_flush_ = true; + is_regular_ = false; + return true; +} + +void DebugCollector::dump(std::ostream &os) { + flush(); + for (const eng::string &line : lines_) { + os << line << std::endl; + } +} + +DebugBlock::DebugBlock(DebugCollector *dbc, const char *target) { + dbc_ = dbc; + if (dbc) { + n_regular_ = dbc->n_regular_; + n_lines_ = dbc->lines_.size(); + active_ = (!dbc->active_) && (dbc->use(target)); + if (active_) dbc_->active_ = true; + } +} + +DebugBlock::~DebugBlock() { + if (dbc_) { + dbc_->flush(); + // If no regular lines have been added, + // remove all header lines that were added. + if (dbc_->n_regular_ == n_regular_) { + if (dbc_->lines_.size() != n_lines_) { + dbc_->lines_.resize(n_lines_); + } + } + if (active_) dbc_->active_ = false; + } +} + diff --git a/luprex/cpp/core/debugcollector.hpp b/luprex/cpp/core/debugcollector.hpp new file mode 100644 index 00000000..947f90d6 --- /dev/null +++ b/luprex/cpp/core/debugcollector.hpp @@ -0,0 +1,51 @@ +#ifndef DEBUGCOLLECTOR_HPP +#define DEBUGCOLLECTOR_HPP + +#include "wrap-vector.hpp" +#include "wrap-string.hpp" +#include "wrap-sstream.hpp" + +#include +#include + +class DebugCollector : public eng::nevernew { +private: + // At any given time, the stringstream may contain one + // extra line that hasn't yet been appended to the list of lines. + // The flag 'need_flush_' is true if the stringstream contains + // an extra line. In that case, is_regular_ indicates whether + // the extra line is a header or a regular line. + eng::vector lines_; + eng::vector targets_; + int n_regular_; + bool need_flush_; + bool is_regular_; + bool active_; + void flush(); + bool use(const char *target); +public: + eng::ostringstream oss_; + DebugCollector(); + DebugCollector(const eng::string &targets); + bool do_header(); + bool do_line(); + void dump(std::ostream &os); + friend class DebugBlock; +}; + +class DebugBlock : public eng::nevernew { +private: + DebugCollector *dbc_; + int n_regular_; + size_t n_lines_; + bool active_; +public: + DebugBlock(DebugCollector *dbc, const char *target); + ~DebugBlock(); +}; + +#define DebugHeader(dbc) if (((dbc)!=nullptr) && (dbc)->do_header()) ((dbc)->oss_) +#define DebugLine(dbc) if (((dbc)!=nullptr) && (dbc)->do_line()) ((dbc)->oss_) + +#endif // DEBUGCOLLECTOR_HPP + diff --git a/luprex/cpp/core/drivenengine.cpp b/luprex/cpp/core/drivenengine.cpp new file mode 100644 index 00000000..6da6d905 --- /dev/null +++ b/luprex/cpp/core/drivenengine.cpp @@ -0,0 +1,980 @@ +#include "wrap-string.hpp" +#include "wrap-vector.hpp" +#include "util.hpp" +#include "drivenengine.hpp" +#include "world.hpp" +#include "base-buffer.hpp" + +#include +#include +#include +#include +#include +#include + +DrivenEngineReg *DrivenEngineReg::All; + +DrivenEngineReg::DrivenEngineReg(const char *n, DrivenEngineMaker fn) { + name = n; + maker = fn; + next = All; + All = this; +} + +DrivenEngineInitializer DrivenEngineInitializerReg::func; + +DrivenEngineInitializerReg::DrivenEngineInitializerReg(DrivenEngineInitializer fn) { + assert(func == nullptr); + func = fn; +} + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// DrivenEngine private methods +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +int DrivenEngine::find_unused_chid() { + // Note: channel ID zero is special, it is never reused. + for (int i = 0; i < DRV_MAX_CHAN; i++) { + int id = next_unused_chid_++; + if (next_unused_chid_ == DRV_MAX_CHAN) next_unused_chid_ = 1; + if (channels_[id] == nullptr) return id; + } + assert(false); + return 0; +} + +Channel *DrivenEngine::get_chid(int chid) const { + assert(unsigned(chid) < DRV_MAX_CHAN); + assert(channels_[chid].get() != nullptr); + return channels_[chid].get(); +} + +static DrivenEngine *make_engine(std::string_view kind) { + for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) { + if (kind == reg->name) { + UniqueDrivenEngine result = reg->maker(); + return result.release(); + } + } + return nullptr; +} + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// The stdostream +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +class StreamBufferWriter : public std::streambuf, public eng::opnew { +private: + StreamBuffer *target_; +public: + StreamBufferWriter(StreamBuffer *t) : target_(t) {} + + virtual int_type overflow(int_type c) { + if (c != EOF) { + target_->write_uint8(c); + } + return c; + } +}; + +class StreamBufferOStream : public std::ostream, public eng::opnew { +private: + StreamBufferWriter writer_; +public: + StreamBufferOStream(StreamBuffer *t) : std::ostream(nullptr), writer_(t) { + rdbuf(&writer_); + } + virtual ~StreamBufferOStream() { + } +}; + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// Class Channel +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +Channel::Channel(DrivenEngine *de, int chid, int port, const eng::string &target, bool stop) { + chid_ = chid; + port_ = port; + closed_ = false; + target_ = target; + stop_driver_ = stop; + sb_in_ = eng::make_shared(); + sb_out_ = eng::make_shared(); +} + +std::string_view Channel::peek_outgoing() const { + return sb_out_->view(); +} + +void Channel::sent_outgoing(int nbytes) { + sb_out_->read_bytes(nbytes); +} + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// DrivenEngine Client-Side API +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +void DrivenEngine::listen_port(int port) { + assert(listen_ports_.size() < DRV_MAX_LISTEN_PORTS); + listen_ports_.push_back(port); +} + +double DrivenEngine::get_clock() { + return clock_; +} + +SharedChannel DrivenEngine::new_outgoing_channel(const eng::string &target) { + int chid = find_unused_chid(); + new_outgoing_.push_back(chid); + SharedChannel result = eng::make_shared(this, chid, 0, target, stop_driver_); + channels_[chid] = result; + return result; +} + +SharedChannel DrivenEngine::new_incoming_channel() { + if (accepted_channels_.empty()) { + return nullptr; + } else { + SharedChannel result = std::move(accepted_channels_.back()); + accepted_channels_.pop_back(); + return result; + } +} + +SharedChannel DrivenEngine::get_stdio_channel() { + return stdio_channel_; +} + +void DrivenEngine::set_console_prompt(const eng::string &prompt) { + console_prompt_ = prompt; +} + +void DrivenEngine::rescan_lua_source() { + rescan_lua_source_ = true; +} + +void DrivenEngine::set_visible_world_and_actor(World *w, int64_t id) { + visible_world_ = w; + visible_actor_id_ = id; +} + +void DrivenEngine::stop_driver() { + stop_driver_ = true; + for (int i = 0; i < DRV_MAX_CHAN; i++) { + if (channels_[i] != nullptr) { + channels_[i]->stop_driver_ = true; + } + } +} + +DrivenEngine::DrivenEngine() { + next_unused_chid_ = 1; + stdio_channel_ = eng::make_shared(this, 0, 0, "", false); + stdostream_.reset(new StreamBufferOStream(stdio_channel_->out())); + channels_[0] = stdio_channel_; + rescan_lua_source_ = false; + clock_ = 0.0; + stop_driver_ = false; +} + +DrivenEngine::~DrivenEngine() {} + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// LOGFILE EVENT IDS. +// +// There's one event ID for each mutator, plus one for 'release'. +// +// There are no event IDs for getters, these aren't considered loggable events. +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +enum DrvAction { + PLAY_INITIALIZE, + PLAY_CLEAR_NEW_OUTGOING, + PLAY_SENT_OUTGOING, + PLAY_RECV_INCOMING, + PLAY_NOTIFY_CLOSE, + PLAY_NOTIFY_ACCEPT, + PLAY_UPDATE, + PLAY_CALL_FUNCTION, + PLAY_RELEASE, +}; + +inline static const char *action_string(DrvAction act) { + switch(act) { + case PLAY_INITIALIZE: return "PLAY_INITIALIZE"; + case PLAY_CLEAR_NEW_OUTGOING: return "PLAY_CLEAR_NEW_OUTGOING"; + case PLAY_SENT_OUTGOING: return "PLAY_SENT_OUTGOING"; + case PLAY_RECV_INCOMING: return "PLAY_RECV_INCOMING"; + case PLAY_NOTIFY_CLOSE: return "PLAY_NOTIFY_CLOSE"; + case PLAY_NOTIFY_ACCEPT: return "PLAY_NOTIFY_ACCEPT"; + case PLAY_UPDATE: return "PLAY_UPDATE"; + case PLAY_CALL_FUNCTION: return "PLAY_CALL_FUNCTION"; + case PLAY_RELEASE: return "PLAY_RELEASE"; + default: return "unknown"; + } +} +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// RLOG and WLOG, functions to read and write binary data to logfiles. +// +// After doing an rlog operation, you should check the stream +// for "good" to find out if there was any error. +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +class PlayLogfile : public BaseWriter, public std::ofstream { + using std::ofstream::ofstream; +public: + void write_bytes(const char *n, size_t size) { write(n, size); } + void raise_truncated() { + fprintf(stderr, "number exceeds allowable size\n"); + std::abort(); + } + void write_short_string(std::string_view v) { + assert(v.size() < DRV_SHORTSTRING_SIZE); + write_string(v); + } + void write_cmd_hash(DrvAction act, uint32_t hash) { + write_uint8(act); + write_uint32(hash); + } +}; + +class ReplayLogfile : public BaseReader, public std::ifstream { + using std::ifstream::ifstream; +public: + using read_string_type = std::string; + void read_bytes_into(char *n, size_t size) { + read(n, size); + if (!good()) { + memset(n, 0, size); + } + } + void raise_string_too_long() { + fprintf(stderr, "string in logfile is too long"); + std::abort(); + } + std::string_view read_short_string(EngineWrapper *w) { + size_t size = read_length(); + assert(size <= DRV_SHORTSTRING_SIZE); + if (size > 0) read(w->databuffer, size); + if (!good()) return std::string_view(); + return std::string_view(w->databuffer, size); + } +}; + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// reset_wrapper +// +// Shut down a EngineWrapper, store an optional error message. +// +// release +// +// Shut down an EngineWrapper cleanly, with no error message, and +// log the step if the logfile is open. +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static void reset_wrapper(EngineWrapper *w, const char *format, ...) { + va_list argp; + va_start(argp, format); + memset(w->error, 0, DRV_ERRMSG_SIZE); + vsnprintf(w->error, DRV_ERRMSG_SIZE, format, argp); + w->error[DRV_ERRMSG_SIZE - 1] = 0; + + if (w->wlog != nullptr) { + w->wlog->close(); + delete w->wlog; + w->wlog = nullptr; + } + + if (w->rlog != nullptr) { + w->rlog->close(); + delete w->rlog; + w->rlog = nullptr; + } + + if (w->engine != nullptr) { + delete w->engine; + w->engine = nullptr; + } +} + +static void release(EngineWrapper *w) { + if (w->wlog != nullptr) { + w->wlog->write_cmd_hash(PLAY_RELEASE, eng::memhash()); + } + reset_wrapper(w, ""); +}; + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// DRIVER Methods: Getters +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +void DrivenEngine::drv_get_listen_ports(uint32_t *nports, const uint32_t **ports) const { + *nports = listen_ports_.size(); + *ports = &listen_ports_[0]; +} + +void DrivenEngine::drv_get_new_outgoing(uint32_t *nchids, const uint32_t **chids) const { + *nchids = new_outgoing_.size(); + *chids = &new_outgoing_[0]; +} + +const char *DrivenEngine::drv_get_target(uint32_t chid) const { + return get_chid(chid)->target_.c_str(); +} + +bool DrivenEngine::drv_get_channel_released(uint32_t chid) const { + return channels_[chid].use_count() == 1; +} + +void DrivenEngine::drv_get_outgoing(uint32_t chid, uint32_t *len, const char **data) const { + std::string_view v = get_chid(chid)->peek_outgoing(); + *len = v.size(); + *data = v.data(); +} + +bool DrivenEngine::drv_get_outgoing_empty(uint32_t chid) const { + std::string_view v = get_chid(chid)->peek_outgoing(); + return (v.size() == 0); +} + +void DrivenEngine::drv_get_console_prompt(uint32_t *len, const char **data) const { + *len = console_prompt_.size(); + *data = console_prompt_.c_str(); +} + +double DrivenEngine::drv_get_clock() const { + return clock_; +} + +bool DrivenEngine::drv_get_rescan_lua_source() const { + return rescan_lua_source_; +} + +bool DrivenEngine::drv_get_stop_driver() const { + return stop_driver_; +} + +int64_t DrivenEngine::drv_get_actor_id() const { + return visible_actor_id_; +} + +void DrivenEngine::drv_get_tangibles_near(int64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids) { + uint32_t hash1 = eng::memhash(); + scan_result_.clear(); + if ((visible_world_ != 0) && (tanid != 0)) { + PlaneScan scan; + scan.set_near(tanid, true); + scan.set_omit_nowhere(true); + scan.set_sorted(false); + scan.set_radius(util::XYZ(rx, ry, rz)); + scan.set_shape(PlaneScan::CYLINDER); + visible_world_->get_near(scan, &scan_result_); + } + *count = scan_result_.size(); + if (*count > 0) { + *ids = &scan_result_[0]; + } else { + *ids = nullptr; + } + uint32_t hash2 = eng::memhash(); + assert(hash1 == hash2); +} + +void DrivenEngine::drv_get_animation_queues(uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings) { + anim_queues_.resize(count); + + if (visible_world_ == nullptr) { + for (int i = 0; i < int(count); i++) { + anim_queues_[i] = AnimQueue::get_encoded_blank_queue(); + } + } else { + visible_world_->get_encoded_animation_queues(count, ids, anim_queues_); + } + for (int i = 0; i < int(count); i++) { + lengths[i] = anim_queues_[i]->size(); + strings[i] = anim_queues_[i]->c_str(); + } +} + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// DRIVER Methods: Mutators +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +void DrivenEngine::drv_initialize(uint32_t srcpklen, const char *srcpk, int argc, char **argv) { + event_init(std::string_view(srcpk, srcpklen), argc, argv); +} + +void DrivenEngine::drv_clear_new_outgoing() { + new_outgoing_.clear(); +} + +void DrivenEngine::drv_sent_outgoing(uint32_t chid, uint32_t nbytes) { + return get_chid(chid)->sent_outgoing(nbytes); +} + +void DrivenEngine::drv_recv_incoming(uint32_t chid, uint32_t nbytes, const char *bytes) { + if (nbytes > 0) { + std::string_view sbytes(bytes, nbytes); + Channel *ch = get_chid(chid); + ch->sb_in_->write_bytes(sbytes); + } +} + +void DrivenEngine::drv_notify_close(uint32_t chid, uint32_t len, const char *data) { + Channel *ch = get_chid(chid); + ch->closed_ = true; + ch->error_ = std::string(data, len); + channels_[chid].reset(); +} + +uint32_t DrivenEngine::drv_notify_accept(uint32_t port) { + int chid = find_unused_chid(); + channels_[chid] = eng::make_shared(this, chid, port, "", stop_driver_); + accepted_channels_.push_back(channels_[chid]); + return chid; +} + +void DrivenEngine::drv_update(double clock) { + clock_ = clock; + event_update(); +} + +void DrivenEngine::drv_call_function(InvocationKind kind, int64_t place, uint32_t datapklen, const char *datapk, uint32_t *retpklen, const char **retpk) { + // This next line is a hack, because the DrivenEngine is not supposed to care about 'kind'. + if (kind == InvocationKind::LUA_SOURCE) rescan_lua_source_ = false; + call_function_retpk_.clear(); + event_call_function(kind, place, std::string_view(datapk, datapklen), &call_function_retpk_); + if (retpklen != nullptr) { + std::string_view view = call_function_retpk_.view(); + *retpklen = view.size(); + *retpk = view.data(); + } +} + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// C Wrappers: Getters +// +// These wrappers make it possible to call the drv_get routines using C +// functions instead of methods. This is important if the engine is compiled +// with one C++ compiler, but the driver is compiled with a different C++ +// compiler. +// +// Some of these take parameter 'EngineWrapper', some take 'EngineWrapper', +// and some come in two versions. This all depends on whether they are used +// during play, during replay, or both. +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static void drv_get_listen_ports(EngineWrapper *w, uint32_t *nports, const uint32_t **ports) { + return w->engine->drv_get_listen_ports(nports, ports); +} + +static void drv_get_new_outgoing(EngineWrapper *w, uint32_t *nchanids, const uint32_t **chanids) { + return w->engine->drv_get_new_outgoing(nchanids, chanids); +} + +static const char *drv_get_target(EngineWrapper *w, uint32_t chid) { + return w->engine->drv_get_target(chid); +} + +static bool drv_get_channel_released(EngineWrapper *w, uint32_t chid) { + return w->engine->drv_get_channel_released(chid); +} + +static void drv_get_outgoing(EngineWrapper *w, uint32_t chid, uint32_t *len, const char **data) { + return w->engine->drv_get_outgoing(chid, len, data); +} + +static bool drv_get_outgoing_empty(EngineWrapper *w, uint32_t chid) { + return w->engine->drv_get_outgoing_empty(chid); +} + +static void drv_get_console_prompt(EngineWrapper *w, uint32_t *len, const char **data) { + return w->engine->drv_get_console_prompt(len, data); +} + +static double drv_get_clock(EngineWrapper *w) { + return w->engine->drv_get_clock(); +} + +static bool drv_get_rescan_lua_source(EngineWrapper *w) { + return w->engine->drv_get_rescan_lua_source(); +} + +static bool drv_get_stop_driver(EngineWrapper *w) { + return w->engine->drv_get_stop_driver(); +} + +static uint64_t drv_get_actor_id(EngineWrapper *w) { + return w->engine->drv_get_actor_id(); +} + +static void drv_get_tangibles_near(EngineWrapper *w, uint64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids) { + return w->engine->drv_get_tangibles_near(tanid, rx, ry, rz, count, ids); +} + +static void drv_get_animation_queues(EngineWrapper *w, uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings) { + return w->engine->drv_get_animation_queues(count, ids, lengths, strings); +} + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// C Wrappers: Mutators +// +// The wrapper for a mutator consists of two parts: the wrapper which is used at +// 'play' time, and the wrapper which is used at 'replay' time. +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + + +static void play_initialize(EngineWrapper *w, uint32_t argc, char **argv, uint32_t srcpklen, const char *srcpk, const char *logfn) { + if (w->engine != nullptr) { + return reset_wrapper(w, "Cannot initialize wrapper, it's already initialized."); + } + + // Clear the error message. + memset(w->error, 0, DRV_ERRMSG_SIZE); + + // Open the logfile, if any is specified. + if ((logfn != nullptr) && (logfn[0] != 0)) { + w->wlog = new PlayLogfile(logfn, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + if (!w->wlog->good()) { + return reset_wrapper(w, "Could not open replay log for writing: %s", logfn); + } + } else { + w->wlog = nullptr; + } + + // If we have a logfile, then log this initialization. + if (w->wlog != nullptr) { + w->wlog->write_cmd_hash(PLAY_INITIALIZE, eng::memhash()); + w->wlog->write_uint32(argc); + for (uint32_t i = 0; i < argc; i++) { + w->wlog->write_string(argv[i]); + } + w->wlog->write_string(std::string_view(srcpk, srcpklen)); + w->wlog->flush(); + } + + // Create the engine of the appropriate type. + if (argc < 1) { + std::ostringstream oss; + oss << "Must pass an engine type on the command line. Known types:\n"; + for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) { + oss << " " << reg->name << std::endl; + } + std::string err = oss.str(); + return reset_wrapper(w, err.c_str()); + } + w->engine = make_engine(argv[0]); + if (w->engine == nullptr) { + return reset_wrapper(w, "No such driven engine type: %s", argv[0]); + } + + // Call the engine initialization sequence. + w->engine->drv_initialize(srcpklen, srcpk, argc - 1, argv + 1); +} + + +static void replay_initialize(EngineWrapper *w) { + assert(w->rlog != nullptr); + std::vector argvstr; + uint32_t argc = w->rlog->read_uint32(); + for (uint32_t i = 0; i < argc; i++) { + argvstr.push_back(w->rlog->read_string()); + } + std::string srcpk = w->rlog->read_string(); + + if (!w->rlog->good()) { + return reset_wrapper(w, "replay log corrupt in replay_initialize"); + } + + // We need to convert the argument vector from an array + // of C++ strings into the canonical argc, argv format. + std::vector argvec; + for (uint32_t i = 0; i < argc; i++) { + argvec.push_back(&argvstr[i][0]); + } + char **argv = &argvec[0]; + + // Create the engine. + w->engine = make_engine(argv[0]); + if (w->engine == nullptr) { + return reset_wrapper(w, "No such driven engine type: %s", argvstr[0]); + } + + + w->engine->drv_initialize(srcpk.size(), srcpk.c_str(), argc - 1, argv + 1); +} + + +//////////////////////// + + +static void play_clear_new_outgoing(EngineWrapper *w) { + assert(w->rlog == nullptr); + if (w->wlog != nullptr) { + w->wlog->write_cmd_hash(PLAY_CLEAR_NEW_OUTGOING, eng::memhash()); + w->wlog->flush(); + } + w->engine->drv_clear_new_outgoing(); +} + +static void replay_clear_new_outgoing(EngineWrapper *w) { + w->engine->drv_clear_new_outgoing(); +} + + +//////////////////////// + + +static void play_sent_outgoing(EngineWrapper *w, uint32_t chid, uint32_t nbytes) { + assert(w->rlog == nullptr); + if (w->wlog != nullptr) { + uint32_t ndata; const char *data; + w->engine->drv_get_outgoing(chid, &ndata, &data); + assert(nbytes <= ndata); + w->wlog->write_cmd_hash(PLAY_SENT_OUTGOING, eng::memhash()); + w->wlog->write_uint32(chid); + w->wlog->write_uint32(nbytes); + w->wlog->write_uint64(SpookyHash::QkHash64(data, nbytes)); + w->wlog->flush(); + } + w->engine->drv_sent_outgoing(chid, nbytes); +} + +static void replay_sent_outgoing(EngineWrapper *w) { + uint32_t chid = w->rlog->read_uint32(); + uint32_t nbytes = w->rlog->read_uint32(); + uint64_t hash = w->rlog->read_uint64(); + + if (!w->rlog->good()) { + return reset_wrapper(w, "replay log corrupt in replay_sent_outgoing"); + } + + uint32_t ndata; const char *data; + w->engine->drv_get_outgoing(chid, &ndata, &data); + if ((nbytes > ndata) || (hash != SpookyHash::QkHash64(data, nbytes))) { + return reset_wrapper(w, "nondeterministic in replay_sent_outgoing"); + } + if (w->replay_cb_sent_outgoing != nullptr) { + w->replay_cb_sent_outgoing(w->replay_cb_vp, chid, ndata, data); + } + w->engine->drv_sent_outgoing(chid, nbytes); +} + + +//////////////////////// + + +static void play_recv_incoming(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data) { + assert(w->rlog == nullptr); + if (w->wlog != nullptr) { + w->wlog->write_cmd_hash(PLAY_RECV_INCOMING, eng::memhash()); + w->wlog->write_uint32(chid); + w->wlog->write_short_string(std::string_view(data, len)); + w->wlog->flush(); + } + w->engine->drv_recv_incoming(chid, len, data); +} + +static void replay_recv_incoming(EngineWrapper *w) { + uint32_t chid = w->rlog->read_uint32(); + std::string_view data = w->rlog->read_short_string(w); + + if (!w->rlog->good()) { + return reset_wrapper(w, "replay log corrupt in replay_recv_incoming"); + } + + w->engine->drv_recv_incoming(chid, data.size(), data.data()); +} + + +//////////////////////// + + +static void play_notify_close(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data) { + assert(w->rlog == nullptr); + if (w->wlog != nullptr) { + w->wlog->write_cmd_hash(PLAY_NOTIFY_CLOSE, eng::memhash()); + w->wlog->write_uint32(chid); + w->wlog->write_string(std::string_view(data, len)); + w->wlog->flush(); + } + + w->engine->drv_notify_close(chid, len, data); +} + +static void replay_notify_close(EngineWrapper *w) { + uint32_t chid = w->rlog->read_uint32(); + std::string message = w->rlog->read_string(); + + if (!w->rlog->good()) { + return reset_wrapper(w, "replay log corrupt in replay_notify_close"); + } + + w->engine->drv_notify_close(chid, message.size(), message.c_str()); +} + + +//////////////////////// + + +static uint32_t play_notify_accept(EngineWrapper *w, uint32_t port) { + assert(w->rlog == nullptr); + if (w->wlog != nullptr) { + w->wlog->write_cmd_hash(PLAY_NOTIFY_ACCEPT, eng::memhash()); + w->wlog->write_uint32(port); + w->wlog->flush(); + } + + return w->engine->drv_notify_accept(port); +} + +static void replay_notify_accept(EngineWrapper *w) { + uint32_t port = w->rlog->read_uint32(); + + if (!w->rlog->good()) { + return reset_wrapper(w, "replay log corrupt in replay_notify_accept"); + } + + w->engine->drv_notify_accept(port); +} + + +//////////////////////// + + +static void play_update(EngineWrapper *w, double clock) { + assert(w->rlog == nullptr); + if (w->wlog != nullptr) { + w->wlog->write_cmd_hash(PLAY_UPDATE, eng::memhash()); + w->wlog->write_double(clock); + w->wlog->flush(); + } + + w->engine->drv_update(clock); +} + +static void replay_update(EngineWrapper *w) { + double clock = w->rlog->read_double(); + if (!w->rlog->good()) { + return reset_wrapper(w, "replay log corrupt in replay_event_update"); + } + + w->engine->drv_update(clock); +} + + +//////////////////////// + + +void play_call_function(EngineWrapper *w, InvocationKind kind, int64_t place, uint32_t datapklen, const char *datapk, uint32_t *retpklen, const char **retpk) { + assert(w->rlog == nullptr); + if (w->wlog != nullptr) { + w->wlog->write_cmd_hash(PLAY_CALL_FUNCTION, eng::memhash()); + w->wlog->write_uint8(int64_t(kind)); + w->wlog->write_int64(place); + w->wlog->write_string(std::string_view(datapk, datapklen)); + w->wlog->flush(); + } + + w->engine->drv_call_function(kind, place, datapklen, datapk, retpklen, retpk); +} + +void replay_call_function(EngineWrapper *w) { + InvocationKind kind = InvocationKind(w->rlog->read_uint8()); + int64_t place = w->rlog->read_int64(); + std::string srcpack = w->rlog->read_string(); + if (!w->rlog->good()) { + return reset_wrapper(w, "replay log corrupt in replay_call_function_lua_call"); + } + + w->engine->drv_call_function(kind, place, srcpack.size(), srcpack.c_str(), nullptr, nullptr); +} + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// Replay Core +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + + +static void replaycore_initialize(EngineWrapper *w, const char *logfn) { + if (w->engine != nullptr) { + return reset_wrapper(w, "Cannot initialize wrapper, it's already initialized."); + return; + } + + // Clear the error message. + memset(w->error, 0, DRV_ERRMSG_SIZE); + + // Open the logfile. + w->rlog = new ReplayLogfile(logfn, std::ios_base::in | std::ios_base::binary); + if (!w->rlog->good()) { + return reset_wrapper(w, "Could not open replay log for reading: %s", logfn); + } + + // Read one step from the logfile, and make sure it's an initialize step. + uint8_t code = w->rlog->read_uint8(); + int hash = w->rlog->read_uint32(); + if (!w->rlog->good()) { + return reset_wrapper(w, "logfile corrupt in initial step"); + } + if (hash != eng::memhash()) { + return reset_wrapper(w, "nondeterminism detected in initial step"); + } + if (code != PLAY_INITIALIZE) { + return reset_wrapper(w, "replay log doesn't begin with initialize step"); + } + + // Replay the initialize step from the logfile. + // Doing this immediately, rather than waiting for the driver + // to call 'step', enforces the invariant that after calling + // initialize, there's an engine. + replay_initialize(w); +} + +static void replaycore_step(EngineWrapper *w) { + if (w->rlog == nullptr) { + return; + } + + uint8_t code = w->rlog->read_uint8(); + if (w->rlog->eof()) { + return reset_wrapper(w, "logfile terminated abruptly"); + } + int hash = w->rlog->read_uint32(); + if (!w->rlog->good()) { + return reset_wrapper(w, "logfile corrupt in replay step"); + } + if (hash != eng::memhash()) { + return reset_wrapper(w, "nondeterminism detected"); + } + switch (code) { + case PLAY_CLEAR_NEW_OUTGOING: replay_clear_new_outgoing(w); return; + case PLAY_SENT_OUTGOING: replay_sent_outgoing(w); return; + case PLAY_RECV_INCOMING: replay_recv_incoming(w); return; + case PLAY_NOTIFY_CLOSE: replay_notify_close(w); return; + case PLAY_NOTIFY_ACCEPT: replay_notify_accept(w); return; + case PLAY_UPDATE: replay_update(w); return; + case PLAY_CALL_FUNCTION: replay_call_function(w); return; + case PLAY_RELEASE: release(w); return; + default: return reset_wrapper(w, "Replay log corrupt in command dispatcher"); + } +} + + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// +// Wrapper Initialization +// +// To access the engine across a DLL boundary, you first use +// GetProcAddress or dlsym to fetch the addresses of 'init_play_engine' +// and 'init_replay_engine'. Then, you use those two functions to +// initialize a EngineWrapper or a EngineWrapper, which contain the addresses +// of all the other functions you need. These are the only two functions +// marked 'DLLEXPORT', all other functions are exported from the DLL +// indirectly. +// +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +#if defined(__linux__) + #define DLLEXPORT __attribute__((visibility("default"))) +#elif defined(_WIN32) + #define DLLEXPORT __declspec(dllexport) +#endif + +static void init_engine_wrapper_helper(EngineWrapper *w) { + static bool called_initializer; + assert(DrivenEngineInitializerReg::func != nullptr); + if (!called_initializer) { + DrivenEngineInitializerReg::func(); + called_initializer = true; + } + + memset(w, 0, sizeof(EngineWrapper)); + + w->get_listen_ports = drv_get_listen_ports; + w->get_new_outgoing = drv_get_new_outgoing; + w->get_target = drv_get_target; + w->get_channel_released = drv_get_channel_released; + w->get_outgoing = drv_get_outgoing; + w->get_outgoing_empty = drv_get_outgoing_empty; + w->get_console_prompt = drv_get_console_prompt; + w->get_clock = drv_get_clock; + w->get_rescan_lua_source = drv_get_rescan_lua_source; + w->get_stop_driver = drv_get_stop_driver; + w->get_actor_id = drv_get_actor_id; + w->get_tangibles_near = drv_get_tangibles_near; + w->get_animation_queues = drv_get_animation_queues; + + w->play_initialize = play_initialize; + w->play_clear_new_outgoing = play_clear_new_outgoing; + w->play_sent_outgoing = play_sent_outgoing; + w->play_recv_incoming = play_recv_incoming; + w->play_notify_close = play_notify_close; + w->play_notify_accept = play_notify_accept; + w->play_update = play_update; + w->play_call_function = play_call_function; + + w->replay_initialize = replaycore_initialize; + w->replay_step = replaycore_step; + + w->hook_dprint = util::hook_dprint; + w->release = release; +}; + +extern "C" { +DLLEXPORT void init_engine_wrapper(EngineWrapper *w) { + init_engine_wrapper_helper(w); +} +} diff --git a/luprex/cpp/core/drivenengine.hpp b/luprex/cpp/core/drivenengine.hpp new file mode 100644 index 00000000..ba94bc38 --- /dev/null +++ b/luprex/cpp/core/drivenengine.hpp @@ -0,0 +1,352 @@ +////////////////////////////////////////////////////////////// +// +// DrivenEngine +// +// This module embodies the idea of an "event-driven game engine." The +// DrivenEngine module provides two APIs: the engine-side API, and the +// driver-side API. +// +// The engine-side API looks like a typical collection of I/O primitives. It +// includes methods to open sockets, read and write sockets, read lua source, +// get the clock, and so forth. +// +// But in reality, these I/O functions don't ever call operating system +// functions like "read" or "write" or "connect." They don't call the operating +// system at all - not even indirectly, through a wrapper. Therefore, they +// can't really do any I/O. When you use one of these I/O functions to (say) +// write some data to a communication channel, the only thing that happens is +// that the data is put into a buffer. The actual transmission of the data +// happens elsewhere, in what is called the "Driver." Likewise, when you use +// one of these I/O functions to read data, it only returns data that was +// previously stored by the "Driver." +// +// The "Driver" is a module that implements the actual I/O. It is highly +// OS-dependent code, because it contains code to manipulate sockets, time +// clocks, and the like. +// +// From the perspective of the driver, the DrivenEngine is a C++ object that +// acts like a state machine. This state machine is driven forward by I/O +// events. The DrivenEngine provides an API where the driver can feed in these +// I/O events. +// +// Notice that the usual call graph is inverted: in most application programs, +// the application calls the operating system to do I/O. But when using class +// DrivenEngine, it's the other way around: the driver calls into class +// DrivenEngine to drive it forward. I/O routines drive computation. +// +// So the upshot of all this is that the DrivenEngine is a deterministic state +// machine, free of all OS-specific code. +// +////////////////////////////////////////////////////////////// + +#ifndef DRIVENENGINE_HPP +#define DRIVENENGINE_HPP + +#include "wrap-string.hpp" +#include "wrap-vector.hpp" + +#include +#include +#include + +#include "util.hpp" +#include "streambuffer.hpp" +#include "enginewrapper.hpp" +#include "planemap.hpp" +#include "invocation.hpp" + +class DrivenEngine; +class World; +using UniqueDrivenEngine = std::unique_ptr; +using DrivenEngineMaker = UniqueDrivenEngine (*)(); +using DrivenEngineInitializer = void (*)(); + +class Channel : public eng::opnew { +public: + // Get the buffers associated with this channel. + // + StreamBuffer *out() { return sb_out_.get(); } + StreamBuffer *in() { return sb_in_.get(); } + + // The channel ID. These are reused. + // + int chid() const { return chid_; } + + // If this is a socket connection, the receiver's port number. + // + int port() const { return port_; } + + // If this is an outgoing socket connection, get the target host. + // + const eng::string &target() const { return target_; } + + // True if the remote has closed the connection. + // + bool closed() const { return closed_; } + + // Get the channel's error message. + // + // If this is an empty string, there is no error. If this is set, + // then the channel is also closed. + // + const eng::string &error() const { return error_; } + + // Do not construct your own Channels. Instead, + // use methods of class DrivenEngine like new_outgoing_channel. + // Channels are referenced by shared_ptr. You can + // release your shared_ptr at any time. + // + Channel(DrivenEngine *de, int chid, int port, const eng::string &target, bool stop); + ~Channel() {}; + +private: + // Constructor is deliberately private. Use + // DrivenEngine::new_outgoing_channel to create outgoing socket channels. + // + std::string_view peek_outgoing() const; + void sent_outgoing(int nbytes); + +private: + int chid_; + + // These are the in/out buffers presented to the user. + std::shared_ptr sb_in_; + std::shared_ptr sb_out_; + + int port_; + bool closed_; + eng::string error_; + eng::string target_; + bool stop_driver_; + + friend class DrivenEngine; +}; + +using SharedChannel = std::shared_ptr; + +class DrivenEngine : public eng::opnew { +public: + ////////////////////////////////////////////////////////////// + // + // Build the named engine + // + ////////////////////////////////////////////////////////////// + + static UniqueDrivenEngine make(std::string_view name); + + static void print_usage(std::ostream &strm, std::string_view progname); + + ////////////////////////////////////////////////////////////// + // + // The following methods are the 'engine' side of the pipe. + // + ////////////////////////////////////////////////////////////// + + // The init callback. You may override this in a subclass. + // This will be called once at program initialization. + // + virtual void event_init(std::string_view srcpk, int argc, char *argv[]) = 0; + + // The call-function callback. This is invoked whenever drv_call_function + // is called. This is the main entry point for "general" access into the + // DrivenEngine. The datapk parameter can contain any arbitrary data needed + // by the call. If the call wants to return anything, it can write the + // return data into the retpk datapack. + // + virtual void event_call_function(InvocationKind kind, int64_t place, std::string_view datapk, StreamBuffer *retpk) {}; + + // The update callback. You may override this in a subclass. + // This will be called whenever anything changes. + // + virtual void event_update() = 0; + + // Specify the set of listening ports. + // This can only be used during the init routine. + // + void listen_port(int port); + + // Get the current time. + // + // DRIVER: This returns the time most recently stored by the driver + // using drv_set_clock. + // + double get_clock(); + + // Create a channel and open an outgoing connection. The channel creation + // always succeeds. You can write to the channel immediately. You can + // read, too, but of course there won't be anything in the incoming buffer + // yet. In future update events, data will show up in the incoming buffer, + // and will have been sent from the outgoing buffer. In future update + // events, the channel may get closed by the remote. If the connection + // fails (say, the remote host doesn't exist), then the Channel will get + // closed with an error. + // + // DRIVER: The channel object is created instantly, but it does nothing + // until the driver notices the new channel. The driver is responsible for + // actually opening the connection and relaying data into the channel using + // drv_get_target, drv_peek_outgoing, drv_sent_outgoing, drv_recv_incoming. + // + SharedChannel new_outgoing_channel(const eng::string &target); + + // Create a new channel from any pending incoming connection. If there is no + // incoming connection, returns nullptr. + // + // DRIVER: The driver must be hardwired to know what ports to listen on. + // When the driver notices a new incoming connection, it calls + // drv_notify_accept, which triggers the creation of the channel. The + // channel is put into the incoming channel queue, which is fetched by this + // method. The driver is responsible for relaying data into the channel + // using drv_get_target, drv_peek_outgoing, drv_sent_outgoing, + // drv_recv_incoming. + // + SharedChannel new_incoming_channel(); + + // Obtain the stdio channel. There is only one stdio channel. + // + // DRIVER: the stdio channel is created automatically when the DrivenEngine + // is created. Stdio should be connected to a console which is in + // line-at-a-time mode. The driver is responsible for relaying data from + // the console into the stdio channel using drv_peek_outgoing, + // drv_sent_outgoing, drv_recv_incoming. + // + SharedChannel get_stdio_channel(); + + // Obtain the output buffer of the stdio channel as an ostream. + // + std::ostream &stdostream() { return *stdostream_; } + + // Set the prompt for the console. + // + void set_console_prompt(const eng::string &prompt); + + // Set the flag to rescan the lua source directory. The lua source + // directory is read once, automatically, at engine creation time. + // If you want to read it again, you must trigger a rescan. + // The rescan is not instantaneous. + // + // DRIVER: this merely sets a flag, which the driver will notice later, + // causing the driver to update the lua source. + // + void rescan_lua_source(); + + // Set the world pointer and the actor ID. + // + // This allows the graphics engine to query the DrivenEngine + // about the state of the world and the player. It is legal to set these + // to zero, in which case queries will return null results. + // + void set_visible_world_and_actor(World *w, int64_t actor); + + // Stop the driver. The engine should call this when it's done + // and there's nothing left to do. + // + void stop_driver(); + + ////////////////////////////////////////////////////////////// + // + // Creation and Destruction. + // + ////////////////////////////////////////////////////////////// + + // Constructor. + // + // Most initialization is achieved by 'drv_xxx' functions, so + // this constructor takes no arguments. + // + DrivenEngine(); + + // Destructor. + // + // It is necessary to delete all channels before deleting the + // DrivenEngine. The destructor will verify that this has been done. + // + virtual ~DrivenEngine(); + + ////////////////////////////////////////////////////////////// + // + // The following accessors are for use by PlayWrapper and ReplayWrapper. + // + // The PlayWrapper and ReplayWrapper use C stubs to access + // the engine. The C stubs, in turn, call these C++ methods. + // + // The stubs for the getters are trivial, one-line stubs. + // + // The stubs for the mutators add logging. + // + ////////////////////////////////////////////////////////////// + + void drv_get_listen_ports(uint32_t *nports, const uint32_t **ports) const; + void drv_get_new_outgoing(uint32_t *nchids, const uint32_t **chids) const; + const char *drv_get_target(uint32_t chid) const; + bool drv_get_channel_released(uint32_t chid) const; + void drv_get_outgoing(uint32_t chid, uint32_t *len, const char **data) const; + bool drv_get_outgoing_empty(uint32_t chid) const; + void drv_get_console_prompt(uint32_t *len, const char **data) const; + double drv_get_clock() const; + bool drv_get_rescan_lua_source() const; + bool drv_get_stop_driver() const; + int64_t drv_get_actor_id() const; + void drv_get_tangibles_near(int64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids); + void drv_get_animation_queues(uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings); + + void drv_initialize(uint32_t srcpklen, const char *srcpk, int argc, char **argv); + void drv_clear_new_outgoing(); + void drv_sent_outgoing(uint32_t chid, uint32_t nbytes); + void drv_recv_incoming(uint32_t chid, uint32_t nbytes, const char *bytes); + void drv_notify_close(uint32_t chid, uint32_t len, const char *data); + uint32_t drv_notify_accept(uint32_t port); + void drv_update(double clock); + void drv_call_function(InvocationKind kind, int64_t place, uint32_t datapklen, const char *datapk, uint32_t *retpklen, const char **retpk); + +private: + // Find a currently-unused channel ID. Channel IDs + // are small integers that are reused. + int find_unused_chid(); + + // Get the channel associated with the specified channel ID. + Channel *get_chid(int chid) const; + +private: + SharedChannel channels_[DRV_MAX_CHAN]; + int next_unused_chid_; + SharedChannel stdio_channel_; + std::unique_ptr stdostream_; + eng::vector accepted_channels_; + eng::vector new_outgoing_; + eng::vector listen_ports_; + World *visible_world_; + int64_t visible_actor_id_; + util::IdVector scan_result_; + std::vector anim_queues_; + StreamBuffer call_function_retpk_; + bool rescan_lua_source_; + double clock_; + bool stop_driver_; + eng::string console_prompt_; + friend class Channel; +}; + +////////////////////////////////////////////////////////////////////////////////// + + +struct DrivenEngineReg { + const char *name; + DrivenEngineMaker maker; + DrivenEngineReg *next; + static DrivenEngineReg *All; + DrivenEngineReg(const char *name, DrivenEngineMaker f); +}; + +#define DrivenEngineDefine(name, cname) \ + UniqueDrivenEngine dengmake_##cname() { \ + return UniqueDrivenEngine(new cname); \ + } \ + DrivenEngineReg dengreg_##cname(name, dengmake_##cname); + +struct DrivenEngineInitializerReg { + static DrivenEngineInitializer func; + DrivenEngineInitializerReg(DrivenEngineInitializer f); +}; + + +#endif // DRIVENENGINE_HPP diff --git a/luprex/cpp/core/eng-malloc.cpp b/luprex/cpp/core/eng-malloc.cpp new file mode 100644 index 00000000..da9e74b2 --- /dev/null +++ b/luprex/cpp/core/eng-malloc.cpp @@ -0,0 +1,122 @@ + +// We only use a custom allocator on linux (for now) +// The allocator we chose is 'dlmalloc' (doug lea's malloc). +// It's a good allocator for single-threaded programs. +// It needs to be configured by setting a bunch of defines. +// +#ifdef __linux__ + +// We need this to define dlmalloc, not malloc. +#define USE_DL_PREFIX 1 + +// We don't need mspaces. +#define ONLY_MSPACES 0 +#define MSPACES 0 + +// We don't need mallinfo +#define NO_MALLINFO 1 + +// We don't need dlmalloc_inspect_all +#define MALLOC_INSPECT_ALL 0 + +// Disable locking. The entire engine is single-threaded. +#define USE_LOCKS 0 + +// This allocator can use sbrk to obtain RAM. We're going to +// emulate sbrk, in order to put it in a different memory region +// than the system malloc, which also uses sbrk. +#define HAVE_MORECORE 1 +#define MORECORE emulated_sbrk +#define MORECORE_CANNOT_TRIM 1 + +// We won't let the memory allocator use mmap. This is because +// opening the replay log may be implemented in stdio using mmap. +// So using mmap might trigger different results under replay. +#define HAVE_MMAP 0 + +// Fix a warning in dlmalloc. +#define MAX_RELEASE_CHECK_RATE INT_MAX + +// Don't generate random seeds, or time-based seeds. +#define USE_DEV_RANDOM 0 +#define LACKS_TIME_H 1 + +// Don't export any dlmalloc functions. +#define DLMALLOC_EXPORT static inline + +#include +#include +#include +#include + +static char *emulated_sbrk_base; +static intptr_t emulated_sbrk_used; +static intptr_t emulated_sbrk_limit; + +// We assume that the increments are already aligned to the system +// page size, because dlmalloc does that for us. +// +// Our emulated sbrk cannot trim the memory size. It can only grow. +// +static void *emulated_sbrk(intptr_t increment) { + if (emulated_sbrk_base == 0) { + emulated_sbrk_limit = intptr_t(64) * 1024 * 1024 * 1024; + void *map = mmap(nullptr, emulated_sbrk_limit, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(map != MAP_FAILED); + assert(map != nullptr); + emulated_sbrk_base = (char *)map; + emulated_sbrk_used = 0; + } + int64_t old_used = emulated_sbrk_used; + int64_t new_used = emulated_sbrk_used + increment; + if (new_used > emulated_sbrk_limit) { + return (void *)(-1); + } + assert(new_used >= old_used); + emulated_sbrk_used = new_used; + if (new_used > old_used) { + int status = mprotect(emulated_sbrk_base + old_used, new_used - old_used, PROT_READ | PROT_WRITE); + assert(status == 0); + } + return emulated_sbrk_base + old_used; +} + +#include "../../ext/dlmalloc.c" + +namespace eng { + static uint32_t hash; + void *malloc(size_t size) { + void *result = dlmalloc(size); + hash += uint32_t(uintptr_t(result)) + 0x83748374; + hash += hash << 10; + hash ^= hash >> 6; + hash += uint32_t(size) + 0x85893489; + hash += hash << 10; + hash ^= hash >> 6; + return result; + } + void *realloc(void *p, size_t size) { + void *result = dlrealloc(p, size); + hash += uint32_t(uintptr_t(p)) + 0x74741912; + hash += hash << 10; + hash ^= hash >> 6; + hash += uint32_t(uintptr_t(result)) + 0x68843823; + hash += hash << 10; + hash ^= hash >> 6; + hash += uint32_t(size) + 0x81444120; + hash += hash << 10; + hash ^= hash >> 6; + return result; + } + void free(void *p) { + hash += uint32_t(uintptr_t(p)) + 0x87823448; + dlfree(p); + } + int memhash() { + return (hash & 0x3FFFFFFF) | 0x40000000; + } +} // namespace eng + +#endif // ifdef __linux__ + + diff --git a/luprex/cpp/core/eng-malloc.hpp b/luprex/cpp/core/eng-malloc.hpp new file mode 100644 index 00000000..61c0be21 --- /dev/null +++ b/luprex/cpp/core/eng-malloc.hpp @@ -0,0 +1,213 @@ +// +// eng-malloc +// +// The engine has its own private eng::malloc which it uses to allocate +// everything. The engine's malloc heap only contains engine data structures. +// This helps achieve determinism when playing a replay log. +// +// About determinism: one of the key rules for maintaining deterministic +// behavior is to not ever use operations that execute in arbitrary order. +// For example, don't iterate over an unordered map, because there's no +// rule about what order the items are produced. They could be produced +// in a different order during replay than during the original recording. +// Actually, you can occasionally get away with it +// +// The engine's eng::malloc is a thin wrapper around Doug Lea's Malloc, a good +// general-purpose single-threaded malloc. It's probably not the fastest any +// more (it was, once), but it's still quite good. It's also fairly easy +// to work with. +// +// In order to get all engine data structures into the eng::malloc heap, you +// need to jump through quite a few hoops: +// +// * When writing a class that gets allocated using operator new, +// always derive from eng::opnew. This adds a custom operator new +// to your class, which causes your class to be allocated using eng::malloc. +// If you write a class that isn't ever supposed to be allocated using +// operator new, derive from eng::nevernew instead. +// +// * When using STL containers, you need to use the eng variant: +// eng::map, eng::set, eng::vector, eng::unordered_map, eng::unordered_set, +// and eng::deque. These classes derive from eng::opnew, and they also +// use eng::malloc for their internal nodes. +// +// * Use eng::string instead of std::string. Use eng::ostringstream +// instead of std::ostringstream. +// +// * Simple classes like std::pair, std::string_view, std::less, std::hash, and +// so forth are not wrapped, because it is not normal to allocate these +// classes using operator new. Do not use operator new or delete on +// these classes. +// +// * Instead of std::make_shared, use eng::make_shared. You need this +// because std::make_shared doesn't respect your custom operator new. +// +// * Failing to jump through all these hoops won't break your code in any +// obvious way - you'll just have some of your data structures in the malloc +// heap instead of the eng::malloc heap. This won't break +// determinism unless you iterate over a data structure like an unordered map +// but it creates a situation where we can't detect +// nondeterminism. +// +// * Sometimes we deliberately put certain data structures into the malloc +// heap, because we know that those particular data structures won't be +// identical between record and replay. In that situation, the fact that +// we don't detect the nondeterminism is actually a benefit. +// +// * Be aware that most C++ streams use the system malloc heap, and there's no +// way to change that. That's ok, it's fine if some small percentage of our +// data goes into the malloc heap. By the way, eng::ostringstream uses +// the eng::malloc heap. +// +#ifndef ENG_MALLOC_HPP +#define ENG_MALLOC_HPP + +#include +#include +#include + +namespace eng { +#ifdef __linux__ +void* malloc(size_t x); +void free(void *p); +void* realloc(void*, size_t); +int memhash(); +#else +inline void *malloc(size_t x) { return ::malloc(x); } +inline void free(void *p) { return ::free(p); } +inline void *realloc(void *p, size_t x) { return ::realloc(p, x); } +inline int memhash() { return 0; } +#endif +} // namespace eng + +// An allocator for lua states that uses eng::malloc and eng::free +namespace eng { + inline void *l_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { + if (nsize == 0) { + ::eng::free(ptr); + return NULL; + } else { + return ::eng::realloc(ptr, nsize); + } + } +} // namespace eng + +// eng_allocator is similar to std::allocator, but allocates +// objects using eng::malloc and eng::free. +template +class eng_allocator +{ +public: + using value_type = T; + eng_allocator() noexcept {} + template eng_allocator(eng_allocator const&) noexcept {} + + value_type* allocate(std::size_t n) + { + return static_cast(eng::malloc(n*sizeof(value_type))); + } + + void deallocate(value_type* p, std::size_t) noexcept + { + eng::free(p); + } +}; + +// Another name for eng_allocator is eng::allocator. +namespace eng { + template + using allocator = ::eng_allocator; +} // namespace eng + +// Mandated equality and inequality operators for eng_allocator. +template +bool operator==(const eng_allocator &, const eng_allocator &) noexcept +{ + return true; +} +template +bool operator!=(const eng_allocator &, const eng_allocator &) noexcept +{ + return false; +} + +// eng::opnew. A class containing operator new and operator delete, +// meant to be used as a base class for inheritance. +namespace eng { + class opnew { + public: + void *operator new(size_t size) + { + return ::eng::malloc(size); + } + + void operator delete(void *p, size_t size) + { + return ::eng::free(p); + } + + void *operator new[](size_t size) + { + return ::eng::malloc(size); + } + + void operator delete[](void *p, size_t size) + { + return ::eng::free(p); + } + }; +} // namespace eng + +// eng::nevernew. A class containing private operator new and +// operator delete, making it impossible to 'new' the class. +// This means the class must be embedded as a field in some other +// class, and it gets allocated when its enclosing object gets +// allocated. +namespace eng { + class nevernew { + private: + void *operator new(size_t size) + { + assert(false && "not supposed to 'new' this class"); + return NULL; + } + + void operator delete(void *p, size_t size) + { + assert(false && "not supposed to 'delete' this class"); + } + + void *operator new[](size_t size) + { + assert(false && "not supposed to 'new' this class"); + return NULL; + } + + void operator delete[](void *p, size_t size) + { + assert(false && "not supposed to 'delete' this class"); + } + }; +} // namespace eng + + +// eng::make_shared allocates shared objects using eng::malloc. +namespace eng { + template + inline ::std::shared_ptr make_shared(Args&&... args) { + return std::allocate_shared(eng::allocator(), args...); + } +} // namespace eng + +// eng::make_unique doesn't do anything different than std::make_unique: they +// both use operator new and delete. You must derive from eng::opnew to change +// operator new and delete. +namespace eng { + template + inline ::std::unique_ptr make_unique(Args&&... args) { + return std::make_unique(args...); + } +} // namespace eng + +#endif // ENG_MALLOC_HPP + diff --git a/luprex/cpp/core/eng-tests.cpp b/luprex/cpp/core/eng-tests.cpp new file mode 100644 index 00000000..e99b3c6f --- /dev/null +++ b/luprex/cpp/core/eng-tests.cpp @@ -0,0 +1,133 @@ +#include "wrap-string.hpp" + +#include "drivenengine.hpp" +#include "streambuffer.hpp" +#include "world.hpp" + +#include + +static void write_closed_message(Channel *ch, StreamBuffer *out) { + eng::ostringstream oss; + oss << "Chan " << ch->chid() << " closed [" << ch->error() << "]\n"; + out->write_bytes(oss.str()); +} + +static void dump_lines(StreamBuffer *in, StreamBuffer *out, int chid) { + while (true) { + eng::string l = in->readline(); + if (l == "") break; + eng::ostringstream oss; + oss << "Chan " << chid << ": " << l; + out->write_bytes(oss.str()); + } +} + +// This test is the minimal possible DrivenEngine. +class DriverStubTest : public DrivenEngine { + virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { + stop_driver(); + } + virtual void event_update() override { + } +}; + +// This test connects to a public webserver and prints +// the output from the server. +class DriverWebServerTest : public DrivenEngine { +public: + eng::vector channels_; + virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { + SharedChannel ch = new_outgoing_channel("cert:stanford.edu:443"); + ch->out()->write_bytes("GET https://stanford.edu/xbanankjdsh.html HTTP/1.1\n\n"); + channels_.emplace_back(std::move(ch)); + } + + virtual void event_update() override { + SharedChannel stdioch = get_stdio_channel(); + dump_lines(stdioch->in(), stdioch->out(), 0); + eng::vector keep; + for (SharedChannel &ch : channels_) { + dump_lines(ch->in(), stdioch->out(), ch->chid()); + if (ch->closed()) { + write_closed_message(ch.get(), stdioch->out()); + } else { + keep.emplace_back(std::move(ch)); + } + } + channels_ = std::move(keep); + } +}; + +// This test produces a DNS resolution failure. +class DriverDNSFailTest : public DrivenEngine { +public: + eng::vector channels_; + virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { + SharedChannel ch = new_outgoing_channel("akjsdkajshdakjshd.alk:80"); + ch->out()->write_bytes("GET http://stanford.edu/index.html HTTP/1.1\n\n"); + channels_.emplace_back(std::move(ch)); + } + + virtual void event_update() override { + SharedChannel stdioch = get_stdio_channel(); + dump_lines(stdioch->in(), stdioch->out(), 0); + eng::vector keep; + for (SharedChannel &ch : channels_) { + dump_lines(ch->in(), stdioch->out(), ch->chid()); + if (ch->closed()) { + write_closed_message(ch.get(), stdioch->out()); + } else { + keep.emplace_back(std::move(ch)); + } + } + channels_ = std::move(keep); + } +}; + +// This test just prints the time. +class DriverPrintClockTest : public DrivenEngine { +public: + int count_; + double last_clock_; + virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { + count_ = 0; + last_clock_ = 0.0; + } + + virtual void event_update() override { + double clock = get_clock(); + if (clock > last_clock_ + 0.5) { + int ms = eng::memhash(); + stdostream() << std::fixed << std::setprecision(2) << clock << " " << std::hex << ms << " "; + count_++; + last_clock_ = clock; + } + if (count_ == 4) { + stdostream() << std::endl; + count_ = 0; + } + } +}; + + +class RunUnitTests : public DrivenEngine { +private: + UniqueWorld world_; + + void event_init(std::string_view srcpk, int argc, char *argv[]) override + { + world_.reset(new World(WORLD_TYPE_MASTER)); + world_->update_source(srcpk); + world_->run_unittests(); + stop_driver(); + } + + void event_update() override {} +}; + +DrivenEngineDefine("driverstubtest", DriverStubTest); +DrivenEngineDefine("driverwebservertest", DriverWebServerTest); +DrivenEngineDefine("driverdnsfailtest", DriverDNSFailTest); +DrivenEngineDefine("driverprintclocktest", DriverPrintClockTest); +DrivenEngineDefine("rununittests", RunUnitTests); + diff --git a/luprex/cpp/core/eng-tests.hpp b/luprex/cpp/core/eng-tests.hpp new file mode 100644 index 00000000..4d1945e5 --- /dev/null +++ b/luprex/cpp/core/eng-tests.hpp @@ -0,0 +1,5 @@ +#ifndef DRIVERTESTS_HPP +#define DRIVERTESTS_HPP + +#endif // DRIVERTESTS_HPP + diff --git a/luprex/cpp/core/enginewrapper.hpp b/luprex/cpp/core/enginewrapper.hpp new file mode 100644 index 00000000..477b9233 --- /dev/null +++ b/luprex/cpp/core/enginewrapper.hpp @@ -0,0 +1,303 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// enginewrapper.hpp +// +// This header file contains driver's interface to class DrivenEngine. +// This is meant to be used across a DLL boundary. Since the DLL may have +// been compiled by a different compiler than the driver, we use only simple +// POD types and we only use C calling conventions. +// +// When calling a wrapper function, you must always pass in the wrapper as +// the first parameter. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef ENGINEWRAPPER_H +#define ENGINEWRAPPER_H + +#define DRV_MAX_CHAN 256 +#define DRV_MAX_LISTEN_PORTS 256 +#define DRV_ERRMSG_SIZE 8192 +#define DRV_SHORTSTRING_SIZE 65536 + +enum class InvocationKind { + INVALID, + LUA_INVOKE, + LUA_PROBE, + LUA_EXPR, + LUA_SOURCE, + FLUSH_PRINTS, + TICK, +}; + +class DrivenEngine; +class PlayLogfile; +class ReplayLogfile; + +struct EngineWrapper { + char error[DRV_ERRMSG_SIZE]; + char databuffer[DRV_SHORTSTRING_SIZE]; + DrivenEngine *engine; + PlayLogfile *wlog; + ReplayLogfile *rlog; + + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + // + // CONSTRUCTION + // + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + // Of course, there's no constructor, since this is a C struct. + // To initialize it, you use 'dlsym' or 'GetProcAddress' to get the + // address of the function 'init_engine_wrapper'. Then, you call + // the function init_engine_wrapper(&wrapper). + + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + // + // GETTERS + // + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + // Get a list of all the listening ports. The driver is expected + // to fetch this set shortly after the event_init callback is invoked. + // + void (*get_listen_ports)(EngineWrapper *w, uint32_t *nports, const uint32_t **ports); + + // Get a list of all recently-opened channels that were created using + // new_outgoing_channel. The driver should initiate outgoing + // connections for these channels. + // + void (*get_new_outgoing)(EngineWrapper *w, uint32_t *nchanids, const uint32_t **chanids); + + // Get a string_view of the target of a channel. A target is a string like + // "cert:whatever.com:80" or "nocert:whatever.com:80". + // The first word indicate whether or not a valid SSL certificate + // is required. The second word is the hostname. The third word is + // the port number. The char string returned here is valid until + // the channel is closed. + // + const char *(*get_target)(EngineWrapper *w, uint32_t chid); + + // Return true if the user has released all references to this channel. + // In this case, the driver should initiate shutdown of the channel, + // and the driver should eventually call notify_close. + // + bool (*get_channel_released)(EngineWrapper *w, uint32_t chid); + + // Get a pointer to the bytes in the outgoing buffer. The char pointer + // returned here is naturally only valid until the buffer is changed. + // This function is used for all channels, including sockets and stdio. + // + void (*get_outgoing)(EngineWrapper *w, uint32_t chid, uint32_t *len, const char **data); + + // Return true if the outgoing buffer is empty. + // + bool (*get_outgoing_empty)(EngineWrapper *w, uint32_t chid); + + // Get the console prompt. + // + void (*get_console_prompt)(EngineWrapper *w, uint32_t *len, const char **data); + + // Get the clock. + // + // Get the current time. This is equal to the last value passed + // in by invoke_event_update. + // + double (*get_clock)(EngineWrapper *w); + + // Check the 'rescan_lua_source' flag. If this flag is set, it means + // that the engine wants the driver to rescan the lua source code. + // When the driver sees this flag, it should rescan the source and call + // set_lua_source_pack. + // + bool (*get_rescan_lua_source)(EngineWrapper *w); + + // If true, the engine is done. Stop the driver. + // + bool (*get_stop_driver)(EngineWrapper *w); + + // Get the actor ID. May return zero if the server is down. + // + uint64_t (*get_actor_id)(EngineWrapper *w); + + // Do a scan to find tangibles near the specified player. + // + // Returns a count and a pointer to an array of tangible IDs. The returned + // pointer is valid until the next call to get_tangibles_near. + // + void (*get_tangibles_near)(EngineWrapper *w, uint64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids); + + // Get the animation queues for the specified tangibles. + // + // You must supply an array of tangible IDs. For each tangible, returns the + // animation queue as a serialized string. The serialized format is + // documented in header file animqueue.hpp. + // + // You must also supply a buffer for the string lengths, and a buffer for + // the string pointers. Both buffers must have space for number of + // tangibles specified in the call. + // + // The returned character pointers remain valid until the next call to + // get_animation_queues. + // + void (*get_animation_queues)(EngineWrapper *w, uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings); + + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + // + // MUTATORS USED ONLY IN PLAY MODE + // + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + // Create the driven engine. argc and argv allow you to specify what + // kind of engine you want. You must pass in the initial state of the lua + // source, if you have any. You may optionally also specify a replay log. + // If you don't want to create a replay log, pass a null pointer. + // + // Check to see if the error buffer contains a message after calling + // this function. + // + void (*play_initialize)(EngineWrapper *w, uint32_t argc, char **argv, uint32_t srcpklen, const char *srcpk, const char *logfn); + + // Clear the list of recently-opened channels. You are meant to fetch + // new outgoing channels using get_new_outgoing, then you call + // clear_new_outgoing after you've opened those channels. + // + void (*play_clear_new_outgoing)(EngineWrapper *w); + + // Notifies the channel that some bytes were transmitted. This causes those + // bytes to be removed from the outgoing buffer. This function is used for + // all channels, including sockets and stdio. + // + void (*play_sent_outgoing)(EngineWrapper *w, uint32_t chid, uint32_t nbytes); + + // Notifies the channel that some bytes were received. This causes those + // bytes to be appended to the incoming buffer. This function is used for + // all channels, including sockets and stdio. + // + void (*play_recv_incoming)(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data); + + // Notify the channel that the connection was closed. This includes all + // sorts of closes, including friendly termination, all the way to network + // failure. Closing the channel doesn't delete it. The engine is + // responsible for noticing that the channel closed and the engine must + // delete it. Closing a channel prevents it from showing up in + // 'list_channels'. + // + void (*play_notify_close)(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data); + + // Notify the DrivenEngine that somebody connected to an incoming port. + // This will cause the DrivenEngine to allocate a new channel and put the + // new channel into the incoming channels queue. Returns the new channel + // ID. The new incoming channel appears in the 'list_channels' list, + // even before the engine pops the channel from the incoming channels queue. + // + uint32_t (*play_notify_accept)(EngineWrapper *w, uint32_t port); + + // Invoke the update event. + // + // The clock value must absolutely be monotonically increasing, + // and it should roughly be equal to the number of seconds since + // the program started. + // + void (*play_update)(EngineWrapper *w, double clock); + + // Send an invoke. + // + // This is the main pathway for blueprints, or the unreal C++ code, + // to change the state of the world. + // + void (*play_call_function)(EngineWrapper *w, InvocationKind kind, int64_t place, uint32_t datapklen, const char *datapk, uint32_t *retpklen, const char **retpk); + + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + // + // MUTATORS USED ONLY IN REPLAY MODE + // + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + // Begin a replay. + // + // Opens the logfile and prepares to replay the log. + // If an error occurs, the error buffer contains a message, + // and the done flag is set to true. + // + void (*replay_initialize)(EngineWrapper *w, const char *logfn); + + // Execute a single step from the replay log. + // + // Calling this when 'done' is true is a no-op. + // + void (*replay_step)(EngineWrapper *w); + + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + // + // CALLBACKS USED ONLY IN REPLAY MODE + // + // The driver can store function pointers here. If it does so, these + // functions will get called during replay_step operations. + // + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + void (*replay_cb_sent_outgoing)(void *vp, int chid, int nbytes, const char *data); + + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + // + // VOID POINTER USED IN REPLAY CALLBACKS + // + // The driver can store a void pointer here. This void pointer will get passed + // to all the replay callbacks. + // + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + void *replay_cb_vp; + + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + // + // FUNCTIONS THAT CAN BE USED AT ANY TIME + // + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + // Hook dprintf + // + // The engine provides a function 'util::dprintf' to print debugging + // messages. It does not use stderr or std::cerr, because in windows, those + // go to the bit-bucket. + // + // This routine hooks dprintf to change where the output goes. Ideally, + // the intent is to send the output somewhere easily accessible, like the + // visual studio debug output log, or the unreal editor output log, or + // something like that. Or, better yet, to all those places at once. + // + // The hook function will get passed one line of output at a time. The line + // will only contain printable characters. The line will not contain a + // newline - the newline is implied. + // + void (*hook_dprint)(void (*func)(const char *oneline, size_t size)); + + // Release: delete the engine object and close log files. + // + // Note that the wrapper must have already been initialized using + // init_engine_wrapper. Otherwise, the 'release' function pointer would not + // be initialized. If writing a logfile, this stores a 'clean exit' marker + // in the logfile, indicating that the engine exited cleanly, as opposed to + // crashing. + // + // If the wrapper is already in its clear state, this is a no-op. + // + void (*release)(EngineWrapper *w); +}; + +#endif // ENGINEWRAPPER_HPP \ No newline at end of file diff --git a/luprex/cpp/core/fast-float.hpp b/luprex/cpp/core/fast-float.hpp new file mode 100644 index 00000000..2482dfdb --- /dev/null +++ b/luprex/cpp/core/fast-float.hpp @@ -0,0 +1,2979 @@ +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes +// +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini +// +// Licensed under the Apache License, Version 2.0, or the +// MIT License at your option. This file may not be copied, +// modified, or distributed except according to those terms. +// +// MIT License Notice +// +// MIT License +// +// Copyright (c) 2021 The fast_float authors +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Apache License (Version 2.0) Notice +// +// Copyright 2021 The fast_float authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + +#include + +namespace fast_float { +enum chars_format { + scientific = 1<<0, + fixed = 1<<2, + hex = 1<<3, + general = fixed | scientific +}; + + +struct from_chars_result { + const char *ptr; + std::errc ec; +}; + +struct parse_options { + constexpr explicit parse_options(chars_format fmt = chars_format::general, + char dot = '.') + : format(fmt), decimal_point(dot) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + char decimal_point; +}; + +/** + * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting + * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. + * The resulting floating-point value is the closest floating-point values (using either float or double), + * using the "round to even" convention for values that would otherwise fall right in-between two values. + * That is, we provide exact parsing according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the + * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned + * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of + * the type `fast_float::chars_format`. It is a bitset value: we check whether + * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set + * to determine whether we allowe the fixed point and scientific notation respectively. + * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. + */ +template +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + */ +template +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept; + +} +#endif // FASTFLOAT_FAST_FLOAT_H + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +#include +#include +#include +#include + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ + || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ + || defined(__MINGW64__) \ + || defined(__s390x__) \ + || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ + || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_64BIT +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ + || defined(__arm__) || defined(_M_ARM) \ + || defined(__MINGW32__)) +#define FASTFLOAT_32BIT +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. + // We can never tell the register width, but the SIZE_MAX is a good approximation. + // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability. + #if SIZE_MAX == 0xffff + #error Unknown platform (16-bit, unsupported) + #elif SIZE_MAX == 0xffffffff + #define FASTFLOAT_32BIT + #elif SIZE_MAX == 0xffffffffffffffff + #define FASTFLOAT_64BIT + #else + #error Unknown platform (not 32-bit, not 64-bit?) + #endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#ifdef _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#else +#include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +#ifndef FASTFLOAT_ASSERT +#define FASTFLOAT_ASSERT(x) { if (!(x)) abort(); } +#endif + +#ifndef FASTFLOAT_DEBUG_ASSERT +#include +#define FASTFLOAT_DEBUG_ASSERT(x) assert(x) +#endif + +// rust style `try!()` macro, or `?` operator +#define FASTFLOAT_TRY(x) { if (!(x)) return false; } + +namespace fast_float { + +// Compares two ASCII strings in a case insensitive manner. +inline bool fastfloat_strncasecmp(const char *input1, const char *input2, + size_t length) { + char running_diff{0}; + for (size_t i = 0; i < length; i++) { + running_diff |= (input1[i] ^ input2[i]); + } + return (running_diff == 0) || (running_diff == 32); +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +// a pointer and a length to a contiguous block of memory +template +struct span { + const T* ptr; + size_t length; + span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {} + span() : ptr(nullptr), length(0) {} + + constexpr size_t len() const noexcept { + return length; + } + + const T& operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return ptr[index]; + } +}; + +struct value128 { + uint64_t low; + uint64_t high; + value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + value128() : low(0), high(0) {} +}; + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline int leading_zeroes(uint64_t input_num) { + assert(input_num > 0); +#ifdef FASTFLOAT_VISUAL_STUDIO + #if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); + #else + int last_bit = 0; + if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, last_bit |= 32; + if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, last_bit |= 16; + if(input_num & uint64_t( 0xff00)) input_num >>= 8, last_bit |= 8; + if(input_num & uint64_t( 0xf0)) input_num >>= 4, last_bit |= 4; + if(input_num & uint64_t( 0xc)) input_num >>= 2, last_bit |= 2; + if(input_num & uint64_t( 0x2)) input_num >>= 1, last_bit |= 1; + return 63 - last_bit; + #endif +#else + return __builtin_clzll(input_num); +#endif +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd, + uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = !!(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + !!(lo < bd); + return lo; +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + + +// compute 64-bit a*b +fastfloat_really_inline value128 full_multiplication(uint64_t a, + uint64_t b) { + value128 answer; +#ifdef _M_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emulate + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + #error Not implemented +#endif + return answer; +} + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int32_t power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + bool operator==(const adjusted_mantissa &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + bool operator!=(const adjusted_mantissa &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static int32_t invalid_am_bias = -0x8000; + +constexpr static double powers_of_ten_double[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; +constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, + 1e6, 1e7, 1e8, 1e9, 1e10}; + +template struct binary_format { + using equiv_uint = typename std::conditional::type; + + static inline constexpr int mantissa_explicit_bits(); + static inline constexpr int minimum_exponent(); + static inline constexpr int infinite_power(); + static inline constexpr int sign_index(); + static inline constexpr int min_exponent_fast_path(); + static inline constexpr int max_exponent_fast_path(); + static inline constexpr int max_exponent_round_to_even(); + static inline constexpr int min_exponent_round_to_even(); + static inline constexpr uint64_t max_mantissa_fast_path(); + static inline constexpr int largest_power_of_ten(); + static inline constexpr int smallest_power_of_ten(); + static inline constexpr T exact_power_of_ten(int64_t power); + static inline constexpr size_t max_digits(); + static inline constexpr equiv_uint exponent_mask(); + static inline constexpr equiv_uint mantissa_mask(); + static inline constexpr equiv_uint hidden_bit_mask(); +}; + +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { return 63; } +template <> inline constexpr int binary_format::sign_index() { return 31; } + +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr double binary_format::exact_power_of_ten(int64_t power) { + return powers_of_ten_double[power]; +} +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + + return powers_of_ten_float[power]; +} + + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -65; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 769; +} +template <> inline constexpr size_t binary_format::max_digits() { + return 114; +} + +template <> inline constexpr binary_format::equiv_uint + binary_format::exponent_mask() { + return 0x7F800000; +} +template <> inline constexpr binary_format::equiv_uint + binary_format::exponent_mask() { + return 0x7FF0000000000000; +} + +template <> inline constexpr binary_format::equiv_uint + binary_format::mantissa_mask() { + return 0x007FFFFF; +} +template <> inline constexpr binary_format::equiv_uint + binary_format::mantissa_mask() { + return 0x000FFFFFFFFFFFFF; +} + +template <> inline constexpr binary_format::equiv_uint + binary_format::hidden_bit_mask() { + return 0x00800000; +} +template <> inline constexpr binary_format::equiv_uint + binary_format::hidden_bit_mask() { + return 0x0010000000000000; +} + +template +fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { + uint64_t word = am.mantissa; + word |= uint64_t(am.power2) << binary_format::mantissa_explicit_bits(); + word = negative + ? word | (uint64_t(1) << binary_format::sign_index()) : word; +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + if (std::is_same::value) { + ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian + } else { + ::memcpy(&value, &word, sizeof(T)); + } +#else + // For little-endian systems: + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +typedef span byte_span; + +struct parsed_number_string { + int64_t exponent{0}; + uint64_t mantissa{0}; + const char *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + byte_span integer{}; // non-nullable + byte_span fraction{}; // nullable +}; + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = byte_span(start_digits, size_t(digit_count)); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char* before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = byte_span(before, size_t(p - before)); + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + const char* int_end = p + answer.integer.len(); + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + const char* frac_end = p + answer.fraction.len(); + while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H + +#include + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^−1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^−1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template +struct powers_template { + +constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); +constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); +constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); +// Powers of five from 5^-342 all the way to 5^308 rounded toward one. +static const uint64_t power_of_five_128[number_of_entries]; +}; + +template +const uint64_t powers_template::power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a,0x113faa2906a13b3f, + 0x9558b4661b6565f8,0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, + 0xe95a99df8ace6f53,0xf4d82c2c107973dc, + 0x91d8a02bb6c10594,0x79071b9b8a4be869, + 0xb64ec836a47146f9,0x9748e2826cdee284, + 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f,0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723,0xad2c788035e61382, + 0x8b16fb203055ac76,0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78,0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b,0x8672648c40e5ad68, + 0xa9c98d8ccb009506,0x680efdaf511f18c2, + 0xd43bf0effdc0ba48,0x212bd1b2566def2, + 0x84a57695fe98746d,0x14bb630f7604b57, + 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, + 0xcf42894a5dce35ea,0x52064cac828675b9, + 0x818995ce7aa0e1b2,0x7343efebd1940993, + 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6,0xd41a26e077774ef6, + 0xfd00b897478238d0,0x8920b098955522b4, + 0x9e20735e8cb16382,0x55b46e5f5d5535b0, + 0xc5a890362fddbc62,0xeb2189f734aa831d, + 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d,0x47b233c92125366e, + 0xc1069cd4eabe89f8,0x999ec0bb696e840a, + 0xf148440a256e2c76,0xc00670ea43ca250d, + 0x96cd2a865764dbca,0x380406926a5e5728, + 0xbc807527ed3e12bc,0xc605083704f5ecf2, + 0xeba09271e88d976b,0xf7864a44c633682e, + 0x93445b8731587ea3,0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c,0x5960ea05bad82964, + 0xe61acf033d1a45df,0x6fb92487298e33bd, + 0x8fd0c16206306bab,0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696,0x8f48a4899877186c, + 0xe0b62e2929aba83c,0x331acdabfe94de87, + 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, + 0x892731ac9faf056e,0xbe311c083a225cd2, + 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, + 0xd64d3d9db981787d,0x92cbbccdad5b108, + 0x85f0468293f0eb4e,0x25bbf56008c58ea5, + 0xa76c582338ed2621,0xaf2af2b80af6f24e, + 0xd1476e2c07286faa,0x1af5af660db4aee1, + 0x82cca4db847945ca,0x50d98d9fc890ed4d, + 0xa37fce126597973c,0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1,0x77b191618c54e9ac, + 0xc795830d75038c1d,0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, + 0x9becce62836ac577,0x4ee367f9430aec32, + 0xc2e801fb244576d5,0x229c41f793cda73f, + 0xf3a20279ed56d48a,0x6b43527578c1110f, + 0x9845418c345644d6,0x830a13896b78aaa9, + 0xbe5691ef416bd60c,0x23cc986bc656d553, + 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, + 0x91376c36d99995be,0x23100809b9c21fa1, + 0xb58547448ffffb2d,0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9,0x16c90c8f323f516c, + 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, + 0xb1442798f49ffb4a,0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d,0x40405643d711d583, + 0x8a7d3eef7f1cfc52,0x482835ea666b2572, + 0xad1c8eab5ee43b66,0xda3243650005eecf, + 0xd863b256369d4a40,0x90bed43e40076a82, + 0x873e4f75e2224e68,0x5a7744a6e804a291, + 0xa90de3535aaae202,0x711515d0a205cb36, + 0xd3515c2831559a83,0xd5a5b44ca873e03, + 0x8412d9991ed58091,0xe858790afe9486c2, + 0xa5178fff668ae0b6,0x626e974dbe39a872, + 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, + 0xa139029f6a239f72,0x1c1fffc1ebc44e80, + 0xc987434744ac874e,0xa327ffb266b56220, + 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, + 0xc4ce17b399107c22,0xcb550fb4384d21d3, + 0xf6019da07f549b2b,0x7e2a53a146606a48, + 0x99c102844f94e0fb,0x2eda7444cbfc426d, + 0xc0314325637a1939,0xfa911155fefb5308, + 0xf03d93eebc589f88,0x793555ab7eba27ca, + 0x96267c7535b763b5,0x4bc1558b2f3458de, + 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb,0x465e15a979c1cadc, + 0x92a1958a7675175f,0xbfacd89ec191ec9, + 0xb749faed14125d36,0xcef980ec671f667b, + 0xe51c79a85916f484,0x82b7e12780e7401a, + 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9,0x67a791e093e1d49a, + 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d,0x58fae9f773886e18, + 0xda7f5bf590966848,0xaf39a475506a899e, + 0x888f99797a5e012d,0x6d8406c952429603, + 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26,0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, + 0xd0601d8efc57b08b,0xf13b94daf124da26, + 0x823c12795db6ce57,0x76c53d08d6b70858, + 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02,0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a,0x359ab6419ca1091b, + 0xf867241c8cc6d4c0,0xc30163d203c94b62, + 0x9b407691d7fc44f8,0x79e0de63425dcf1d, + 0xc21094364dfb5636,0x985915fc12f542e4, + 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, + 0xbd8430bd08277231,0x50c6ff782a838353, + 0xece53cec4a314ebd,0xa4f8bf5635246428, + 0x940f4613ae5ed136,0x871b7795e136be99, + 0xb913179899f68584,0x28e2557b59846e3f, + 0xe757dd7ec07426e5,0x331aeada2fe589cf, + 0x9096ea6f3848984f,0x3ff0d2c85def7621, + 0xb4bca50b065abe63,0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, + 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, + 0xb080392cc4349dec,0xbd8d794d96aacfb3, + 0xdca04777f541c567,0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60,0xf41686c49db57244, + 0xac5d37d5b79b6239,0x311c2875c522ced5, + 0xd77485cb25823ac7,0x7d633293366b828b, + 0x86a8d39ef77164bc,0xae5dff9c02033197, + 0xa8530886b54dbdeb,0xd9f57f830283fdfc, + 0xd267caa862a12d66,0xd072df63c324fd7b, + 0x8380dea93da4bc60,0x4247cb9e59f71e6d, + 0xa46116538d0deb78,0x52d9be85f074e608, + 0xcd795be870516656,0x67902e276c921f8b, + 0x806bd9714632dff6,0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c,0x796b805720085f81, + 0x9cc3a6eec6311a63,0xcbe3303674053bb0, + 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b,0xee92fb5515482d44, + 0x991711052d8bf3c5,0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6,0xd262d45a78a0635d, + 0xef340a98172aace4,0x86fb897116c87c34, + 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, + 0xbae0a846d2195712,0x8974836059cca109, + 0xe998d258869facd7,0x2bd1a438703fc94b, + 0x91ff83775423cc06,0x7b6306a34627ddcf, + 0xb67f6455292cbf08,0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, + 0x8e938662882af53e,0x547eb47b7282ee9c, + 0xb23867fb2a35b28d,0xe99e619a4f23aa43, + 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, + 0xae0b158b4738705e,0x9624ab50b148d445, + 0xd98ddaee19068c76,0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b,0x7647c3200069671f, + 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, + 0xa5fb0a17c777cf09,0xf468107100525890, + 0xcf79cc9db955c2cc,0x7182148d4066eeb4, + 0x81ac1fe293d599bf,0xc6f14cd848405530, + 0xa21727db38cb002f,0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, + 0xfd442e4688bd304a,0x908f4a166d1da663, + 0x9e4a9cec15763e2e,0x9a598e4e043287fe, + 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, + 0xf7549530e188c128,0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, + 0xc13a148e3032d6e7,0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, + 0xebdf661791d60f56,0x111b495b3464ad21, + 0x936b9fcebb25c995,0xcab10dd900beec34, + 0xb84687c269ef3bfb,0x3d5d514f40eea742, + 0xe65829b3046b0afa,0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, + 0xb3f4e093db73a093,0x59ed216765690f56, + 0xe0f218b8d25088b8,0x306869c13ec3532c, + 0x8c974f7383725573,0x1e414218c73a13fb, + 0xafbd2350644eeacf,0xe5d1929ef90898fa, + 0xdbac6c247d62a583,0xdf45f746b74abf39, + 0x894bc396ce5da772,0x6b8bba8c328eb783, + 0xab9eb47c81f5114f,0x66ea92f3f326564, + 0xd686619ba27255a2,0xc80a537b0efefebd, + 0x8613fd0145877585,0xbd06742ce95f5f36, + 0xa798fc4196e952e7,0x2c48113823b73704, + 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, + 0x82ef85133de648c4,0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3,0x318df905079926a8, + 0xffbbcfe994e5c61f,0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d,0x6bea10ca65c084e, + 0xc31bfa0fe5698db8,0x486e494fcff30a62, + 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7,0xf89629465a75e01c, + 0xbe89523386091465,0xf6bbb397f1135823, + 0xee2ba6c0678b597f,0x746aa07ded582e2c, + 0x94db483840b717ef,0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb,0x92f34d62616ce413, + 0xe896a0d7e51e1566,0x77b020baf9c81d17, + 0x915e2486ef32cd60,0xace1474dc1d122e, + 0xb5b5ada8aaff80b8,0xd819992132456ba, + 0xe3231912d5bf60e6,0x10e1fff697ed6c69, + 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d,0x86c16c98d2c953c6, + 0xd89d64d57a607744,0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b,0x11471cd764ad4972, + 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, + 0xd389b47879823479,0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb,0xcedf722a585139ba, + 0xa54394fe1eedb8fe,0xc2974eb4ee658828, + 0xce947a3da6a9273e,0x733d226229feea32, + 0x811ccc668829b887,0x806357d5a3f525f, + 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052,0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67,0xbbac2078d443ace2, + 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, + 0xc5029163f384a931,0xa9e795e65d4df11, + 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e,0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, + 0xf07da27a82c37088,0x5d767327bb4e5a4c, + 0x964e858c91ba2655,0x3a6a07f8d510f86f, + 0xbbe226efb628afea,0x890489f70a55368b, + 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb,0x9ce6ebb40173744, + 0xe55990879ddcaabd,0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6,0x9fa946824a12232d, + 0xb32df8e9f3546564,0x47939822dc96abf9, + 0xdff9772470297ebd,0x59787e2b93bc56f7, + 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, + 0xaefae51477a06b03,0xede622920b6b23f1, + 0xdab99e59958885c4,0xe95fab368e45eced, + 0x88b402f7fd75539b,0x11dbcb0218ebb414, + 0xaae103b5fcd2a881,0xd652bdc29f26a119, + 0xd59944a37c0752a2,0x4be76d3346f0495f, + 0x857fcae62d8493a5,0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2,0x7e2000a41346a7a7, + 0x825ecc24c873782f,0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b,0x728900802f0f32fa, + 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc,0xe2f610c84987bfa8, + 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143,0x91503d1c79720dbb, + 0xf8a95fcf88747d94,0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, + 0xc24452da229b021b,0xfbe85badce996168, + 0xf2d56790ab41c2a2,0xfae27299423fb9c3, + 0x97c560ba6b0919a5,0xdccd879fc967d41a, + 0xbdb6b8e905cb600f,0x5400e987bbc1c920, + 0xed246723473e3813,0x290123e9aab23b68, + 0x9436c0760c86e30b,0xf9a0b6720aaf6521, + 0xb94470938fa89bce,0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2,0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232,0x25c6da63c38de1b0, + 0x8d590723948a535f,0x579c487e5a38ad0e, + 0xb0af48ec79ace837,0x2d835a9df0c6d851, + 0xdcdb1b2798182244,0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5,0xe272467e3d222f3f, + 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea,0x98e947129fc2b4e9, + 0xa87fea27a539e9a5,0x3f2398d747b36224, + 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89,0x1953cf68300424ac, + 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, + 0xcdb02555653131b6,0x3792f412cb06794d, + 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b,0xf245825a5a445275, + 0xfb158592be068d2e,0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d,0x55464dd69685606b, + 0xc428d05aa4751e4c,0xaa97e14c3c26b886, + 0xf53304714d9265df,0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab,0xe546a8038efe4029, + 0xbf8fdb78849a5f96,0xde98520472bdd033, + 0xef73d256a5c0f77c,0x963e66858f6d4440, + 0x95a8637627989aad,0xdde7001379a44aa8, + 0xbb127c53b17ec159,0x5560c018580d5d52, + 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, + 0x9226712162ab070d,0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, + 0xb267ed1940f1c61c,0x55f038b237591ed3, + 0xdf01e85f912e37a3,0x6b6c46dec52f6688, + 0x8b61313bbabce2c6,0x2323ac4b3b3da015, + 0xae397d8aa96c1b77,0xabec975e0a0d081a, + 0xd9c7dced53c72255,0x96e7bd358c904a21, + 0x881cea14545c7575,0x7e50d64177da2e54, + 0xaa242499697392d2,0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, + 0x84ec3c97da624ab4,0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba,0x67de18eda5814af2, + 0x81ceb32c4b43fcf4,0x80eacf948770ced7, + 0xa2425ff75e14fc31,0xa1258379a94d028d, + 0xcad2f7f5359a3b3e,0x96ee45813a04330, + 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, + 0x9e74d1b791e07e48,0x775ea264cf55347e, + 0xc612062576589dda,0x95364afe032a819e, + 0xf79687aed3eec551,0x3a83ddbd83f52205, + 0x9abe14cd44753b52,0xc4926a9672793543, + 0xc16d9a0095928a27,0x75b7053c0f178294, + 0xf1c90080baf72cb1,0x5324c68b12dd6339, + 0x971da05074da7bee,0xd3f6fc16ebca5e04, + 0xbce5086492111aea,0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07,0x3aff322e62439fd0, + 0xb877aa3236a4b449,0x9befeb9fad487c3, + 0xe69594bec44de15b,0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9,0xf9d37014bf60a11, + 0xb424dc35095cd80f,0x538484c19ef38c95, + 0xe12e13424bb40e13,0x2865a5f206b06fba, + 0x8cbccc096f5088cb,0xf93f87b7442e45d4, + 0xafebff0bcb24aafe,0xf78f69a51539d749, + 0xdbe6fecebdedd5be,0xb573440e5a884d1c, + 0x89705f4136b4a597,0x31680a88f8953031, + 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc,0x3d32907604691b4d, + 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, + 0xa7c5ac471b478423,0xfcf80dc33721d54, + 0xd1b71758e219652b,0xd3c36113404ea4a9, + 0x83126e978d4fdf3b,0x645a1cac083126ea, + 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, + 0xcccccccccccccccc,0xcccccccccccccccd, + 0x8000000000000000,0x0, + 0xa000000000000000,0x0, + 0xc800000000000000,0x0, + 0xfa00000000000000,0x0, + 0x9c40000000000000,0x0, + 0xc350000000000000,0x0, + 0xf424000000000000,0x0, + 0x9896800000000000,0x0, + 0xbebc200000000000,0x0, + 0xee6b280000000000,0x0, + 0x9502f90000000000,0x0, + 0xba43b74000000000,0x0, + 0xe8d4a51000000000,0x0, + 0x9184e72a00000000,0x0, + 0xb5e620f480000000,0x0, + 0xe35fa931a0000000,0x0, + 0x8e1bc9bf04000000,0x0, + 0xb1a2bc2ec5000000,0x0, + 0xde0b6b3a76400000,0x0, + 0x8ac7230489e80000,0x0, + 0xad78ebc5ac620000,0x0, + 0xd8d726b7177a8000,0x0, + 0x878678326eac9000,0x0, + 0xa968163f0a57b400,0x0, + 0xd3c21bcecceda100,0x0, + 0x84595161401484a0,0x0, + 0xa56fa5b99019a5c8,0x0, + 0xcecb8f27f4200f3a,0x0, + 0x813f3978f8940984,0x4000000000000000, + 0xa18f07d736b90be5,0x5000000000000000, + 0xc9f2c9cd04674ede,0xa400000000000000, + 0xfc6f7c4045812296,0x4d00000000000000, + 0x9dc5ada82b70b59d,0xf020000000000000, + 0xc5371912364ce305,0x6c28000000000000, + 0xf684df56c3e01bc6,0xc732000000000000, + 0x9a130b963a6c115c,0x3c7f400000000000, + 0xc097ce7bc90715b3,0x4b9f100000000000, + 0xf0bdc21abb48db20,0x1e86d40000000000, + 0x96769950b50d88f4,0x1314448000000000, + 0xbc143fa4e250eb31,0x17d955a000000000, + 0xeb194f8e1ae525fd,0x5dcfab0800000000, + 0x92efd1b8d0cf37be,0x5aa1cae500000000, + 0xb7abc627050305ad,0xf14a3d9e40000000, + 0xe596b7b0c643c719,0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f,0xe4820023a2000000, + 0xb35dbf821ae4f38b,0xdda2802c8a800000, + 0xe0352f62a19e306e,0xd50b2037ad200000, + 0x8c213d9da502de45,0x4526f422cc340000, + 0xaf298d050e4395d6,0x9670b12b7f410000, + 0xdaf3f04651d47b4c,0x3c0cdd765f114000, + 0x88d8762bf324cd0f,0xa5880a69fb6ac800, + 0xab0e93b6efee0053,0x8eea0d047a457a00, + 0xd5d238a4abe98068,0x72a4904598d6d880, + 0x85a36366eb71f041,0x47a6da2b7f864750, + 0xa70c3c40a64e6c51,0x999090b65f67d924, + 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, + 0x82818f1281ed449f,0xbff8f10e7a8921a4, + 0xa321f2d7226895c7,0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, + 0xfee50b7025c36a08,0x2f236d04753d5b4, + 0x9f4f2726179a2245,0x1d762422c946590, + 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, + 0x9b934c3b330c8577,0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a,0x8bef464e3945ef7a, + 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, + 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436,0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44,0x60dbbca87196b616, + 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, + 0xb51d13aea4a488dd,0x6babab6398bdbe41, + 0xe264589a4dcdab14,0xc696963c7eed2dd1, + 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8,0x3b25a55f43294bcb, + 0xdd15fe86affad912,0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab,0x6e3569326c784337, + 0xacb92ed9397bf996,0x49c2c37f07965404, + 0xd7e77a8f87daf7fb,0xdc33745ec97be906, + 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, + 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b,0xf50a3fa490c30190, + 0x83c7088e1aab65db,0x792667c6da79e0fa, + 0xa4b8cab1a1563f52,0x577001b891185938, + 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, + 0x80b05e5ac60b6178,0x544f8158315b05b4, + 0xa0dc75f1778e39d6,0x696361ae3db1c721, + 0xc913936dd571c84c,0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f,0x4ab48a04065c723, + 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, + 0xc45d1df942711d9a,0x3ba5d0bd324f8394, + 0xf5746577930d6500,0xca8f44ec7ee36479, + 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5,0xbba1f1d158724a12, + 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, + 0xea1575143cf97226,0xf52d09d71a3293bd, + 0x924d692ca61be758,0x593c2626705f9c56, + 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, + 0xe498f455c38b997a,0xb6dfb9c0f956447, + 0x8edf98b59a373fec,0x4724bd4189bd5eac, + 0xb2977ee300c50fe7,0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, + 0x8b865b215899f46c,0xbd79e0d20082ee74, + 0xae67f1e9aec07187,0xecd8590680a3aa11, + 0xda01ee641a708de9,0xe80e6f4820cc9495, + 0x884134fe908658b2,0x3109058d147fdcdd, + 0xaa51823e34a7eede,0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, + 0x850fadc09923329e,0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45,0x84db8346b786151c, + 0xcfe87f7cef46ff16,0xe612641865679a63, + 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749,0xe3be5e330f38f09d, + 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, + 0xc646d63501a1511d,0xb281e1fd541501b8, + 0xf7d88bc24209a565,0x1f225a7ca91a4226, + 0x9ae757596946075f,0x3375788de9b06958, + 0xc1a12d2fc3978937,0x52d6b1641c83ae, + 0xf209787bb47d6b84,0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332,0xf840b7ba963646e0, + 0xbd176620a501fbff,0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf,0xc66f336c36b10137, + 0xb8a8d9bbe123f017,0xb80b0047445d4184, + 0xe6d3102ad96cec1d,0xa60dc059157491e5, + 0x9043ea1ac7e41392,0x87c89837ad68db2f, + 0xb454e4a179dd1877,0x29babe4598c311fb, + 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d,0x1899e4a65f58660c, + 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d,0x76707543f4fa1f73, + 0x899504ae72497eba,0x6a06494a791c53a8, + 0xabfa45da0edbde69,0x487db9d17636892, + 0xd6f8d7509292d603,0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, + 0xa7f26836f282b732,0x8e6cac7768d7141e, + 0xd1ef0244af2364ff,0x3207d795430cd926, + 0x8335616aed761f1f,0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, + 0xcd036837130890a1,0x36dba887c37a8c0f, + 0x802221226be55a64,0xc2494954da2c9789, + 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d,0x6f92829494e5acc7, + 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, + 0x9c69a97284b578d7,0xff2a760414536efb, + 0xc38413cf25e2d70d,0xfef5138519684aba, + 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, + 0x98bf2f79d5993802,0xef2f773ffbd97a61, + 0xbeeefb584aff8603,0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2,0xdd945a747bf26183, + 0xba756174393d88df,0x94f971119aeef9e4, + 0xe912b9d1478ceb17,0x7a37cd5601aab85d, + 0x91abb422ccb812ee,0xac62e055c10ab33a, + 0xb616a12b7fe617aa,0x577b986b314d6009, + 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d,0x14588f13be847307, + 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee,0x25de7bb9480d5854, + 0xada72ccc20054ae9,0xaf561aa79a10ae6a, + 0xd910f7ff28069da4,0x1b2ba1518094da04, + 0x87aa9aff79042286,0x90fb44d2f05d0842, + 0xa99541bf57452b28,0x353a1607ac744a53, + 0xd3fa922f2d1675f2,0x42889b8997915ce8, + 0x847c9b5d7c2e09b7,0x69956135febada11, + 0xa59bc234db398c25,0x43fab9837e699095, + 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, + 0x8161afb94b44f57d,0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc,0x6462d92a69731732, + 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78,0x5cda735244c3d43e, + 0x9defbf01b061adab,0x3a0888136afa64a7, + 0xc56baec21c7a1916,0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b,0x8aad549e57273d45, + 0x9a3c2087a63f6399,0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, + 0x969eb7c47859e743,0x9f644ae5a4b1b325, + 0xbc4665b596706114,0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8,0x9a7f12442d588f2, + 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, + 0x8fa475791a569d10,0xf96e017d694487bc, + 0xb38d92d760ec4455,0x37c981dcc395a9ac, + 0xe070f78d3927556a,0x85bbe253f47b1417, + 0x8c469ab843b89562,0x93956d7478ccec8e, + 0xaf58416654a6babb,0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, + 0x88fcf317f22241e2,0x441fece3bdf81f03, + 0xab3c2fddeeaad25a,0xd527e81cad7626c3, + 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, + 0x85c7056562757456,0xf6872d5667844e49, + 0xa738c6bebb12d16c,0xb428f8ac016561db, + 0xd106f86e69d785c7,0xe13336d701beba52, + 0x82a45b450226b39c,0xecc0024661173473, + 0xa34d721642b06084,0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, + 0xff290242c83396ce,0x7e67047175a15271, + 0x9f79a169bd203e41,0xf0062c6e984d386, + 0xc75809c42c684dd1,0x52c07b78a3e60868, + 0xf92e0c3537826145,0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb,0x88a66076400bb691, + 0xc2abf989935ddbfe,0x6acff893d00ea435, + 0xf356f7ebf83552fe,0x583f6b8c4124d43, + 0x98165af37b2153de,0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, + 0xeda2ee1c7064130c,0x1162def06f79df73, + 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, + 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0,0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, + 0x8da471a9de737e24,0x5ceaecfed289e5d2, + 0xb10d8e1456105dad,0x7425a83e872c5f47, + 0xdd50f1996b947518,0xd12f124e28f77719, + 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b,0x636cc64d1001550b, + 0xd8210befd30efa5a,0x3c47f7e05401aa4e, + 0x8714a775e3e95c78,0x65acfaec34810a71, + 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, + 0xd31045a8341ca07c,0x1ede48111209a050, + 0x83ea2b892091e44d,0x934aed0aab460432, + 0xa4e4b66b68b65d60,0xf81da84d5617853f, + 0xce1de40642e3f4b9,0x36251260ab9d668e, + 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, + 0xa1075a24e4421730,0xb24cf65b8612f81f, + 0xc94930ae1d529cfc,0xdee033f26797b627, + 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, + 0x9d412e0806e88aa5,0x8e1f289560ee864e, + 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2,0xae10af696774b1db, + 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f,0x17fd090a58d32af3, + 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, + 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513,0x84c86189216dc5ed, + 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515,0xfabaf3feaa5334a, + 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8,0x743e20e9ef511012, + 0xdf78e4b2bd342cf6,0x914da9246b255416, + 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, + 0xae9672aba3d0c320,0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, + 0x8865899617fb1871,0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, + 0xd51ea6fa85785631,0x552a74227f3ea565, + 0x8533285c936b35de,0xd53a88958f87275f, + 0xa67ff273b8460356,0x8a892abaf368f137, + 0xd01fef10a657842c,0x2d2b7569b0432d85, + 0x8213f56a67f6b29b,0x9c3b29620e29fc73, + 0xa298f2c501f45f42,0x8349f3ba91b47b8f, + 0xcb3f2f7642717713,0x241c70a936219a73, + 0xfe0efb53d30dd4d7,0xed238cd383aa0110, + 0x9ec95d1463e8a506,0xf4363804324a40aa, + 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da,0xdd94b7868e94050a, + 0x9b10a4e5e9913128,0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf,0xbc633b39673c8cec, + 0x976e41088617ca01,0xd5be0503e085d813, + 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, + 0xec9c459d51852ba2,0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45,0xcabb90e5c942b503, + 0xb8da1662e7b00a17,0x3d6a751f3b936243, + 0xe7109bfba19c0c9d,0xcc512670a783ad4, + 0x906a617d450187e2,0x27fb2b80668b24c5, + 0xb484f9dc9641e9da,0xb1f9f660802dedf6, + 0xe1a63853bbd26451,0x5e7873f8a0396973, + 0x8d07e33455637eb2,0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7,0x7641a140cc7810fb, + 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, + 0xac2820d9623bf429,0x546345fa9fbdcd44, + 0xd732290fbacaf133,0xa97c177947ad4095, + 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, + 0xa81f301449ee8c70,0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c,0x73832eec6fff3111, + 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, + 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, + 0xa0555e361951c366,0xd7e105bcc332621f, + 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, + 0xfa856334878fc150,0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07,0xa862f80ec4700c8, + 0xf4a642e14c6262c8,0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, + 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, + 0xeeea5d5004981478,0x1858ccfce06cac74, + 0x95527a5202df0ccb,0xf37801e0c43ebc8, + 0xbaa718e68396cffd,0xd30560258f54e6ba, + 0xe950df20247c83fd,0x47c6b82ef32a2069, + 0x91d28b7416cdd27e,0x4cdc331d57fa5441, + 0xb6472e511c81471d,0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5,0x58180fddd97723a6, + 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; +using powers = powers_template<>; + +} + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include +#include +#include +#include +#include +#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating +// the result, with the "high" part corresponding to the most significant bits and the +// low part corresponding to the least significant bits. +// +template +fastfloat_really_inline +value128 compute_product_approximation(int64_t q, uint64_t w) { + const int index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact because + // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); + // gives the exact answer. + value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); + constexpr uint64_t precision_mask = (bit_precision < 64) ? + (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. + value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if(secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ + constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; + } +} // namespace detail + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +template +fastfloat_really_inline +adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { + int hilz = int(w >> 63) ^ 1; + adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +template +fastfloat_really_inline +adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { + int lz = leading_zeroes(w); + w <<= lz; + value128 product = compute_product_approximation(q, w); + return compute_error_scaled(q, product.high, lz); +} + +// w * 10 ** q +// The returned value should be a valid ieee64 number that simply need to be packed. +// However, in some very rare cases, the computation will fail. In such cases, we +// return an adjusted_mantissa with a negative power of 2: the caller should recompute +// in such cases. +template +fastfloat_really_inline +adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) + + value128 product = compute_product_approximation(q, w); + if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further + // In some very rare cases, this could happen, in which case we might need a more accurate + // computation that what we can provide cheaply. This is very, very unlikely. + // + const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0, + // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation. + if(!inside_safe_exponent) { + return compute_error_scaled(q, product.high, lz); + } + } + // The "compute_product_approximation" function can be slightly slower than a branchless approach: + // value128 product = compute_product(q, w); + // but in practice, we can win big with the compute_product_approximation if its additional branch + // is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + + answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + + answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0. + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1) ) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go back!!! + if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_BIGINT_H +#define FASTFLOAT_BIGINT_H + +#include +#include +#include +#include + + +namespace fast_float { + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FASTFLOAT_64BIT) && !defined(__sparc) +#define FASTFLOAT_64BIT_LIMB +typedef uint64_t limb; +constexpr size_t limb_bits = 64; +#else +#define FASTFLOAT_32BIT_LIMB +typedef uint32_t limb; +constexpr size_t limb_bits = 32; +#endif + +typedef span limb_span; + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +constexpr size_t bigint_bits = 4000; +constexpr size_t bigint_limbs = bigint_bits / limb_bits; + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. +template +struct stackvec { + limb data[size]; + // we never need more than 150 limbs + uint16_t length{0}; + + stackvec() = default; + stackvec(const stackvec &) = delete; + stackvec &operator=(const stackvec &) = delete; + stackvec(stackvec &&) = delete; + stackvec &operator=(stackvec &&other) = delete; + + // create stack vector from existing limb span. + stackvec(limb_span s) { + FASTFLOAT_ASSERT(try_extend(s)); + } + + limb& operator[](size_t index) noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + const limb& operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + // index from the end of the container + const limb& rindex(size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + size_t rindex = length - index - 1; + return data[rindex]; + } + + // set the length, without bounds checking. + void set_len(size_t len) noexcept { + length = uint16_t(len); + } + constexpr size_t len() const noexcept { + return length; + } + constexpr bool is_empty() const noexcept { + return length == 0; + } + constexpr size_t capacity() const noexcept { + return size; + } + // append item to vector, without bounds checking + void push_unchecked(limb value) noexcept { + data[length] = value; + length++; + } + // append item to vector, returning if item was added + bool try_push(limb value) noexcept { + if (len() < capacity()) { + push_unchecked(value); + return true; + } else { + return false; + } + } + // add items to the vector, from a span, without bounds checking + void extend_unchecked(limb_span s) noexcept { + limb* ptr = data + length; + ::memcpy((void*)ptr, (const void*)s.ptr, sizeof(limb) * s.len()); + set_len(len() + s.len()); + } + // try to add items to the vector, returning if items were added + bool try_extend(limb_span s) noexcept { + if (len() + s.len() <= capacity()) { + extend_unchecked(s); + return true; + } else { + return false; + } + } + // resize the vector, without bounds checking + // if the new size is longer than the vector, assign value to each + // appended item. + void resize_unchecked(size_t new_len, limb value) noexcept { + if (new_len > len()) { + size_t count = new_len - len(); + limb* first = data + len(); + limb* last = first + count; + ::std::fill(first, last, value); + set_len(new_len); + } else { + set_len(new_len); + } + } + // try to resize the vector, returning if the vector was resized. + bool try_resize(size_t new_len, limb value) noexcept { + if (new_len > capacity()) { + return false; + } else { + resize_unchecked(new_len, value); + return true; + } + } + // check if any limbs are non-zero after the given index. + // this needs to be done in reverse order, since the index + // is relative to the most significant limbs. + bool nonzero(size_t index) const noexcept { + while (index < len()) { + if (rindex(index) != 0) { + return true; + } + index++; + } + return false; + } + // normalize the big integer, so most-significant zero limbs are removed. + void normalize() noexcept { + while (len() > 0 && rindex(0) == 0) { + length--; + } + } +}; + +fastfloat_really_inline +uint64_t empty_hi64(bool& truncated) noexcept { + truncated = false; + return 0; +} + +fastfloat_really_inline +uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept { + truncated = false; + int shl = leading_zeroes(r0); + return r0 << shl; +} + +fastfloat_really_inline +uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept { + int shl = leading_zeroes(r0); + if (shl == 0) { + truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +fastfloat_really_inline +uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept { + return uint64_hi64(r0, truncated); +} + +fastfloat_really_inline +uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + return uint64_hi64((x0 << 32) | x1, truncated); +} + +fastfloat_really_inline +uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return uint64_hi64(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +fastfloat_really_inline +limb scalar_add(limb x, limb y, bool& overflow) noexcept { + limb z; + +// gcc and clang +#if defined(__has_builtin) + #if __has_builtin(__builtin_add_overflow) + overflow = __builtin_add_overflow(x, y, &z); + return z; + #endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +fastfloat_really_inline +limb scalar_mul(limb x, limb y, limb& carry) noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + #if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); + carry = limb(z >> limb_bits); + return limb(z); + #else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + value128 z = full_multiplication(x, y); + bool overflow; + z.low = scalar_add(z.low, carry, overflow); + z.high += uint64_t(overflow); // cannot overflow + carry = z.high; + return z.low; + #endif +#else + uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +template +inline bool small_add_from(stackvec& vec, limb y, size_t start) noexcept { + size_t index = start; + limb carry = y; + bool overflow; + while (carry != 0 && index < vec.len()) { + vec[index] = scalar_add(vec[index], carry, overflow); + carry = limb(overflow); + index += 1; + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add scalar value to bigint. +template +fastfloat_really_inline bool small_add(stackvec& vec, limb y) noexcept { + return small_add_from(vec, y, 0); +} + +// multiply bigint by scalar value. +template +inline bool small_mul(stackvec& vec, limb y) noexcept { + limb carry = 0; + for (size_t index = 0; index < vec.len(); index++) { + vec[index] = scalar_mul(vec[index], y, carry); + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +template +bool large_add_from(stackvec& x, limb_span y, size_t start) noexcept { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + if (x.len() < start || y.len() > x.len() - start) { + FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + } + + bool carry = false; + for (size_t index = 0; index < y.len(); index++) { + limb xi = x[index + start]; + limb yi = y[index]; + bool c1 = false; + bool c2 = false; + xi = scalar_add(xi, yi, c1); + if (carry) { + xi = scalar_add(xi, 1, c2); + } + x[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + } + return true; +} + +// add bigint to bigint. +template +fastfloat_really_inline bool large_add_from(stackvec& x, limb_span y) noexcept { + return large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +template +bool long_mul(stackvec& x, limb_span y) noexcept { + limb_span xs = limb_span(x.data, x.len()); + stackvec z(xs); + limb_span zs = limb_span(z.data, z.len()); + + if (y.len() != 0) { + limb y0 = y[0]; + FASTFLOAT_TRY(small_mul(x, y0)); + for (size_t index = 1; index < y.len(); index++) { + limb yi = y[index]; + stackvec zi; + if (yi != 0) { + // re-use the same buffer throughout + zi.set_len(0); + FASTFLOAT_TRY(zi.try_extend(zs)); + FASTFLOAT_TRY(small_mul(zi, yi)); + limb_span zis = limb_span(zi.data, zi.len()); + FASTFLOAT_TRY(large_add_from(x, zis, index)); + } + } + } + + x.normalize(); + return true; +} + +// grade-school multiplication algorithm +template +bool large_mul(stackvec& x, limb_span y) noexcept { + if (y.len() == 1) { + FASTFLOAT_TRY(small_mul(x, y[0])); + } else { + FASTFLOAT_TRY(long_mul(x, y)); + } + return true; +} + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +struct bigint { + // storage of the limbs, in little-endian order. + stackvec vec; + + bigint(): vec() {} + bigint(const bigint &) = delete; + bigint &operator=(const bigint &) = delete; + bigint(bigint &&) = delete; + bigint &operator=(bigint &&other) = delete; + + bigint(uint64_t value): vec() { +#ifdef FASTFLOAT_64BIT_LIMB + vec.push_unchecked(value); +#else + vec.push_unchecked(uint32_t(value)); + vec.push_unchecked(uint32_t(value >> 32)); +#endif + vec.normalize(); + } + + // get the high 64 bits from the vector, and if bits were truncated. + // this is to get the significant digits for the float. + uint64_t hi64(bool& truncated) const noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint64_hi64(vec.rindex(0), truncated); + } else { + uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); + truncated |= vec.nonzero(2); + return result; + } +#else + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint32_hi64(vec.rindex(0), truncated); + } else if (vec.len() == 2) { + return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); + } else { + uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); + truncated |= vec.nonzero(3); + return result; + } +#endif + } + + // compare two big integers, returning the large value. + // assumes both are normalized. if the return value is + // negative, other is larger, if the return value is + // positive, this is larger, otherwise they are equal. + // the limbs are stored in little-endian order, so we + // must compare the limbs in ever order. + int compare(const bigint& other) const noexcept { + if (vec.len() > other.vec.len()) { + return 1; + } else if (vec.len() < other.vec.len()) { + return -1; + } else { + for (size_t index = vec.len(); index > 0; index--) { + limb xi = vec[index - 1]; + limb yi = other.vec[index - 1]; + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } + } + + // shift left each limb n bits, carrying over to the new limb + // returns true if we were able to shift all the digits. + bool shl_bits(size_t n) noexcept { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FASTFLOAT_DEBUG_ASSERT(n != 0); + FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); + + size_t shl = n; + size_t shr = limb_bits - shl; + limb prev = 0; + for (size_t index = 0; index < vec.len(); index++) { + limb xi = vec[index]; + vec[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + limb carry = prev >> shr; + if (carry != 0) { + return vec.try_push(carry); + } + return true; + } + + // move the limbs left by `n` limbs. + bool shl_limbs(size_t n) noexcept { + FASTFLOAT_DEBUG_ASSERT(n != 0); + if (n + vec.len() > vec.capacity()) { + return false; + } else if (!vec.is_empty()) { + // move limbs + limb* dst = vec.data + n; + const limb* src = vec.data; + ::memmove(dst, src, sizeof(limb) * vec.len()); + // fill in empty limbs + limb* first = vec.data; + limb* last = first + n; + ::std::fill(first, last, 0); + vec.set_len(n + vec.len()); + return true; + } else { + return true; + } + } + + // move the limbs left by `n` bits. + bool shl(size_t n) noexcept { + size_t rem = n % limb_bits; + size_t div = n / limb_bits; + if (rem != 0) { + FASTFLOAT_TRY(shl_bits(rem)); + } + if (div != 0) { + FASTFLOAT_TRY(shl_limbs(div)); + } + return true; + } + + // get the number of leading zeros in the bigint. + int ctlz() const noexcept { + if (vec.is_empty()) { + return 0; + } else { +#ifdef FASTFLOAT_64BIT_LIMB + return leading_zeroes(vec.rindex(0)); +#else + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); +#endif + } + } + + // get the number of bits in the bigint. + int bit_length() const noexcept { + int lz = ctlz(); + return int(limb_bits * vec.len()) - lz; + } + + bool mul(limb y) noexcept { + return small_mul(vec, y); + } + + bool add(limb y) noexcept { + return small_add(vec, y); + } + + // multiply as if by 2 raised to a power. + bool pow2(uint32_t exp) noexcept { + return shl(exp); + } + + // multiply as if by 5 raised to a power. + bool pow5(uint32_t exp) noexcept { + // multiply by a power of 5 + static constexpr uint32_t large_step = 135; + static constexpr uint64_t small_power_of_5[] = { + 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, + 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, + 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, + 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, + 2384185791015625UL, 11920928955078125UL, 59604644775390625UL, + 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, + }; +#ifdef FASTFLOAT_64BIT_LIMB + constexpr static limb large_power_of_5[] = { + 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, + 10482974169319127550UL, 198276706040285095UL}; +#else + constexpr static limb large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif + size_t large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span large = limb_span(large_power_of_5, large_length); + while (exp >= large_step) { + FASTFLOAT_TRY(large_mul(vec, large)); + exp -= large_step; + } +#ifdef FASTFLOAT_64BIT_LIMB + uint32_t small_step = 27; + limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FASTFLOAT_TRY(small_mul(vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp]))); + } + + return true; + } + + // multiply as if by 10 raised to a power. + bool pow10(uint32_t exp) noexcept { + FASTFLOAT_TRY(pow5(exp)); + return pow2(exp); + } +}; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +typedef span byte_span; + +struct parsed_number_string { + int64_t exponent{0}; + uint64_t mantissa{0}; + const char *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + byte_span integer{}; // non-nullable + byte_span fraction{}; // nullable +}; + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = byte_span(start_digits, size_t(digit_count)); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char* before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = byte_span(before, size_t(p - before)); + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + const char* int_end = p + answer.integer.len(); + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + const char* frac_end = p + answer.fraction.len(); + while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DIGIT_COMPARISON_H +#define FASTFLOAT_DIGIT_COMPARISON_H + +#include +#include +#include +#include + + +namespace fast_float { + +// 1e0 to 1e19 +constexpr static uint64_t powers_of_ten_uint64[] = { + 1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, + 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, + 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, + 1000000000000000000UL, 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// this algorithm is not even close to optimized, but it has no practical +// effect on performance: in order to have a faster algorithm, we'd need +// to slow down performance for faster algorithms, and this is still fast. +fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& num) noexcept { + uint64_t mantissa = num.mantissa; + int32_t exponent = int32_t(num.exponent); + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +template +fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept { + using equiv_uint = typename binary_format::equiv_uint; + constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); + constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); + constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); + + adjusted_mantissa am; + int32_t bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); + equiv_uint bits; + ::memcpy(&bits, &value, sizeof(T)); + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((bits & exponent_mask) >> binary_format::mantissa_explicit_bits()); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +template +fastfloat_really_inline adjusted_mantissa to_extended_halfway(T value) noexcept { + adjusted_mantissa am = to_extended(value); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +// round an extended-precision float to the nearest machine float. +template +fastfloat_really_inline void round(adjusted_mantissa& am, callback cb) noexcept { + int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; + if (-am.power2 >= mantissa_shift) { + // have a denormal float + int32_t shift = -am.power2 + 1; + cb(am, std::min(shift, 64)); + // check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = (am.mantissa < (uint64_t(1) << binary_format::mantissa_explicit_bits())) ? 0 : 1; + return; + } + + // have a normal float, use the default shift. + cb(am, mantissa_shift); + + // check for carry + if (am.mantissa >= (uint64_t(2) << binary_format::mantissa_explicit_bits())) { + am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); + am.power2++; + } + + // check for infinite: we could have carried to an infinite power + am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); + if (am.power2 >= binary_format::infinite_power()) { + am.power2 = binary_format::infinite_power(); + am.mantissa = 0; + } +} + +template +fastfloat_really_inline +void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept { + uint64_t mask; + uint64_t halfway; + if (shift == 64) { + mask = UINT64_MAX; + } else { + mask = (uint64_t(1) << shift) - 1; + } + if (shift == 0) { + halfway = 0; + } else { + halfway = uint64_t(1) << (shift - 1); + } + uint64_t truncated_bits = am.mantissa & mask; + uint64_t is_above = truncated_bits > halfway; + uint64_t is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; + + bool is_odd = (am.mantissa & 1) == 1; + am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); +} + +fastfloat_really_inline void round_down(adjusted_mantissa& am, int32_t shift) noexcept { + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; +} + +fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept { + uint64_t val; + while (std::distance(first, last) >= 8) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != 0x3030303030303030) { + break; + } + first += 8; + } + while (first != last) { + if (*first != '0') { + break; + } + first++; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept { + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while (std::distance(first, last) >= 8) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != 0x3030303030303030) { + return true; + } + first += 8; + } + while (first != last) { + if (*first != '0') { + return true; + } + first++; + } + return false; +} + +fastfloat_really_inline bool is_truncated(byte_span s) noexcept { + return is_truncated(s.ptr, s.ptr + s.len()); +} + +fastfloat_really_inline +void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { + value = value * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + counter += 8; + count += 8; +} + +fastfloat_really_inline +void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { + value = value * 10 + limb(*p - '0'); + p++; + counter++; + count++; +} + +fastfloat_really_inline +void add_native(bigint& big, limb power, limb value) noexcept { + big.mul(power); + big.add(value); +} + +fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcept { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + add_native(big, 10, 1); + count++; +} + +// parse the significant digits into a big integer +inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max_digits, size_t& digits) noexcept { + // try to minimize the number of big integer and scalar multiplication. + // therefore, try to parse 8 digits at a time, and multiply by the largest + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + digits = 0; + limb value = 0; +#ifdef FASTFLOAT_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + const char* p = num.integer.ptr; + const char* pend = p + num.integer.len(); + skip_zeros(p, pend); + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (num.fraction.ptr != nullptr) { + truncated |= is_truncated(num.fraction); + } + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction.ptr != nullptr) { + p = num.fraction.ptr; + pend = p + num.fraction.len(); + if (digits == 0) { + skip_zeros(p, pend); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + } +} + +template +inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); + adjusted_mantissa answer; + bool truncated; + answer.mantissa = bigmant.hi64(truncated); + int bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); + answer.power2 = bigmant.bit_length() - 64 + bias; + + round(answer, [truncated](adjusted_mantissa& a, int32_t shift) { + round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { + return is_above || (is_halfway && truncated) || (is_odd && is_halfway); + }); + }); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +template +inline adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint& real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + adjusted_mantissa am_b = am; + // gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type. + round(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); }); + T b; + to_float(false, am_b, b); + adjusted_mantissa theor = to_extended_halfway(b); + bigint theor_digits(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = uint32_t(-real_exp); + if (pow5_exp != 0) { + FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); + } + if (pow2_exp > 0) { + FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + } else if (pow2_exp < 0) { + FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + } + + // compare digits, and use it to director rounding + int ord = real_digits.compare(theor_digits); + adjusted_mantissa answer = am; + round(answer, [ord](adjusted_mantissa& a, int32_t shift) { + round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool { + (void)_; // not needed, since we've done our comparison + (void)__; // not needed, since we've done our comparison + if (ord > 0) { + return true; + } else if (ord < 0) { + return false; + } else { + return is_odd; + } + }); + }); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round the +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +template +inline adjusted_mantissa digit_comp(parsed_number_string& num, adjusted_mantissa am) noexcept { + // remove the invalid exponent bias + am.power2 -= invalid_am_bias; + + int32_t sci_exp = scientific_exponent(num); + size_t max_digits = binary_format::max_digits(); + size_t digits = 0; + bigint bigmant; + parse_mantissa(bigmant, num, max_digits, digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - int32_t(digits); + if (exponent >= 0) { + return positive_digit_comp(bigmant, exponent); + } else { + return negative_digit_comp(bigmant, am, exponent); + } +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + + +#include +#include +#include +#include + +namespace fast_float { + + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { + from_chars_result answer; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + bool minusSign = false; + if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here + minusSign = true; + ++first; + } + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, "nan", 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if(first != last && *first == '(') { + for(const char* ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == ')') { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } + else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_')) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, "inf", 3)) { + if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +} // namespace detail + +template +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_advanced(first, last, value, parse_options{fmt}); +} + +template +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept { + + static_assert (std::is_same::value || std::is_same::value, "only float and double are supported"); + + + from_chars_result answer; + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string pns = parse_number_string(first, last, options); + if (!pns.valid) { + return detail::parse_infnan(first, last, value); + } + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + // Next is Clinger's fast path. + if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && pns.mantissa <=binary_format::max_mantissa_fast_path() && !pns.too_many_digits) { + value = T(pns.mantissa); + if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } + else { value = value * binary_format::exact_power_of_ten(pns.exponent); } + if (pns.negative) { value = -value; } + return answer; + } + adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); + if(pns.too_many_digits && am.power2 >= 0) { + if(am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am = compute_error>(pns.exponent, pns.mantissa); + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), + // then we need to go the long way around again. This is very uncommon. + if(am.power2 < 0) { am = digit_comp(pns, am); } + to_float(pns.negative, am, value); + return answer; +} + +} // namespace fast_float + +#endif + diff --git a/luprex/cpp/core/flat-hash-map.hpp b/luprex/cpp/core/flat-hash-map.hpp new file mode 100644 index 00000000..a8723ee8 --- /dev/null +++ b/luprex/cpp/core/flat-hash-map.hpp @@ -0,0 +1,1496 @@ +// Copyright Malte Skarupke 2017. +// Distributed under the Boost Software License, Version 1.0. +// (See http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#define SKA_NOINLINE(...) __declspec(noinline) __VA_ARGS__ +#else +#define SKA_NOINLINE(...) __VA_ARGS__ __attribute__((noinline)) +#endif + +namespace ska +{ +struct prime_number_hash_policy; +struct power_of_two_hash_policy; +struct fibonacci_hash_policy; + +namespace detailv3 +{ +template +struct functor_storage : Functor +{ + functor_storage() = default; + functor_storage(const Functor & functor) + : Functor(functor) + { + } + template + Result operator()(Args &&... args) + { + return static_cast(*this)(std::forward(args)...); + } + template + Result operator()(Args &&... args) const + { + return static_cast(*this)(std::forward(args)...); + } +}; +template +struct functor_storage +{ + typedef Result (*function_ptr)(Args...); + function_ptr function; + functor_storage(function_ptr function) + : function(function) + { + } + Result operator()(Args... args) const + { + return function(std::forward(args)...); + } + operator function_ptr &() + { + return function; + } + operator const function_ptr &() + { + return function; + } +}; +template +struct KeyOrValueHasher : functor_storage +{ + typedef functor_storage hasher_storage; + KeyOrValueHasher() = default; + KeyOrValueHasher(const hasher & hash) + : hasher_storage(hash) + { + } + size_t operator()(const key_type & key) + { + return static_cast(*this)(key); + } + size_t operator()(const key_type & key) const + { + return static_cast(*this)(key); + } + size_t operator()(const value_type & value) + { + return static_cast(*this)(value.first); + } + size_t operator()(const value_type & value) const + { + return static_cast(*this)(value.first); + } + template + size_t operator()(const std::pair & value) + { + return static_cast(*this)(value.first); + } + template + size_t operator()(const std::pair & value) const + { + return static_cast(*this)(value.first); + } +}; +template +struct KeyOrValueEquality : functor_storage +{ + typedef functor_storage equality_storage; + KeyOrValueEquality() = default; + KeyOrValueEquality(const key_equal & equality) + : equality_storage(equality) + { + } + bool operator()(const key_type & lhs, const key_type & rhs) + { + return static_cast(*this)(lhs, rhs); + } + bool operator()(const key_type & lhs, const value_type & rhs) + { + return static_cast(*this)(lhs, rhs.first); + } + bool operator()(const value_type & lhs, const key_type & rhs) + { + return static_cast(*this)(lhs.first, rhs); + } + bool operator()(const value_type & lhs, const value_type & rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + template + bool operator()(const key_type & lhs, const std::pair & rhs) + { + return static_cast(*this)(lhs, rhs.first); + } + template + bool operator()(const std::pair & lhs, const key_type & rhs) + { + return static_cast(*this)(lhs.first, rhs); + } + template + bool operator()(const value_type & lhs, const std::pair & rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + template + bool operator()(const std::pair & lhs, const value_type & rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + template + bool operator()(const std::pair & lhs, const std::pair & rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } +}; +static constexpr int8_t min_lookups = 4; +template +struct sherwood_v3_entry +{ + sherwood_v3_entry() + { + } + sherwood_v3_entry(int8_t distance_from_desired) + : distance_from_desired(distance_from_desired) + { + } + ~sherwood_v3_entry() + { + } + static sherwood_v3_entry * empty_default_table() + { + static sherwood_v3_entry result[min_lookups] = { {}, {}, {}, {special_end_value} }; + return result; + } + + bool has_value() const + { + return distance_from_desired >= 0; + } + bool is_empty() const + { + return distance_from_desired < 0; + } + bool is_at_desired_position() const + { + return distance_from_desired <= 0; + } + template + void emplace(int8_t distance, Args &&... args) + { + new (std::addressof(value)) T(std::forward(args)...); + distance_from_desired = distance; + } + + void destroy_value() + { + value.~T(); + distance_from_desired = -1; + } + + int8_t distance_from_desired = -1; + static constexpr int8_t special_end_value = 0; + union { T value; }; +}; + +inline int8_t log2(size_t value) +{ + static constexpr int8_t table[64] = + { + 63, 0, 58, 1, 59, 47, 53, 2, + 60, 39, 48, 27, 54, 33, 42, 3, + 61, 51, 37, 40, 49, 18, 28, 20, + 55, 30, 34, 11, 43, 14, 22, 4, + 62, 57, 46, 52, 38, 26, 32, 41, + 50, 36, 17, 19, 29, 10, 13, 21, + 56, 45, 25, 31, 35, 16, 9, 12, + 44, 24, 15, 8, 23, 7, 6, 5 + }; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + return table[((value - (value >> 1)) * 0x07EDD5E59A4E28C2) >> 58]; +} + +template +struct AssignIfTrue +{ + void operator()(T & lhs, const T & rhs) + { + lhs = rhs; + } + void operator()(T & lhs, T && rhs) + { + lhs = std::move(rhs); + } +}; +template +struct AssignIfTrue +{ + void operator()(T &, const T &) + { + } + void operator()(T &, T &&) + { + } +}; + +inline size_t next_power_of_two(size_t i) +{ + --i; + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + i |= i >> 32; + ++i; + return i; +} + +template using void_t = void; + +template +struct HashPolicySelector +{ + typedef fibonacci_hash_policy type; +}; +template +struct HashPolicySelector> +{ + typedef typename T::hash_policy type; +}; + +template +class sherwood_v3_table : private EntryAlloc, private Hasher, private Equal +{ + using Entry = detailv3::sherwood_v3_entry; + using AllocatorTraits = std::allocator_traits; + using EntryPointer = typename AllocatorTraits::pointer; + struct convertible_to_iterator; + +public: + + using value_type = T; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using hasher = ArgumentHash; + using key_equal = ArgumentEqual; + using allocator_type = EntryAlloc; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = value_type *; + using const_pointer = const value_type *; + + sherwood_v3_table() + { + } + explicit sherwood_v3_table(size_type bucket_count, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) + : EntryAlloc(alloc), Hasher(hash), Equal(equal) + { + rehash(bucket_count); + } + sherwood_v3_table(size_type bucket_count, const ArgumentAlloc & alloc) + : sherwood_v3_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v3_table(size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) + : sherwood_v3_table(bucket_count, hash, ArgumentEqual(), alloc) + { + } + explicit sherwood_v3_table(const ArgumentAlloc & alloc) + : EntryAlloc(alloc) + { + } + template + sherwood_v3_table(It first, It last, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) + : sherwood_v3_table(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + template + sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentAlloc & alloc) + : sherwood_v3_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + template + sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) + : sherwood_v3_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v3_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) + : sherwood_v3_table(bucket_count, hash, equal, alloc) + { + if (bucket_count == 0) + rehash(il.size()); + insert(il.begin(), il.end()); + } + sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc & alloc) + : sherwood_v3_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) + : sherwood_v3_table(il, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v3_table(const sherwood_v3_table & other) + : sherwood_v3_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) + { + } + sherwood_v3_table(const sherwood_v3_table & other, const ArgumentAlloc & alloc) + : EntryAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor) + { + rehash_for_other_container(other); + try + { + insert(other.begin(), other.end()); + } + catch(...) + { + clear(); + deallocate_data(entries, num_slots_minus_one, max_lookups); + throw; + } + } + sherwood_v3_table(sherwood_v3_table && other) noexcept + : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)) + { + swap_pointers(other); + } + sherwood_v3_table(sherwood_v3_table && other, const ArgumentAlloc & alloc) noexcept + : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)) + { + swap_pointers(other); + } + sherwood_v3_table & operator=(const sherwood_v3_table & other) + { + if (this == std::addressof(other)) + return *this; + + clear(); + if (AllocatorTraits::propagate_on_container_copy_assignment::value) + { + if (static_cast(*this) != static_cast(other)) + { + reset_to_empty_state(); + } + AssignIfTrue()(*this, other); + } + _max_load_factor = other._max_load_factor; + static_cast(*this) = other; + static_cast(*this) = other; + rehash_for_other_container(other); + insert(other.begin(), other.end()); + return *this; + } + sherwood_v3_table & operator=(sherwood_v3_table && other) noexcept + { + if (this == std::addressof(other)) + return *this; + else if (AllocatorTraits::propagate_on_container_move_assignment::value) + { + clear(); + reset_to_empty_state(); + AssignIfTrue()(*this, std::move(other)); + swap_pointers(other); + } + else if (static_cast(*this) == static_cast(other)) + { + swap_pointers(other); + } + else + { + clear(); + _max_load_factor = other._max_load_factor; + rehash_for_other_container(other); + for (T & elem : other) + emplace(std::move(elem)); + other.clear(); + } + static_cast(*this) = std::move(other); + static_cast(*this) = std::move(other); + return *this; + } + ~sherwood_v3_table() + { + clear(); + deallocate_data(entries, num_slots_minus_one, max_lookups); + } + + const allocator_type & get_allocator() const + { + return static_cast(*this); + } + const ArgumentEqual & key_eq() const + { + return static_cast(*this); + } + const ArgumentHash & hash_function() const + { + return static_cast(*this); + } + + template + struct templated_iterator + { + templated_iterator() = default; + templated_iterator(EntryPointer current) + : current(current) + { + } + EntryPointer current = EntryPointer(); + + using iterator_category = std::forward_iterator_tag; + using value_type = ValueType; + using difference_type = ptrdiff_t; + using pointer = ValueType *; + using reference = ValueType &; + + friend bool operator==(const templated_iterator & lhs, const templated_iterator & rhs) + { + return lhs.current == rhs.current; + } + friend bool operator!=(const templated_iterator & lhs, const templated_iterator & rhs) + { + return !(lhs == rhs); + } + + templated_iterator & operator++() + { + do + { + ++current; + } + while(current->is_empty()); + return *this; + } + templated_iterator operator++(int) + { + templated_iterator copy(*this); + ++*this; + return copy; + } + + ValueType & operator*() const + { + return current->value; + } + ValueType * operator->() const + { + return std::addressof(current->value); + } + + operator templated_iterator() const + { + return { current }; + } + }; + using iterator = templated_iterator; + using const_iterator = templated_iterator; + + iterator begin() + { + for (EntryPointer it = entries;; ++it) + { + if (it->has_value()) + return { it }; + } + } + const_iterator begin() const + { + for (EntryPointer it = entries;; ++it) + { + if (it->has_value()) + return { it }; + } + } + const_iterator cbegin() const + { + return begin(); + } + iterator end() + { + return { entries + static_cast(num_slots_minus_one + max_lookups) }; + } + const_iterator end() const + { + return { entries + static_cast(num_slots_minus_one + max_lookups) }; + } + const_iterator cend() const + { + return end(); + } + + iterator find(const FindKey & key) + { + size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + EntryPointer it = entries + ptrdiff_t(index); + for (int8_t distance = 0; it->distance_from_desired >= distance; ++distance, ++it) + { + if (compares_equal(key, it->value)) + return { it }; + } + return end(); + } + const_iterator find(const FindKey & key) const + { + return const_cast(this)->find(key); + } + size_t count(const FindKey & key) const + { + return find(key) == end() ? 0 : 1; + } + std::pair equal_range(const FindKey & key) + { + iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + std::pair equal_range(const FindKey & key) const + { + const_iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + + template + std::pair emplace(Key && key, Args &&... args) + { + size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + EntryPointer current_entry = entries + ptrdiff_t(index); + int8_t distance_from_desired = 0; + for (; current_entry->distance_from_desired >= distance_from_desired; ++current_entry, ++distance_from_desired) + { + if (compares_equal(key, current_entry->value)) + return { { current_entry }, false }; + } + return emplace_new_key(distance_from_desired, current_entry, std::forward(key), std::forward(args)...); + } + + std::pair insert(const value_type & value) + { + return emplace(value); + } + std::pair insert(value_type && value) + { + return emplace(std::move(value)); + } + template + iterator emplace_hint(const_iterator, Args &&... args) + { + return emplace(std::forward(args)...).first; + } + iterator insert(const_iterator, const value_type & value) + { + return emplace(value).first; + } + iterator insert(const_iterator, value_type && value) + { + return emplace(std::move(value)).first; + } + + template + void insert(It begin, It end) + { + for (; begin != end; ++begin) + { + emplace(*begin); + } + } + void insert(std::initializer_list il) + { + insert(il.begin(), il.end()); + } + + void rehash(size_t num_buckets) + { + num_buckets = std::max(num_buckets, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); + if (num_buckets == 0) + { + reset_to_empty_state(); + return; + } + auto new_prime_index = hash_policy.next_size_over(num_buckets); + if (num_buckets == bucket_count()) + return; + int8_t new_max_lookups = compute_max_lookups(num_buckets); + EntryPointer new_buckets(AllocatorTraits::allocate(*this, num_buckets + new_max_lookups)); + EntryPointer special_end_item = new_buckets + static_cast(num_buckets + new_max_lookups - 1); + for (EntryPointer it = new_buckets; it != special_end_item; ++it) + it->distance_from_desired = -1; + special_end_item->distance_from_desired = Entry::special_end_value; + std::swap(entries, new_buckets); + std::swap(num_slots_minus_one, num_buckets); + --num_slots_minus_one; + hash_policy.commit(new_prime_index); + int8_t old_max_lookups = max_lookups; + max_lookups = new_max_lookups; + num_elements = 0; + for (EntryPointer it = new_buckets, end = it + static_cast(num_buckets + old_max_lookups); it != end; ++it) + { + if (it->has_value()) + { + emplace(std::move(it->value)); + it->destroy_value(); + } + } + deallocate_data(new_buckets, num_buckets, old_max_lookups); + } + + void reserve(size_t num_elements) + { + size_t required_buckets = num_buckets_for_reserve(num_elements); + if (required_buckets > bucket_count()) + rehash(required_buckets); + } + + // the return value is a type that can be converted to an iterator + // the reason for doing this is that it's not free to find the + // iterator pointing at the next element. if you care about the + // next iterator, turn the return value into an iterator + convertible_to_iterator erase(const_iterator to_erase) + { + EntryPointer current = to_erase.current; + current->destroy_value(); + --num_elements; + for (EntryPointer next = current + ptrdiff_t(1); !next->is_at_desired_position(); ++current, ++next) + { + current->emplace(next->distance_from_desired - 1, std::move(next->value)); + next->destroy_value(); + } + return { to_erase.current }; + } + + iterator erase(const_iterator begin_it, const_iterator end_it) + { + if (begin_it == end_it) + return { begin_it.current }; + for (EntryPointer it = begin_it.current, end = end_it.current; it != end; ++it) + { + if (it->has_value()) + { + it->destroy_value(); + --num_elements; + } + } + if (end_it == this->end()) + return this->end(); + ptrdiff_t num_to_move = std::min(static_cast(end_it.current->distance_from_desired), end_it.current - begin_it.current); + EntryPointer to_return = end_it.current - num_to_move; + for (EntryPointer it = end_it.current; !it->is_at_desired_position();) + { + EntryPointer target = it - num_to_move; + target->emplace(it->distance_from_desired - num_to_move, std::move(it->value)); + it->destroy_value(); + ++it; + num_to_move = std::min(static_cast(it->distance_from_desired), num_to_move); + } + return { to_return }; + } + + size_t erase(const FindKey & key) + { + auto found = find(key); + if (found == end()) + return 0; + else + { + erase(found); + return 1; + } + } + + void clear() + { + for (EntryPointer it = entries, end = it + static_cast(num_slots_minus_one + max_lookups); it != end; ++it) + { + if (it->has_value()) + it->destroy_value(); + } + num_elements = 0; + } + + void shrink_to_fit() + { + rehash_for_other_container(*this); + } + + void swap(sherwood_v3_table & other) + { + using std::swap; + swap_pointers(other); + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + if (AllocatorTraits::propagate_on_container_swap::value) + swap(static_cast(*this), static_cast(other)); + } + + size_t size() const + { + return num_elements; + } + size_t max_size() const + { + return (AllocatorTraits::max_size(*this)) / sizeof(Entry); + } + size_t bucket_count() const + { + return num_slots_minus_one ? num_slots_minus_one + 1 : 0; + } + size_type max_bucket_count() const + { + return (AllocatorTraits::max_size(*this) - min_lookups) / sizeof(Entry); + } + size_t bucket(const FindKey & key) const + { + return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + } + float load_factor() const + { + size_t buckets = bucket_count(); + if (buckets) + return static_cast(num_elements) / bucket_count(); + else + return 0; + } + void max_load_factor(float value) + { + _max_load_factor = value; + } + float max_load_factor() const + { + return _max_load_factor; + } + + bool empty() const + { + return num_elements == 0; + } + +private: + EntryPointer entries = Entry::empty_default_table(); + size_t num_slots_minus_one = 0; + typename HashPolicySelector::type hash_policy; + int8_t max_lookups = detailv3::min_lookups - 1; + float _max_load_factor = 0.5f; + size_t num_elements = 0; + + static int8_t compute_max_lookups(size_t num_buckets) + { + int8_t desired = detailv3::log2(num_buckets); + return std::max(detailv3::min_lookups, desired); + } + + size_t num_buckets_for_reserve(size_t num_elements) const + { + return static_cast(std::ceil(num_elements / std::min(0.5, static_cast(_max_load_factor)))); + } + void rehash_for_other_container(const sherwood_v3_table & other) + { + rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count())); + } + + void swap_pointers(sherwood_v3_table & other) + { + using std::swap; + swap(hash_policy, other.hash_policy); + swap(entries, other.entries); + swap(num_slots_minus_one, other.num_slots_minus_one); + swap(num_elements, other.num_elements); + swap(max_lookups, other.max_lookups); + swap(_max_load_factor, other._max_load_factor); + } + + template + SKA_NOINLINE(std::pair) emplace_new_key(int8_t distance_from_desired, EntryPointer current_entry, Key && key, Args &&... args) + { + using std::swap; + if (num_slots_minus_one == 0 || distance_from_desired == max_lookups || num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor)) + { + grow(); + return emplace(std::forward(key), std::forward(args)...); + } + else if (current_entry->is_empty()) + { + current_entry->emplace(distance_from_desired, std::forward(key), std::forward(args)...); + ++num_elements; + return { { current_entry }, true }; + } + value_type to_insert(std::forward(key), std::forward(args)...); + swap(distance_from_desired, current_entry->distance_from_desired); + swap(to_insert, current_entry->value); + iterator result = { current_entry }; + for (++distance_from_desired, ++current_entry;; ++current_entry) + { + if (current_entry->is_empty()) + { + current_entry->emplace(distance_from_desired, std::move(to_insert)); + ++num_elements; + return { result, true }; + } + else if (current_entry->distance_from_desired < distance_from_desired) + { + swap(distance_from_desired, current_entry->distance_from_desired); + swap(to_insert, current_entry->value); + ++distance_from_desired; + } + else + { + ++distance_from_desired; + if (distance_from_desired == max_lookups) + { + swap(to_insert, result.current->value); + grow(); + return emplace(std::move(to_insert)); + } + } + } + } + + void grow() + { + rehash(std::max(size_t(4), 2 * bucket_count())); + } + + void deallocate_data(EntryPointer begin, size_t num_slots_minus_one, int8_t max_lookups) + { + if (begin != Entry::empty_default_table()) + { + AllocatorTraits::deallocate(*this, begin, num_slots_minus_one + max_lookups + 1); + } + } + + void reset_to_empty_state() + { + deallocate_data(entries, num_slots_minus_one, max_lookups); + entries = Entry::empty_default_table(); + num_slots_minus_one = 0; + hash_policy.reset(); + max_lookups = detailv3::min_lookups - 1; + } + + template + size_t hash_object(const U & key) + { + return static_cast(*this)(key); + } + template + size_t hash_object(const U & key) const + { + return static_cast(*this)(key); + } + template + bool compares_equal(const L & lhs, const R & rhs) + { + return static_cast(*this)(lhs, rhs); + } + + struct convertible_to_iterator + { + EntryPointer it; + + operator iterator() + { + if (it->has_value()) + return { it }; + else + return ++iterator{it}; + } + operator const_iterator() + { + if (it->has_value()) + return { it }; + else + return ++const_iterator{it}; + } + }; + +}; +} + +struct prime_number_hash_policy +{ + static size_t mod0(size_t) { return 0llu; } + static size_t mod2(size_t hash) { return hash % 2llu; } + static size_t mod3(size_t hash) { return hash % 3llu; } + static size_t mod5(size_t hash) { return hash % 5llu; } + static size_t mod7(size_t hash) { return hash % 7llu; } + static size_t mod11(size_t hash) { return hash % 11llu; } + static size_t mod13(size_t hash) { return hash % 13llu; } + static size_t mod17(size_t hash) { return hash % 17llu; } + static size_t mod23(size_t hash) { return hash % 23llu; } + static size_t mod29(size_t hash) { return hash % 29llu; } + static size_t mod37(size_t hash) { return hash % 37llu; } + static size_t mod47(size_t hash) { return hash % 47llu; } + static size_t mod59(size_t hash) { return hash % 59llu; } + static size_t mod73(size_t hash) { return hash % 73llu; } + static size_t mod97(size_t hash) { return hash % 97llu; } + static size_t mod127(size_t hash) { return hash % 127llu; } + static size_t mod151(size_t hash) { return hash % 151llu; } + static size_t mod197(size_t hash) { return hash % 197llu; } + static size_t mod251(size_t hash) { return hash % 251llu; } + static size_t mod313(size_t hash) { return hash % 313llu; } + static size_t mod397(size_t hash) { return hash % 397llu; } + static size_t mod499(size_t hash) { return hash % 499llu; } + static size_t mod631(size_t hash) { return hash % 631llu; } + static size_t mod797(size_t hash) { return hash % 797llu; } + static size_t mod1009(size_t hash) { return hash % 1009llu; } + static size_t mod1259(size_t hash) { return hash % 1259llu; } + static size_t mod1597(size_t hash) { return hash % 1597llu; } + static size_t mod2011(size_t hash) { return hash % 2011llu; } + static size_t mod2539(size_t hash) { return hash % 2539llu; } + static size_t mod3203(size_t hash) { return hash % 3203llu; } + static size_t mod4027(size_t hash) { return hash % 4027llu; } + static size_t mod5087(size_t hash) { return hash % 5087llu; } + static size_t mod6421(size_t hash) { return hash % 6421llu; } + static size_t mod8089(size_t hash) { return hash % 8089llu; } + static size_t mod10193(size_t hash) { return hash % 10193llu; } + static size_t mod12853(size_t hash) { return hash % 12853llu; } + static size_t mod16193(size_t hash) { return hash % 16193llu; } + static size_t mod20399(size_t hash) { return hash % 20399llu; } + static size_t mod25717(size_t hash) { return hash % 25717llu; } + static size_t mod32401(size_t hash) { return hash % 32401llu; } + static size_t mod40823(size_t hash) { return hash % 40823llu; } + static size_t mod51437(size_t hash) { return hash % 51437llu; } + static size_t mod64811(size_t hash) { return hash % 64811llu; } + static size_t mod81649(size_t hash) { return hash % 81649llu; } + static size_t mod102877(size_t hash) { return hash % 102877llu; } + static size_t mod129607(size_t hash) { return hash % 129607llu; } + static size_t mod163307(size_t hash) { return hash % 163307llu; } + static size_t mod205759(size_t hash) { return hash % 205759llu; } + static size_t mod259229(size_t hash) { return hash % 259229llu; } + static size_t mod326617(size_t hash) { return hash % 326617llu; } + static size_t mod411527(size_t hash) { return hash % 411527llu; } + static size_t mod518509(size_t hash) { return hash % 518509llu; } + static size_t mod653267(size_t hash) { return hash % 653267llu; } + static size_t mod823117(size_t hash) { return hash % 823117llu; } + static size_t mod1037059(size_t hash) { return hash % 1037059llu; } + static size_t mod1306601(size_t hash) { return hash % 1306601llu; } + static size_t mod1646237(size_t hash) { return hash % 1646237llu; } + static size_t mod2074129(size_t hash) { return hash % 2074129llu; } + static size_t mod2613229(size_t hash) { return hash % 2613229llu; } + static size_t mod3292489(size_t hash) { return hash % 3292489llu; } + static size_t mod4148279(size_t hash) { return hash % 4148279llu; } + static size_t mod5226491(size_t hash) { return hash % 5226491llu; } + static size_t mod6584983(size_t hash) { return hash % 6584983llu; } + static size_t mod8296553(size_t hash) { return hash % 8296553llu; } + static size_t mod10453007(size_t hash) { return hash % 10453007llu; } + static size_t mod13169977(size_t hash) { return hash % 13169977llu; } + static size_t mod16593127(size_t hash) { return hash % 16593127llu; } + static size_t mod20906033(size_t hash) { return hash % 20906033llu; } + static size_t mod26339969(size_t hash) { return hash % 26339969llu; } + static size_t mod33186281(size_t hash) { return hash % 33186281llu; } + static size_t mod41812097(size_t hash) { return hash % 41812097llu; } + static size_t mod52679969(size_t hash) { return hash % 52679969llu; } + static size_t mod66372617(size_t hash) { return hash % 66372617llu; } + static size_t mod83624237(size_t hash) { return hash % 83624237llu; } + static size_t mod105359939(size_t hash) { return hash % 105359939llu; } + static size_t mod132745199(size_t hash) { return hash % 132745199llu; } + static size_t mod167248483(size_t hash) { return hash % 167248483llu; } + static size_t mod210719881(size_t hash) { return hash % 210719881llu; } + static size_t mod265490441(size_t hash) { return hash % 265490441llu; } + static size_t mod334496971(size_t hash) { return hash % 334496971llu; } + static size_t mod421439783(size_t hash) { return hash % 421439783llu; } + static size_t mod530980861(size_t hash) { return hash % 530980861llu; } + static size_t mod668993977(size_t hash) { return hash % 668993977llu; } + static size_t mod842879579(size_t hash) { return hash % 842879579llu; } + static size_t mod1061961721(size_t hash) { return hash % 1061961721llu; } + static size_t mod1337987929(size_t hash) { return hash % 1337987929llu; } + static size_t mod1685759167(size_t hash) { return hash % 1685759167llu; } + static size_t mod2123923447(size_t hash) { return hash % 2123923447llu; } + static size_t mod2675975881(size_t hash) { return hash % 2675975881llu; } + static size_t mod3371518343(size_t hash) { return hash % 3371518343llu; } + static size_t mod4247846927(size_t hash) { return hash % 4247846927llu; } + static size_t mod5351951779(size_t hash) { return hash % 5351951779llu; } + static size_t mod6743036717(size_t hash) { return hash % 6743036717llu; } + static size_t mod8495693897(size_t hash) { return hash % 8495693897llu; } + static size_t mod10703903591(size_t hash) { return hash % 10703903591llu; } + static size_t mod13486073473(size_t hash) { return hash % 13486073473llu; } + static size_t mod16991387857(size_t hash) { return hash % 16991387857llu; } + static size_t mod21407807219(size_t hash) { return hash % 21407807219llu; } + static size_t mod26972146961(size_t hash) { return hash % 26972146961llu; } + static size_t mod33982775741(size_t hash) { return hash % 33982775741llu; } + static size_t mod42815614441(size_t hash) { return hash % 42815614441llu; } + static size_t mod53944293929(size_t hash) { return hash % 53944293929llu; } + static size_t mod67965551447(size_t hash) { return hash % 67965551447llu; } + static size_t mod85631228929(size_t hash) { return hash % 85631228929llu; } + static size_t mod107888587883(size_t hash) { return hash % 107888587883llu; } + static size_t mod135931102921(size_t hash) { return hash % 135931102921llu; } + static size_t mod171262457903(size_t hash) { return hash % 171262457903llu; } + static size_t mod215777175787(size_t hash) { return hash % 215777175787llu; } + static size_t mod271862205833(size_t hash) { return hash % 271862205833llu; } + static size_t mod342524915839(size_t hash) { return hash % 342524915839llu; } + static size_t mod431554351609(size_t hash) { return hash % 431554351609llu; } + static size_t mod543724411781(size_t hash) { return hash % 543724411781llu; } + static size_t mod685049831731(size_t hash) { return hash % 685049831731llu; } + static size_t mod863108703229(size_t hash) { return hash % 863108703229llu; } + static size_t mod1087448823553(size_t hash) { return hash % 1087448823553llu; } + static size_t mod1370099663459(size_t hash) { return hash % 1370099663459llu; } + static size_t mod1726217406467(size_t hash) { return hash % 1726217406467llu; } + static size_t mod2174897647073(size_t hash) { return hash % 2174897647073llu; } + static size_t mod2740199326961(size_t hash) { return hash % 2740199326961llu; } + static size_t mod3452434812973(size_t hash) { return hash % 3452434812973llu; } + static size_t mod4349795294267(size_t hash) { return hash % 4349795294267llu; } + static size_t mod5480398654009(size_t hash) { return hash % 5480398654009llu; } + static size_t mod6904869625999(size_t hash) { return hash % 6904869625999llu; } + static size_t mod8699590588571(size_t hash) { return hash % 8699590588571llu; } + static size_t mod10960797308051(size_t hash) { return hash % 10960797308051llu; } + static size_t mod13809739252051(size_t hash) { return hash % 13809739252051llu; } + static size_t mod17399181177241(size_t hash) { return hash % 17399181177241llu; } + static size_t mod21921594616111(size_t hash) { return hash % 21921594616111llu; } + static size_t mod27619478504183(size_t hash) { return hash % 27619478504183llu; } + static size_t mod34798362354533(size_t hash) { return hash % 34798362354533llu; } + static size_t mod43843189232363(size_t hash) { return hash % 43843189232363llu; } + static size_t mod55238957008387(size_t hash) { return hash % 55238957008387llu; } + static size_t mod69596724709081(size_t hash) { return hash % 69596724709081llu; } + static size_t mod87686378464759(size_t hash) { return hash % 87686378464759llu; } + static size_t mod110477914016779(size_t hash) { return hash % 110477914016779llu; } + static size_t mod139193449418173(size_t hash) { return hash % 139193449418173llu; } + static size_t mod175372756929481(size_t hash) { return hash % 175372756929481llu; } + static size_t mod220955828033581(size_t hash) { return hash % 220955828033581llu; } + static size_t mod278386898836457(size_t hash) { return hash % 278386898836457llu; } + static size_t mod350745513859007(size_t hash) { return hash % 350745513859007llu; } + static size_t mod441911656067171(size_t hash) { return hash % 441911656067171llu; } + static size_t mod556773797672909(size_t hash) { return hash % 556773797672909llu; } + static size_t mod701491027718027(size_t hash) { return hash % 701491027718027llu; } + static size_t mod883823312134381(size_t hash) { return hash % 883823312134381llu; } + static size_t mod1113547595345903(size_t hash) { return hash % 1113547595345903llu; } + static size_t mod1402982055436147(size_t hash) { return hash % 1402982055436147llu; } + static size_t mod1767646624268779(size_t hash) { return hash % 1767646624268779llu; } + static size_t mod2227095190691797(size_t hash) { return hash % 2227095190691797llu; } + static size_t mod2805964110872297(size_t hash) { return hash % 2805964110872297llu; } + static size_t mod3535293248537579(size_t hash) { return hash % 3535293248537579llu; } + static size_t mod4454190381383713(size_t hash) { return hash % 4454190381383713llu; } + static size_t mod5611928221744609(size_t hash) { return hash % 5611928221744609llu; } + static size_t mod7070586497075177(size_t hash) { return hash % 7070586497075177llu; } + static size_t mod8908380762767489(size_t hash) { return hash % 8908380762767489llu; } + static size_t mod11223856443489329(size_t hash) { return hash % 11223856443489329llu; } + static size_t mod14141172994150357(size_t hash) { return hash % 14141172994150357llu; } + static size_t mod17816761525534927(size_t hash) { return hash % 17816761525534927llu; } + static size_t mod22447712886978529(size_t hash) { return hash % 22447712886978529llu; } + static size_t mod28282345988300791(size_t hash) { return hash % 28282345988300791llu; } + static size_t mod35633523051069991(size_t hash) { return hash % 35633523051069991llu; } + static size_t mod44895425773957261(size_t hash) { return hash % 44895425773957261llu; } + static size_t mod56564691976601587(size_t hash) { return hash % 56564691976601587llu; } + static size_t mod71267046102139967(size_t hash) { return hash % 71267046102139967llu; } + static size_t mod89790851547914507(size_t hash) { return hash % 89790851547914507llu; } + static size_t mod113129383953203213(size_t hash) { return hash % 113129383953203213llu; } + static size_t mod142534092204280003(size_t hash) { return hash % 142534092204280003llu; } + static size_t mod179581703095829107(size_t hash) { return hash % 179581703095829107llu; } + static size_t mod226258767906406483(size_t hash) { return hash % 226258767906406483llu; } + static size_t mod285068184408560057(size_t hash) { return hash % 285068184408560057llu; } + static size_t mod359163406191658253(size_t hash) { return hash % 359163406191658253llu; } + static size_t mod452517535812813007(size_t hash) { return hash % 452517535812813007llu; } + static size_t mod570136368817120201(size_t hash) { return hash % 570136368817120201llu; } + static size_t mod718326812383316683(size_t hash) { return hash % 718326812383316683llu; } + static size_t mod905035071625626043(size_t hash) { return hash % 905035071625626043llu; } + static size_t mod1140272737634240411(size_t hash) { return hash % 1140272737634240411llu; } + static size_t mod1436653624766633509(size_t hash) { return hash % 1436653624766633509llu; } + static size_t mod1810070143251252131(size_t hash) { return hash % 1810070143251252131llu; } + static size_t mod2280545475268481167(size_t hash) { return hash % 2280545475268481167llu; } + static size_t mod2873307249533267101(size_t hash) { return hash % 2873307249533267101llu; } + static size_t mod3620140286502504283(size_t hash) { return hash % 3620140286502504283llu; } + static size_t mod4561090950536962147(size_t hash) { return hash % 4561090950536962147llu; } + static size_t mod5746614499066534157(size_t hash) { return hash % 5746614499066534157llu; } + static size_t mod7240280573005008577(size_t hash) { return hash % 7240280573005008577llu; } + static size_t mod9122181901073924329(size_t hash) { return hash % 9122181901073924329llu; } + static size_t mod11493228998133068689(size_t hash) { return hash % 11493228998133068689llu; } + static size_t mod14480561146010017169(size_t hash) { return hash % 14480561146010017169llu; } + static size_t mod18446744073709551557(size_t hash) { return hash % 18446744073709551557llu; } + + using mod_function = size_t (*)(size_t); + + mod_function next_size_over(size_t & size) const + { + // prime numbers generated by the following method: + // 1. start with a prime p = 2 + // 2. go to wolfram alpha and get p = NextPrime(2 * p) + // 3. repeat 2. until you overflow 64 bits + // you now have large gaps which you would hit if somebody called reserve() with an unlucky number. + // 4. to fill the gaps for every prime p go to wolfram alpha and get ClosestPrime(p * 2^(1/3)) and ClosestPrime(p * 2^(2/3)) and put those in the gaps + // 5. get PrevPrime(2^64) and put it at the end + static constexpr const size_t prime_list[] = + { + 2llu, 3llu, 5llu, 7llu, 11llu, 13llu, 17llu, 23llu, 29llu, 37llu, 47llu, + 59llu, 73llu, 97llu, 127llu, 151llu, 197llu, 251llu, 313llu, 397llu, + 499llu, 631llu, 797llu, 1009llu, 1259llu, 1597llu, 2011llu, 2539llu, + 3203llu, 4027llu, 5087llu, 6421llu, 8089llu, 10193llu, 12853llu, 16193llu, + 20399llu, 25717llu, 32401llu, 40823llu, 51437llu, 64811llu, 81649llu, + 102877llu, 129607llu, 163307llu, 205759llu, 259229llu, 326617llu, + 411527llu, 518509llu, 653267llu, 823117llu, 1037059llu, 1306601llu, + 1646237llu, 2074129llu, 2613229llu, 3292489llu, 4148279llu, 5226491llu, + 6584983llu, 8296553llu, 10453007llu, 13169977llu, 16593127llu, 20906033llu, + 26339969llu, 33186281llu, 41812097llu, 52679969llu, 66372617llu, + 83624237llu, 105359939llu, 132745199llu, 167248483llu, 210719881llu, + 265490441llu, 334496971llu, 421439783llu, 530980861llu, 668993977llu, + 842879579llu, 1061961721llu, 1337987929llu, 1685759167llu, 2123923447llu, + 2675975881llu, 3371518343llu, 4247846927llu, 5351951779llu, 6743036717llu, + 8495693897llu, 10703903591llu, 13486073473llu, 16991387857llu, + 21407807219llu, 26972146961llu, 33982775741llu, 42815614441llu, + 53944293929llu, 67965551447llu, 85631228929llu, 107888587883llu, + 135931102921llu, 171262457903llu, 215777175787llu, 271862205833llu, + 342524915839llu, 431554351609llu, 543724411781llu, 685049831731llu, + 863108703229llu, 1087448823553llu, 1370099663459llu, 1726217406467llu, + 2174897647073llu, 2740199326961llu, 3452434812973llu, 4349795294267llu, + 5480398654009llu, 6904869625999llu, 8699590588571llu, 10960797308051llu, + 13809739252051llu, 17399181177241llu, 21921594616111llu, 27619478504183llu, + 34798362354533llu, 43843189232363llu, 55238957008387llu, 69596724709081llu, + 87686378464759llu, 110477914016779llu, 139193449418173llu, + 175372756929481llu, 220955828033581llu, 278386898836457llu, + 350745513859007llu, 441911656067171llu, 556773797672909llu, + 701491027718027llu, 883823312134381llu, 1113547595345903llu, + 1402982055436147llu, 1767646624268779llu, 2227095190691797llu, + 2805964110872297llu, 3535293248537579llu, 4454190381383713llu, + 5611928221744609llu, 7070586497075177llu, 8908380762767489llu, + 11223856443489329llu, 14141172994150357llu, 17816761525534927llu, + 22447712886978529llu, 28282345988300791llu, 35633523051069991llu, + 44895425773957261llu, 56564691976601587llu, 71267046102139967llu, + 89790851547914507llu, 113129383953203213llu, 142534092204280003llu, + 179581703095829107llu, 226258767906406483llu, 285068184408560057llu, + 359163406191658253llu, 452517535812813007llu, 570136368817120201llu, + 718326812383316683llu, 905035071625626043llu, 1140272737634240411llu, + 1436653624766633509llu, 1810070143251252131llu, 2280545475268481167llu, + 2873307249533267101llu, 3620140286502504283llu, 4561090950536962147llu, + 5746614499066534157llu, 7240280573005008577llu, 9122181901073924329llu, + 11493228998133068689llu, 14480561146010017169llu, 18446744073709551557llu + }; + static constexpr size_t (* const mod_functions[])(size_t) = + { + &mod0, &mod2, &mod3, &mod5, &mod7, &mod11, &mod13, &mod17, &mod23, &mod29, &mod37, + &mod47, &mod59, &mod73, &mod97, &mod127, &mod151, &mod197, &mod251, &mod313, &mod397, + &mod499, &mod631, &mod797, &mod1009, &mod1259, &mod1597, &mod2011, &mod2539, &mod3203, + &mod4027, &mod5087, &mod6421, &mod8089, &mod10193, &mod12853, &mod16193, &mod20399, + &mod25717, &mod32401, &mod40823, &mod51437, &mod64811, &mod81649, &mod102877, + &mod129607, &mod163307, &mod205759, &mod259229, &mod326617, &mod411527, &mod518509, + &mod653267, &mod823117, &mod1037059, &mod1306601, &mod1646237, &mod2074129, + &mod2613229, &mod3292489, &mod4148279, &mod5226491, &mod6584983, &mod8296553, + &mod10453007, &mod13169977, &mod16593127, &mod20906033, &mod26339969, &mod33186281, + &mod41812097, &mod52679969, &mod66372617, &mod83624237, &mod105359939, &mod132745199, + &mod167248483, &mod210719881, &mod265490441, &mod334496971, &mod421439783, + &mod530980861, &mod668993977, &mod842879579, &mod1061961721, &mod1337987929, + &mod1685759167, &mod2123923447, &mod2675975881, &mod3371518343, &mod4247846927, + &mod5351951779, &mod6743036717, &mod8495693897, &mod10703903591, &mod13486073473, + &mod16991387857, &mod21407807219, &mod26972146961, &mod33982775741, &mod42815614441, + &mod53944293929, &mod67965551447, &mod85631228929, &mod107888587883, &mod135931102921, + &mod171262457903, &mod215777175787, &mod271862205833, &mod342524915839, + &mod431554351609, &mod543724411781, &mod685049831731, &mod863108703229, + &mod1087448823553, &mod1370099663459, &mod1726217406467, &mod2174897647073, + &mod2740199326961, &mod3452434812973, &mod4349795294267, &mod5480398654009, + &mod6904869625999, &mod8699590588571, &mod10960797308051, &mod13809739252051, + &mod17399181177241, &mod21921594616111, &mod27619478504183, &mod34798362354533, + &mod43843189232363, &mod55238957008387, &mod69596724709081, &mod87686378464759, + &mod110477914016779, &mod139193449418173, &mod175372756929481, &mod220955828033581, + &mod278386898836457, &mod350745513859007, &mod441911656067171, &mod556773797672909, + &mod701491027718027, &mod883823312134381, &mod1113547595345903, &mod1402982055436147, + &mod1767646624268779, &mod2227095190691797, &mod2805964110872297, &mod3535293248537579, + &mod4454190381383713, &mod5611928221744609, &mod7070586497075177, &mod8908380762767489, + &mod11223856443489329, &mod14141172994150357, &mod17816761525534927, + &mod22447712886978529, &mod28282345988300791, &mod35633523051069991, + &mod44895425773957261, &mod56564691976601587, &mod71267046102139967, + &mod89790851547914507, &mod113129383953203213, &mod142534092204280003, + &mod179581703095829107, &mod226258767906406483, &mod285068184408560057, + &mod359163406191658253, &mod452517535812813007, &mod570136368817120201, + &mod718326812383316683, &mod905035071625626043, &mod1140272737634240411, + &mod1436653624766633509, &mod1810070143251252131, &mod2280545475268481167, + &mod2873307249533267101, &mod3620140286502504283, &mod4561090950536962147, + &mod5746614499066534157, &mod7240280573005008577, &mod9122181901073924329, + &mod11493228998133068689, &mod14480561146010017169, &mod18446744073709551557 + }; + const size_t * found = std::lower_bound(std::begin(prime_list), std::end(prime_list) - 1, size); + size = *found; + return mod_functions[1 + found - prime_list]; + } + void commit(mod_function new_mod_function) + { + current_mod_function = new_mod_function; + } + void reset() + { + current_mod_function = &mod0; + } + + size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const + { + return current_mod_function(hash); + } + size_t keep_in_range(size_t index, size_t num_slots_minus_one) const + { + return index > num_slots_minus_one ? current_mod_function(index) : index; + } + +private: + mod_function current_mod_function = &mod0; +}; + +struct power_of_two_hash_policy +{ + size_t index_for_hash(size_t hash, size_t num_slots_minus_one) const + { + return hash & num_slots_minus_one; + } + size_t keep_in_range(size_t index, size_t num_slots_minus_one) const + { + return index_for_hash(index, num_slots_minus_one); + } + int8_t next_size_over(size_t & size) const + { + size = detailv3::next_power_of_two(size); + return 0; + } + void commit(int8_t) + { + } + void reset() + { + } + +}; + +struct fibonacci_hash_policy +{ + size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const + { + return (11400714819323198485ull * hash) >> shift; + } + size_t keep_in_range(size_t index, size_t num_slots_minus_one) const + { + return index & num_slots_minus_one; + } + + int8_t next_size_over(size_t & size) const + { + size = std::max(size_t(2), detailv3::next_power_of_two(size)); + return 64 - detailv3::log2(size); + } + void commit(int8_t shift) + { + this->shift = shift; + } + void reset() + { + shift = 63; + } + +private: + int8_t shift = 63; +}; + +template, typename E = std::equal_to, typename A = std::allocator > > +class flat_hash_map + : public detailv3::sherwood_v3_table + < + std::pair, + K, + H, + detailv3::KeyOrValueHasher, H>, + E, + detailv3::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc>> + > +{ + using Table = detailv3::sherwood_v3_table + < + std::pair, + K, + H, + detailv3::KeyOrValueHasher, H>, + E, + detailv3::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc>> + >; +public: + + using key_type = K; + using mapped_type = V; + + using Table::Table; + flat_hash_map() + { + } + + inline V & operator[](const K & key) + { + return emplace(key, convertible_to_value()).first->second; + } + inline V & operator[](K && key) + { + return emplace(std::move(key), convertible_to_value()).first->second; + } + V & at(const K & key) + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + const V & at(const K & key) const + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + + using Table::emplace; + std::pair emplace() + { + return emplace(key_type(), convertible_to_value()); + } + template + std::pair insert_or_assign(const key_type & key, M && m) + { + auto emplace_result = emplace(key, std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + std::pair insert_or_assign(key_type && key, M && m) + { + auto emplace_result = emplace(std::move(key), std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type & key, M && m) + { + return insert_or_assign(key, std::forward(m)).first; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type && key, M && m) + { + return insert_or_assign(std::move(key), std::forward(m)).first; + } + + friend bool operator==(const flat_hash_map & lhs, const flat_hash_map & rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const typename Table::value_type & value : lhs) + { + auto found = rhs.find(value.first); + if (found == rhs.end()) + return false; + else if (value.second != found->second) + return false; + } + return true; + } + friend bool operator!=(const flat_hash_map & lhs, const flat_hash_map & rhs) + { + return !(lhs == rhs); + } + +private: + struct convertible_to_value + { + operator V() const + { + return V(); + } + }; +}; + +template, typename E = std::equal_to, typename A = std::allocator > +class flat_hash_set + : public detailv3::sherwood_v3_table + < + T, + T, + H, + detailv3::functor_storage, + E, + detailv3::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc> + > +{ + using Table = detailv3::sherwood_v3_table + < + T, + T, + H, + detailv3::functor_storage, + E, + detailv3::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc> + >; +public: + + using key_type = T; + + using Table::Table; + flat_hash_set() + { + } + + template + std::pair emplace(Args &&... args) + { + return Table::emplace(T(std::forward(args)...)); + } + std::pair emplace(const key_type & arg) + { + return Table::emplace(arg); + } + std::pair emplace(key_type & arg) + { + return Table::emplace(arg); + } + std::pair emplace(const key_type && arg) + { + return Table::emplace(std::move(arg)); + } + std::pair emplace(key_type && arg) + { + return Table::emplace(std::move(arg)); + } + + friend bool operator==(const flat_hash_set & lhs, const flat_hash_set & rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const T & value : lhs) + { + if (rhs.find(value) == rhs.end()) + return false; + } + return true; + } + friend bool operator!=(const flat_hash_set & lhs, const flat_hash_set & rhs) + { + return !(lhs == rhs); + } +}; + + +template +struct power_of_two_std_hash : std::hash +{ + typedef ska::power_of_two_hash_policy hash_policy; +}; + +} // end namespace ska diff --git a/luprex/cpp/core/globaldb.cpp b/luprex/cpp/core/globaldb.cpp new file mode 100644 index 00000000..f8fc1d01 --- /dev/null +++ b/luprex/cpp/core/globaldb.cpp @@ -0,0 +1,3 @@ +#include "luastack.hpp" +#include "globaldb.hpp" + diff --git a/luprex/cpp/core/globaldb.hpp b/luprex/cpp/core/globaldb.hpp new file mode 100644 index 00000000..c04321ef --- /dev/null +++ b/luprex/cpp/core/globaldb.hpp @@ -0,0 +1,14 @@ +//////////////////////////////////////////////////////////// +// +// GLOBALDB +// +//////////////////////////////////////////////////////////// + +#ifndef GLOBALDB_HPP +#define GLOBALDB_HPP + + + +#endif // GLOBALDB_HPP + + diff --git a/luprex/cpp/core/http.cpp b/luprex/cpp/core/http.cpp new file mode 100644 index 00000000..35c6eb83 --- /dev/null +++ b/luprex/cpp/core/http.cpp @@ -0,0 +1,1955 @@ +// +// Things to worry about: +// Expect: 100-Continue + + + +#include "http.hpp" +#include "wrap-sstream.hpp" +#include "wrap-string.hpp" +#include "util.hpp" +#include "luastack.hpp" +#include "json.hpp" + +#include + +using string_view = std::string_view; + +struct { int code; const char *name; } status_codes[] = { + // Commonly-used codes duplicated at front of list. + { 200, "OK" }, + { 201, "Created" }, + { 404, "Not Found" }, + { 400, "Bad Request" }, + { 500, "Internal Server Error" }, + + // All valid codes, including the ones above. + { 101, "Switching Protocols" }, + { 102, "Processing" }, + { 103, "Early Hints" }, + + { 200, "OK" }, + { 201, "Created" }, + { 202, "Accepted" }, + { 203, "Non-Authoritative Information" }, + { 204, "No Content" }, + { 205, "Reset Content" }, + { 206, "Partial Content" }, + { 207, "Multi-Status" }, + { 208, "Already Reported" }, + + { 300, "Multiple Choices" }, + { 301, "Moved Permanently" }, + { 302, "Found" }, + { 303, "See Other" }, + { 304, "Not Modified" }, + { 305, "Use Proxy" }, + { 306, "Switch Proxy" }, + { 307, "Temporary Redirect" }, + { 308, "Permanent Redirect" }, + + { 400, "Bad Request" }, + { 401, "Unauthorized" }, + { 402, "Payment Required" }, + { 403, "Forbidden" }, + { 404, "Not Found" }, + { 405, "Method Not Allowed" }, + { 406, "Not Acceptable" }, + { 407, "Proxy Authentication Required" }, + { 408, "Request Timeout" }, + { 409, "Conflict" }, + { 410, "Gone" }, + { 411, "Length Required" }, + { 412, "Precondition Failed" }, + { 413, "Payload Too Large" }, + { 414, "URI Too Long" }, + { 415, "Unsupported Media Type" }, + { 416, "Range Not Satisfiable" }, + { 417, "Expectation Failed" }, + { 418, "I'm a teapot" }, + { 421, "Misdirected Request" }, + { 422, "Unprocessable Entity" }, + { 423, "Locked" }, + { 424, "Failed Dependency" }, + { 425, "Too Early" }, + { 426, "Upgrade Required" }, + { 428, "Precondition Required" }, + { 429, "Too Many Requests" }, + { 431, "Request Header Fields Too Large" }, + { 451, "Unavailable For Legal Reasons" }, + + { 500, "Internal Server Error" }, + { 501, "Not Implemented" }, + { 502, "Bad Gateway" }, + { 503, "Service Unavailable" }, + { 504, "Gateway Timeout" }, + { 505, "HTTP Version Not Supported" }, + { 506, "Variant Also Negotiates" }, + { 507, "Insufficient Storage" }, + { 508, "Loop Detected" }, + { 510, "Not Extended" }, + { 511, "Network Authentication Required" }, + + { 0, "" }, +}; + +static const char *status_code_to_string(int code) { + for (int i = 0; status_codes[i].code != 0; i++) { + if (status_codes[i].code == code) { + return status_codes[i].name; + } + } + switch (code / 100) { + case 1: return "Unknown Informational Status"; + case 2: return "Unknown Successful Status"; + case 3: return "Unknown Redirect"; + case 4: return "Unknown Client Error"; + case 5: return "Unknown Server Error"; + default: return "Unknown Error"; + } +} + +static int status_code_from_string(std::string_view s) { + for (int i = 0; status_codes[i].code != 0; i++) { + if (sv::case_insensitive_eq(status_codes[i].name, s)) { + return status_codes[i].code; + } + } + return 0; +} + +// Our client always sends HTTP/1.1 queries, but it +// accepts HTTP/1.0 responses. Our server accepts both protocols. +static bool is_supported_protocol(string_view protocol) { + return (protocol == "HTTP/1.0") || (protocol == "HTTP/1.1"); +} + +// Our client can make GET, HEAD, and POST requests. +static bool is_supported_client_method(string_view method) { + return ((method == "GET") || (method == "HEAD") || (method == "POST")); +} + +// Our server is only allowed to return certain status codes. +static bool is_supported_server_status(int status) { + if (status == 200) return true; + if ((status >= 400) && (status <= 599)) return true; + return false; +} + +bool contains_newline(std::string_view s) { + if (s.find('\n') != std::string_view::npos) return true; + return false; +} + +bool words_separated_by_dashes(string_view v) { + while (true) { + string_view word = sv::read_ascii_identifier(v); + if (word.empty()) return false; + if (v.empty()) return true; + char c = v.front(); + if (c != '-') return false; + v.remove_prefix(1); + } +} + +// This doesn't check whether the mime type is actually +// registered, obviously. It only checks that it's in +// the desired notation. +bool valid_mime_type(string_view method) { + string_view part1 = sv::read_ascii_identifier(method); + if (part1.empty()) return false; + if (sv::zfront(method) != '/') return false; + method.remove_prefix(1); + while (true) { + string_view word = sv::read_ascii_identifier(method); + if (word.empty()) return false; + if (method.empty()) return true; + char c = method.front(); + if ((c != '-') && (c != '.') && (c != '+')) return false; + method.remove_prefix(1); + } +} + +// Technically, this is a true, correct URL encode routine. +static eng::string url_encode_param(string_view value) { + eng::ostringstream result; + const char *hexdigits = "0123456789ABCDEF"; + for (int i = 0; i < int(value.size()); i++) { + char c = value[i]; + + if (sv::ascii_isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + result << c; + } else if (c == ' ') { + result << '+'; + } else { + result << '%' << hexdigits[c>>4] << hexdigits[c&15]; + } + } + return result.str(); +} + +// This URL encode routine leaves slashes intact. That's not +// technically correct, but it's really what you want for paths. +static eng::string url_encode_path(string_view value) { + eng::ostringstream result; + const char *hexdigits = "0123456789ABCDEF"; + for (int i = 0; i < int(value.size()); i++) { + char c = value[i]; + + if (sv::ascii_isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~' || c == '/') { + result << c; + } else if (c == ' ') { + result << '+'; + } else { + result << '%' << hexdigits[c>>4] << hexdigits[c&15]; + } + } + return result.str(); +} + +static eng::string url_decode(string_view eurl) { + eng::ostringstream result; + int i = 0; + int len = eurl.size(); + while (i < len) { + char c = eurl[i]; + if (c == '+') { + result << ' '; + i += 1; + } else if ((c == '%') && (i + 2 < len)) { + std::string_view code = eurl.substr(i + 1, 2); + uint64_t value = sv::to_hex64(code); + if (value > 255) { + result << '?'; + } else { + result << char(value); + } + i += 3; + } else { + result << c; + i += 1; + } + } + return result.str(); +} + +static void send_encoded_path(std::string_view path, const UrlParameters ¶ms, StreamBuffer *sb) { + sb->write_bytes(url_encode_path(path)); + bool first_param = true; + for (const auto &pair : params) { + sb->write_char(first_param ? '?' : '&'); + sb->write_bytes(url_encode_param(pair.first)); + sb->write_char('='); + sb->write_bytes(url_encode_param(pair.second)); + first_param = false; + } +} + +static void send_host_and_port(std::string_view host, int port, StreamBuffer *sb) { + sb->write_bytes(host); + if (port != 0) { + sb->write_bytes(util::ss(":", port)); + } +} + +static void send_protocol(bool http11, StreamBuffer *sb) { + if (http11) { + sb->write_bytes("HTTP/1.1"); + } else { + sb->write_bytes("HTTP/1.0"); + } +} + +static void send_content_length_header(bool http11, int size, StreamBuffer *sb) { + if (http11) { + sb->write_bytes(util::ss("Content-length: ", size, "\r\n")); + } +} + +static void send_content_type_header(bool http11, const eng::string &mime, StreamBuffer *sb) { + sb->write_bytes("Content-type: "); + sb->write_bytes(mime); + if (http11) { + if (sv::has_prefix(mime, "text/")) { + sb->write_bytes(" ; charset=utf-8"); + } + } + sb->write_bytes("\r\n"); +} + +static void send_cache_control_header(bool http11, int max_age, StreamBuffer *sb) { + if (http11) { + if (max_age == 0) { + sb->write_bytes("Cache-control: no-cache\r\n"); + } else { + sb->write_bytes(util::ss("Cache-control: max-age=", max_age, "\r\n")); + } + } else { + sb->write_bytes("Pragma: no-cache\r\n"); + } +} + +static void send_connection_header(bool http11, bool keep_alive, StreamBuffer *sb) { + if (http11) { + if (keep_alive) { + sb->write_bytes("Connection: keep-alive\r\n"); + } else { + sb->write_bytes("Connection: close\r\n"); + } + } +} + +static void send_error_response(bool http11, int code, eng::string extrainfo, StreamBuffer *sb) { + send_protocol(http11, sb); + eng::string errstr = status_code_to_string(code); + sb->write_bytes(util::ss(" ", code, " ", errstr)); + if ((!extrainfo.empty()) && (!contains_newline(extrainfo))) { + sb->write_bytes(": "); + sb->write_bytes(extrainfo); + } + sb->write_bytes("\r\n"); + sb->write_bytes("Content-Type: text/plain\r\n"); + sb->write_bytes("\r\n"); + sb->write_bytes(errstr); + if (!extrainfo.empty()) { + sb->write_bytes(": "); + sb->write_bytes(extrainfo); + } +} + +// HTTP 1.0: An entity body is included with a request message only when the request method calls for one. +// The presence of an entity body in a request is signaled by the inclusion of a Content-Length header field +// in the request message headers. HTTP/1.0 requests containing an entity body must include a valid +// Content-Length header field. +// +// HTTP 1.1: The presence of a message-body in a request is signaled by the inclusion of a Content-Length or +// Transfer-Encoding header field in the request's message-headers +// +static bool request_contains_content(int content_length, std::string_view transfer_encoding) { + return (content_length >= 0) || (!transfer_encoding.empty()); +} + +// HTTP 1.0: For response messages, whether or not an entity body is included with a message is dependent on +// both the request method and the response code. All responses to the HEAD request method must not include a +// body, even though the presence of entity header fields may lead one to believe they do. All 1xx (informational), +// 204 (no content), and 304 (not modified) responses must not include a body. All other responses must include an +// entity body or a Content-Length header field defined with a value of zero (0). +// +// HTTP 1.1: For response messages, whether or not a message-body is included with a message is dependent on both +// the request method and the response status code (section 6.1.1). All responses to the HEAD request method MUST +// NOT include a message-body, even though the presence of entity- header fields might lead one to believe they +// do. All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST NOT include a message-body. +// All other responses do include a message-body, although it MAY be of zero length. +// +static bool response_contains_content(std::string_view method, int status) { + if (method == "HEAD") return false; + if ((status >= 100) && (status <= 199)) return false; + if (status == 204) return false; + if (status == 304) return false; + return true; +} + +// In a properly-formed url, the hostname and path are url encoded. +// This parser expects an encoded URL. +struct ParsedURL { +public: + bool valid; + eng::string proto; + eng::string host; + int port; + eng::string path; + UrlParameters params; + +public: + void clear() { + valid = false; + proto.clear(); + host.clear(); + port = 0; + path.clear(); + params.clear(); + } + + eng::string str() { + StreamBuffer sb; + sb.write_bytes(proto); + sb.write_bytes("://"); + send_host_and_port(host, port, &sb); + send_encoded_path(path, params, &sb); + return eng::string(sb.view()); + } + + ParsedURL(std::string_view url) { + clear(); + + if (!sv::has_prefix(url, "/")) { + proto = util::ascii_tolower(sv::read_to_sep(url, ':')); + if (!sv::has_prefix(url, "//")) { clear(); return; } + url.remove_prefix(2); + if (!words_separated_by_dashes(proto)) { clear(); return; } + + // Extract the host and port as a single string. + string_view turl = url; + string_view hostport = sv::read_to_sep(turl, '/'); + url.remove_prefix(hostport.size()); + + // Split the host and port from each other and parse them. + host = util::ascii_tolower(sv::read_to_sep(hostport, ':')); + if (host.empty()) { clear(); return; } + if (!hostport.empty()) { + int64_t iport = sv::to_int64(hostport); + if ((iport < 1) || (iport > 65535)) { + clear(); return; + } + port = iport; + } + } else { + // Stick in some defaults for unspecified fields. + host = "host"; + proto = "https"; + } + + // Split off the path. + path = url_decode(sv::read_to_sep(url, '?')); + if (path.empty()) { + path = "/"; + } + + // Process url parameters. + while (!sv::isnull(url)) { + std::string_view keyval = sv::read_to_sep(url, '&'); + if (keyval.empty()) { clear(); return; } + std::string_view key = sv::read_to_sep(keyval, '='); + if (key.empty()) { clear(); return; } + if (sv::isnull(keyval)) { clear(); return; } + eng::string dkey = url_decode(key); + eng::string dval = url_decode(keyval); + params[dkey] = dval; + } + + // If we made it here, we have a valid URL + valid = true; + } +}; + +void HttpClientRequest::check_fail(string_view s) { + if (check_fail_.empty()) { + check_fail_ = s; + } +} + +void HttpClientRequest::send_internal(StreamBuffer *sb, bool debug_string) const { + // If there's an error in the request, handle it. In debug string mode, + // we just put the error into the output. In production mode, we assert + // fail. + eng::string error = check(); + if (debug_string) { + if (!error.empty()) { + sb->write_bytes(error); + return; + } + } else { + assert(error.empty()); + } + + // Send the command. + sb->write_bytes(method_); + sb->write_char(' '); + send_encoded_path(path_, params_, sb); + sb->write_bytes(" HTTP/1.1"); + sb->write_bytes("\r\n"); + + // Send the host header. + sb->write_bytes("Host: "); + send_host_and_port(host_, port_, sb); + sb->write_bytes("\r\n"); + + // The empty accept-encoding header notifies the + // server that we don't support gzip, deflate, or + // other content compression. + sb->write_bytes("Accept-encoding:"); + sb->write_bytes("\r\n"); + + // Add a user-agent header. Not sure why. + sb->write_bytes("User-agent: Mozilla 5.0 (luprex)"); + sb->write_bytes("\r\n"); + + // Add the requester IDs (debug string only) + if (debug_string && ((request_id_ != 0) || (place_id_ != 0) || (thread_id_ != 0))) { + sb->write_bytes("X-requester-ids: "); + sb->write_bytes(util::ss("rid=", request_id_, "; pid=", place_id_, "; tid=", thread_id_)); + sb->write_bytes("\r\n"); + } + + // Send the content length and the content type. + if (content_assigned_) { + send_content_length_header(true, content_.size(), sb); + send_content_type_header(true, mime_type_, sb); + } + + // Send the extra linebreak. + sb->write_bytes("\r\n"); + + // Send the content. + if (content_assigned_) { + sb->write_bytes(content_); + } +} + +HttpClientRequest::HttpClientRequest() { + verify_certificate_ = false; + port_ = 0; + content_assigned_ = false; + request_id_ = 0; + place_id_ = 0; + thread_id_ = 0; +} + +void HttpClientRequest::set_verify_certificate(bool flag) { + verify_certificate_ = flag; +} + +void HttpClientRequest::set_method(const eng::string &s) { + eng::string method = util::ascii_toupper(s); + if (!is_supported_client_method(method)) { + check_fail(util::ss("method not implemented: ", method, ".")); + return; + } + if ((!method_.empty()) && (method_ != method)) { + check_fail(util::ss("method specified twice: ", method_, " and ", method)); + return; + } + method_ = method; +} + +void HttpClientRequest::set_host(const eng::string &s) { + eng::string host = util::ascii_tolower(s); + if (host.empty()) { + check_fail(util::ss("hostname cannot be empty string.")); + return; + } + // This is not quite strict, but it's close. I believe + // the DNS lookup will fail for invalid hostnames anyway. + for (char c : host) { + if ((c != '-') && (c != '.') && (!sv::ascii_isalnum(c))) { + check_fail(util::ss("hostnames can only contain letters, digits, and hyphen: ", host)); + return; + } + } + if (!host_.empty()) { + check_fail(util::ss("hostname specified twice: ", host_, " and ", host)); + return; + } + host_ = host; +} + +void HttpClientRequest::set_port(int port) { + if ((port < 1) || (port > 65535)) { + check_fail(util::ss("port must be between 1 and 65535: ", port)); + return; + } + if (port_ != 0) { + check_fail(util::ss("port specified twice: ", port_, " and ", port)); + return; + } + port_ = port; +} + +void HttpClientRequest::set_path(string_view path) { + if (!sv::has_prefix(path, "/")) { + check_fail(util::ss("path must start with slash")); + return; + } + if (!path_.empty()) { + check_fail(util::ss("path specified twice: ", path_, " and ", path)); + return; + } + path_ = path; +} + +void HttpClientRequest::set_param(const eng::string &key, const eng::string &val) { + if (params_.find(key) != params_.end()) { + check_fail(util::ss("url parameter specified twice: ", key)); + return; + } + if (key.empty()) { + check_fail(util::ss("parameter key cannot be empty")); + return; + } + params_[key] = val; +} + +void HttpClientRequest::set_url(string_view url) { + ParsedURL parsed_url(url); + if (!parsed_url.valid) { + check_fail(util::ss("syntactically invalid URL: ", url)); + return; + } + if (parsed_url.proto != "https") { + check_fail(util::ss("unsupported protocol: ", parsed_url.proto)); + return; + } + set_host(parsed_url.host); + if (parsed_url.port) set_port(parsed_url.port); + set_path(parsed_url.path); + for (const auto &pair : parsed_url.params) { + set_param(pair.first, pair.second); + } +} + +void HttpClientRequest::set_mime_type(const eng::string &mime_type) { + if (!valid_mime_type(mime_type)) { + check_fail(util::ss("Not a valid mime type: ", mime_type)); + return; + } + if (!mime_type_.empty()) { + check_fail(util::ss("Mime type specified twice: ", mime_type_, " and ", mime_type)); + return; + } + mime_type_ = mime_type; +} + +void HttpClientRequest::set_content(const eng::string &content) { + if (!content_.empty()) { + check_fail(util::ss("Content specified twice")); + return; + } + if (content_assigned_) { + check_fail("Content specified twice."); + return; + } + content_ = content; + content_assigned_ = true; +} + +void HttpClientRequest::set_verify_certificate(LuaCoreStack &LS, LuaSlot val) { + auto tval = LS.tryboolean(val); + if (!tval) { + check_fail(util::ss("verifycertificate must be a boolean")); + return; + } + set_verify_certificate(*tval); +} + +void HttpClientRequest::set_method(LuaCoreStack &LS, LuaSlot val) { + auto tval = LS.trystring(val); + if (!tval) { + check_fail(util::ss("method must be a string")); + return; + } + set_method(*tval); +} + +void HttpClientRequest::set_host(LuaCoreStack &LS, LuaSlot val) { + auto tval = LS.trystring(val); + if (!tval) { + check_fail(util::ss("host must be a string")); + return; + } + set_host(*tval); +} + +void HttpClientRequest::set_port(LuaCoreStack &LS, LuaSlot val) { + auto tval = LS.tryint(val); + if (!tval) { + check_fail(util::ss("port must be an int")); + return; + } + set_port(*tval); +} + +void HttpClientRequest::set_path(LuaCoreStack &LS, LuaSlot val) { + auto tval = LS.trystring(val); + if (!tval) { + check_fail(util::ss("path must be a string")); + return; + } + set_path(*tval); +} + +void HttpClientRequest::set_param(LuaCoreStack &LS, LuaSlot key, LuaSlot val) { + auto tkey = LS.trystring(key); + auto tval = LS.trystring(val); + if (!tkey) { + check_fail(util::ss("url parameter key must be a string")); + return; + } + if (!tval) { + check_fail(util::ss("url parameter val must be a string")); + return; + } + set_param(*tkey, *tval); +} + +void HttpClientRequest::set_params(LuaCoreStack &LS0, LuaSlot tab) { + if (!LS0.trytable(tab)) { + check_fail(util::ss("params must be a table")); + return; + } + LuaVar key, val; + LuaExtStack LS(LS0.state(), key, val); + LS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + set_param(LS, key, val); + } +} + +void HttpClientRequest::set_url(LuaCoreStack &LS, LuaSlot val) { + auto tval = LS.trystring(val); + if (!tval) { + check_fail(util::ss("url must be a string")); + return; + } + set_url(*tval); +} + +void HttpClientRequest::set_mime_type(LuaCoreStack &LS, LuaSlot val) { + auto tval = LS.trystring(val); + if (!tval) { + check_fail(util::ss("mime type must be a string")); + return; + } + set_mime_type(*tval); +} + +void HttpClientRequest::set_content(LuaCoreStack &LS, LuaSlot val) { + auto tval = LS.trystring(val); + if (!tval) { + check_fail(util::ss("content must be a string")); + return; + } + set_content(*tval); +} + +void HttpClientRequest::set_jsonvalue(LuaCoreStack &LS, LuaSlot val) { + eng::string out; + eng::string err = json::encode(LS, val, out, false, HttpParser::MAX_CONTENT_LENGTH); + if (!err.empty()) { + check_fail(util::ss("json encode failure: ", err)); + return; + } + set_content(out); + set_mime_type("application/json"); +} + +void HttpClientRequest::set_defaults() { + if (method_.empty()) { + method_ = "GET"; + } + if (port_ == 0) { + port_ = 443; + } +} + +void HttpClientRequest::configure(LuaKeywordParser &kp) { + LuaVar val; + LuaExtStack LS(kp.state(), val); + if (kp.parse(val, "method")) { + set_method(LS, val); + } + if (kp.parse(val, "host")) { + set_host(LS, val); + } + if (kp.parse(val, "port")) { + set_port(LS, val); + } + if (kp.parse(val, "path")) { + set_path(LS, val); + } + if (kp.parse(val, "params")) { + set_params(LS, val); + } + if (kp.parse(val, "url")) { + set_url(LS, val); + } + if (kp.parse(val, "verifycertificate")) { + set_verify_certificate(LS, val); + } + if (kp.parse(val, "mimetype")) { + set_mime_type(LS, val); + } + if (kp.parse(val, "content")) { + set_content(LS, val); + } + if (kp.parse(val, "html")) { + set_content(LS, val); + set_mime_type("text/html"); + } + + if (kp.parse(val, "text")) { + set_content(LS, val); + set_mime_type("text/plain"); + } + if (kp.parse(val, "json")) { + set_content(LS, val); + set_mime_type("application/json"); + } + if (kp.parse(val, "bytes")) { + set_content(LS, val); + set_mime_type("application/octet-stream"); + } + if (kp.parse(val, "jsonvalue")) { + set_jsonvalue(LS, val); + } +} + +eng::string HttpClientRequest::target() const { + assert(check().empty()); + eng::ostringstream oss; + oss << (verify_certificate_ ? "cert" : "nocert"); + oss << ':' << host_ << ':' << port_; + return oss.str(); +} + + +eng::string HttpClientRequest::check() const { + if (!check_fail_.empty()) { + return check_fail_; + } + if (method_.empty()) { + return "method has not been set"; + } + if (host_.empty()) { + return "host has not been set"; + } + if (port_ == 0) { + return "port has not been set"; + } + if (path_.empty()) { + return "url has not been set"; + } + if (method_ == "POST") { + if (!content_assigned_) { + return "content not set for POST request"; + } + if (mime_type_.empty()) { + return "mime type not set for POST request"; + } + } + return ""; +} + +void HttpClientRequest::serialize(StreamBuffer *sb) const { + sb->write_int64(request_id_); + sb->write_int64(place_id_); + sb->write_int64(thread_id_); + sb->write_string(check_fail_); + sb->write_bool(verify_certificate_); + sb->write_string(method_); + sb->write_string(host_); + sb->write_int32(port_); + sb->write_string(path_); + sb->write_int32(params_.size()); + for (const auto &pair : params_) { + sb->write_string(pair.first); + sb->write_string(pair.second); + } +} + +void HttpClientRequest::deserialize(StreamBuffer *sb) { + request_id_ = sb->read_int64(); + place_id_ = sb->read_int64(); + thread_id_ = sb->read_int64(); + check_fail_ = sb->read_string(); + verify_certificate_ = sb->read_bool(); + method_ = sb->read_string(); + host_ = sb->read_string(); + port_ = sb->read_int32(); + path_ = sb->read_string(); + int32_t nparams = sb->read_int32(); + params_.clear(); + for (int i = 0; i < nparams; i++) { + eng::string k = sb->read_string(); + eng::string v = sb->read_string(); + params_[k] = v; + } +} + +eng::string HttpClientRequest::debug_string() { + StreamBuffer sb; + send_internal(&sb, true); + return eng::string(sb.view()); +} + +void HttpServerResponse::check_fail(string_view s) { + if (check_fail_.empty()) { + check_fail_ = s; + } +} + + +void HttpServerResponse::send_internal(StreamBuffer *sb, bool debug_string) const { + eng::string error = check(); + if (!error.empty()) { + send_error_response(http11_, 500, error, sb); + return; + } + + // Determine if we're going to be sending content. + bool contains_content = response_contains_content("GET", status_); + + // The status message is the human-readable status code. + eng::string statusmsg = status_code_to_string(status_); + + // Annotate error messages. + const eng::string *content = &content_; + if (errstatus() && contains_content && + (mime_type_ == "text/plain") && !contains_newline(content_)) { + if (sv::has_prefix(content_, statusmsg)) { + statusmsg = content_; + } else if (!content_.empty()) { + statusmsg += ": "; + statusmsg += content_; + } + content = &statusmsg; + } + + // Send the status line. + send_protocol(http11_, sb); + sb->write_bytes(util::ss(" ", status_, " ", statusmsg, "\r\n")); + + // Send the connection header. + if (!errstatus()) { + send_connection_header(http11_, keep_alive_, sb); + } + + // Send the headers that make sense if we're sending content. + if (contains_content) { + if (!errstatus()) { + send_cache_control_header(http11_, max_age_, sb); + } + send_content_length_header(http11_, content->size(), sb); + send_content_type_header(http11_, mime_type_, sb); + } + + // Send the extra linebreak. + sb->write_bytes("\r\n"); + + // Send the content. + if (contains_content) { + sb->write_bytes(*content); + } +} + +HttpServerResponse::HttpServerResponse() { + status_ = 0; + max_age_ = 0; + keep_alive_ = false; + content_assigned_ = false; + http11_ = false; +} + +void HttpServerResponse::set_status(int status) { + if (!is_supported_server_status(status)) { + check_fail(util::ss("status code not supported yet: ", status)); + return; + } + if (status_ != 0) { + check_fail(util::ss("status specified twice: ", status_, " and ", status)); + return; + } + status_ = status; +} + +void HttpServerResponse::set_max_age(int max_age) { + if (max_age < 0) { + check_fail(util::ss("max age must be a positive integer: ", max_age)); + return; + } + if (max_age_ != 0) { + check_fail(util::ss("max age specified twice: ", max_age_, " and ", max_age)); + return; + } + max_age_ = max_age; +} + +void HttpServerResponse::set_mime_type(const eng::string &mime_type) { + if (!valid_mime_type(mime_type)) { + check_fail(util::ss("mime type not syntactically valid: ", mime_type)); + return; + } + if (!mime_type_.empty()) { + check_fail(util::ss("mime type specified twice: ", mime_type_, " and ", mime_type)); + return; + } + mime_type_ = mime_type; +} + +void HttpServerResponse::set_content(const eng::string &content) { + if (content_assigned_) { + check_fail(util::ss("content specified twice")); + return; + } + content_ = content; + content_assigned_ = true; +} + +void HttpServerResponse::set_status(LuaCoreStack &LS, LuaSlot val) { + int status = 0; + auto vstring = LS.trystring(val); + auto vint = LS.tryint(val); + if (vstring) { + status = status_code_from_string(*vstring); + if (status == 0) { + check_fail(util::ss("unrecognized status code: ", *vstring)); + return; + } + } else if (vint) { + status = *vint; + } else { + check_fail(util::ss("status must be an integer")); + return; + } + set_status(status); +} + +void HttpServerResponse::set_max_age(LuaCoreStack &LS, LuaSlot val) { + auto vint = LS.tryint(val); + if (!vint) { + check_fail(util::ss("max-age must be an int")); + return; + } + set_max_age(*vint); +} + +void HttpServerResponse::set_mime_type(LuaCoreStack &LS, LuaSlot val) { + auto vstring = LS.trystring(val); + if (!vstring) { + check_fail(util::ss("mime type must be a string")); + return; + } + set_mime_type(*vstring); +} + +void HttpServerResponse::set_content(LuaCoreStack &LS, LuaSlot val) { + auto vstring = LS.trystring(val); + if (!vstring) { + check_fail(util::ss("content must be a string")); + return; + } + set_content(*vstring); +} + +void HttpServerResponse::set_jsonvalue(LuaCoreStack &LS, LuaSlot val) { + eng::string out; + eng::string err = json::encode(LS, val, out, false, HttpParser::MAX_CONTENT_LENGTH); + if (!err.empty()) { + check_fail(util::ss("json encode failure: ", err)); + return; + } + set_content(out); + set_mime_type("application/json"); +} + +void HttpServerResponse::configure(LuaKeywordParser &kp) { + LuaVar val; + LuaExtStack LS(kp.state(), val); + if (kp.parse(val, "status")) { + set_status(LS, val); + } + if (kp.parse(val, "maxage")) { + set_max_age(LS, val); + } + if (kp.parse(val, "mimetype")) { + set_mime_type(LS, val); + } + if (kp.parse(val, "content")) { + set_content(LS, val); + } + if (kp.parse(val, "html")) { + set_content(LS, val); + set_mime_type("text/html"); + } + if (kp.parse(val, "text")) { + set_content(LS, val); + set_mime_type("text/plain"); + } + if (kp.parse(val, "json")) { + set_content(LS, val); + set_mime_type("application/json"); + } + if (kp.parse(val, "bytes")) { + set_content(LS, val); + set_mime_type("application/octet-stream"); + } + if (kp.parse(val, "jsonvalue")) { + set_jsonvalue(LS, val); + } +} + +void HttpServerResponse::fail(int status, const eng::string &errmessage) { + status_ = status; + content_ = errmessage; + content_assigned_ = true; + mime_type_ = "text/plain"; + max_age_ = 0; +} + +void HttpServerResponse::set_defaults() { + // If you specified content, and didn't specify + // a status code, then assume it's a success. + if ((status_ == 0) && (content_assigned_)) { + status_ = 200; + } + + // If the response status indicates that we're sending + // content, and there's no content, generate blank content. + if (response_contains_content("GET", status_) && + (!content_assigned_) && (mime_type_.empty())) { + content_assigned_ = true; + content_ = ""; + mime_type_ = "text/plain"; + } +} + +eng::string HttpServerResponse::check() const { + if (!check_fail_.empty()) { + return check_fail_; + } + + // There needs to be a status code. + if (status_ == 0) { + return "status code not specified"; + } + + // If you assigned a mime type and didn't specify + // content, that's bad. + if ((!content_assigned_) && (!mime_type_.empty())) { + return "mime type specified without content"; + } + + // If you assigned content, but didn't assign + // a mime type, that's bad. + if (content_assigned_ && (mime_type_.empty())) { + return "content specified without mime type"; + } + return ""; +} + +eng::string HttpServerResponse::debug_string() { + StreamBuffer sb; + send_internal(&sb, true); + return eng::string(sb.view()); +} + +HttpParser::HttpParser() { + request_id_ = 0; + is_request_ = false; + status_ = 0; + mime_type_ = ""; + content_length_ = -1; + comm_length_ = 0; + http11_ = false; +} + +eng::string HttpParser::first_path_component(std::string_view defval) const { + std::string_view v = path_; + assert(sv::zfront(v) == '/'); + v.remove_prefix(1); + std::string_view first = sv::read_to_sep(v, '/'); + if (first.empty()) { + return eng::string(defval); + } else { + return eng::string(first); + } +} + +eng::string HttpParser::to_lua_identifier(std::string_view pathcomp) { + eng::ostringstream oss; + for (char c : pathcomp) { + if (sv::ascii_islower(c)) { + oss << c; + } else if (sv::ascii_isupper(c)) { + oss << char(c + 'a' - 'A'); + } else if (sv::ascii_isdigit(c)) { + oss << c; + } else if ((c == '.') || (c == '_')) { + oss << '_'; + } else { + return ""; + } + } + return oss.str(); +} + +void HttpParser::fail(int code, std::string_view message) { + status_ = code; + error_ = message; + mime_type_ = ""; + charset_ = ""; + content_ = ""; +} + +void HttpParser::syntax(std::string_view detail) { + if (is_request_) { + fail(400, util::ss("Bad Request: ", detail)); + } else { + fail(500, util::ss("Bad Response: ", detail)); + } +} + +void HttpParser::incomplete(bool closed) { + if (closed) { + syntax("response truncated"); + } else { + fail(0, "response not yet fully received"); + } +} + +void HttpParser::oversized() { + fail(413, util::ss("Payload Too Large: Limit=", MAX_CONTENT_LENGTH)); +} + +bool HttpParser::parse_request_line(std::string_view &view, bool closed) { + // Extract the request line. + // + string_view request = sv::trim(sv::read_to_line(view)); + if (sv::isnull(view)) { + incomplete(closed); + return false; + } + + // Break down the request line. + // + eng::string method = util::ascii_toupper(sv::read_to_space(request)); + string_view path = sv::read_to_space(request); + eng::string protocol = util::ascii_toupper(sv::read_to_space(request)); + if ((!request.empty()) || (protocol.empty())) { + syntax("invalid request line"); + return false; + } + if (method != "GET") { + fail(405, util::ss("Method Not Allowed: ", method)); + return false; + } + if (!is_supported_protocol(protocol)) { + fail(505, util::ss("HTTP Version Not Supported: ", protocol)); + return false; + } + http11_ = (protocol == "HTTP/1.1"); + + // Parse the url. + // + ParsedURL url(path); + if (!url.valid) { + syntax(util::ss("invalid URL path: ", path)); + return false; + } + + method_ = method; + path_ = url.path; + params_ = url.params; + return true; +} + +bool HttpParser::parse_status_line(std::string_view &view, bool closed) { + // Extract the status line. + // + string_view status = sv::trim(sv::read_to_line(view)); + if (sv::isnull(view)) { + incomplete(closed); + return false; + } + + // Get the protocol version from the response line. + // + string_view protoversion = sv::read_to_space(status); + if (!is_supported_protocol(protoversion)) { + syntax(util::ss("unsupported protocol: ", protoversion)); + return false; + } + http11_ = (protoversion == "HTTP/1.1"); + + // Get the status code from the response line. + // + string_view scode = sv::read_to_space(status); + int64_t code = sv::to_int64(scode, 0); + if ((code < 100) || (code > 599)) { + syntax(util::ss("invalid response code: ", scode)); + return false; + } + status_ = code; + + // Responses outside the range 200-299 are errors, + // and therefore must store a nonempty error message. + // + if ((code < 200) || (code > 299)) { + if (status.empty()) { + error_ = status_code_to_string(code); + } else { + error_ = status; + } + } + return true; +} + +void HttpParser::parse_content_encoding(string_view value) { + content_encoding_ = util::ascii_tolower(value); +} + +void HttpParser::parse_content_length(string_view value) { + int64_t code = sv::to_int64(value); + if ((code < 0) || (code > INT_MAX)) { + syntax(util::ss("unparseable content-length: ", value)); + } + content_length_ = code; +} + +void HttpParser::parse_content_type(string_view value) { + eng::string ctype = util::ascii_tolower(value); + string_view ctview(ctype); + mime_type_ = sv::trim(sv::read_to_sep(ctview, ';')); + if (mime_type_.empty()) { + syntax(util::ss("unparseable content-type: ", value)); + return; + } + while (true) { + string_view feature = sv::trim(sv::read_to_sep(ctview, ';')); + if (feature.empty()) { + return; + } + string_view ftype = sv::trim(sv::read_to_sep(feature, '=')); + if (ftype == "charset") { + charset_ = sv::trim(feature); + } + } +} + +void HttpParser::parse_location(string_view value) { + location_ = url_decode(value); +} + +void HttpParser::parse_transfer_encoding(string_view value) { + transfer_encoding_ = util::ascii_tolower(value); +} + +void HttpParser::parse_header(string_view header, string_view value) { + if (header == "content-encoding") { + parse_content_encoding(value); + } else if (header == "content-length") { + parse_content_length(value); + } else if (header == "content-type") { + parse_content_type(value); + } else if (header == "location") { + parse_location(value); + } else if (header == "transfer-encoding") { + parse_transfer_encoding(value); + } else if (header == "content-range") { + fail(416, util::ss("Range Not Satisfiable: unsupported header: ", header)); + } +} + +bool HttpParser::parse_headers(std::string_view &view, bool closed) { + // Parse the headers. + while (true) { + string_view header = sv::read_to_line(view); + if (sv::isnull(view)) { + incomplete(closed); + return false; + } + if (header.empty()) { + return true; + } + eng::string command = util::ascii_tolower(sv::trim(sv::read_to_sep(header, ':'))); + if (sv::isnull(header)) { + syntax(util::ss("no colon in header line: ", command)); + return false; + } + if (!words_separated_by_dashes(command)) { + syntax(util::ss("invalid header: ", command)); + return false; + } + parse_header(command, sv::trim(header)); + } +} + +bool HttpParser::parse_content_basic(std::string_view &view, bool closed) { + if (content_length_ >= 0) { + if (content_length_ > MAX_CONTENT_LENGTH) { + oversized(); + return false; + } + if (int(view.size()) < content_length_) { + incomplete(closed); + return false; + } + content_ = sv::read_nbytes(view, content_length_); + } else { + if (int64_t(view.size()) > MAX_CONTENT_LENGTH) { + oversized(); + return false; + } + if (!closed) { + incomplete(closed); + return false; + } + content_ = sv::read_nbytes(view, view.size()); + } + return true; +} + +bool HttpParser::parse_content_chunked(std::string_view &view, bool closed) { + int64_t total_size = 0; + std::vector chunks; + while (true) { + std::string_view chunk_header = sv::trim(sv::read_to_line(view)); + if (sv::isnull(view)) { + incomplete(closed); + return false; + } + int64_t chunk_size = sv::to_hex64(chunk_header, -1); + if (chunk_size < 0) { + syntax("unparseable chunk header"); + return false; + } + if (chunk_size > MAX_CONTENT_LENGTH) { + oversized(); + return false; + } + if (chunk_size == 0) break; + total_size += chunk_size; + if (total_size > MAX_CONTENT_LENGTH) { + oversized(); + return false; + } + std::string_view chunk = sv::read_nbytes(view, chunk_size); + if (int64_t(chunk.size()) != chunk_size) { + incomplete(closed); + return false; + } + std::string_view newline = sv::read_to_line(view); + if (!newline.empty()) { + syntax("corrupted chunk encoding"); + return false; + } + if (sv::isnull(view)) { + incomplete(closed); + return false; + } + chunks.push_back(chunk); + } + content_.resize(total_size); + size_t offset = 0; + for (string_view chunk : chunks) { + content_.replace(offset, chunk.size(), chunk); + offset += chunk.size(); + } + return true; +} + +bool HttpParser::parse_content(std::string_view &view, bool closed) { + // If there's no body, just return true. + if (is_request_) { + if (!request_contains_content(content_length_, transfer_encoding_)) { + return true; + } + } else { + if (!response_contains_content(method_, status_)) { + return true; + } + } + + // Parse the content. + if (transfer_encoding_ == "") { + if (!parse_content_basic(view, closed)) { + return false; + } + } else if (transfer_encoding_ == "chunked") { + if (!parse_content_chunked(view, closed)) { + return false; + } + } else { + syntax(util::ss("unsupported transfer-encoding: ", transfer_encoding_)); + return false; + } + + // Uncompress the content. + if ((content_encoding_ == "") || (content_encoding_ == "identity")) { + } else { + syntax(util::ss("content-encoding not supported: ", content_encoding_)); + return true; + } + + // If the sender didn't specify content-type, make a guess based on the content. + if (mime_type_.empty()) { + if (sv::valid_utf8(content_)) { + mime_type_ = "text/html"; + charset_ = "utf-8"; + } else { + mime_type_ = "application/octet-stream"; + charset_ = ""; + } + } + + // Switch the charset to utf-8, if it's text. + if (sv::has_prefix(mime_type_, "text/")) { + if (charset_.empty() || (charset_ == "ascii") || (charset_ == "utf-8")) { + // we're already good. + } else { + // We can't convert charsets yet. + syntax(util::ss("charset not supported: ", charset_)); + return true; + } + } else { + // Not text. No need to specify charset. + charset_.clear(); + } + return true; +} + +void HttpParser::store(LuaCoreStack &LS0, LuaSlot tab) const { + LuaVar ptab, djson; + LuaExtStack LS(LS0.state(), ptab, djson); + + LS.newtable(tab); + if (!is_request_) { + LS.rawset(tab, "status", status_); + } + if (!error_.empty()) { + LS.rawset(tab, "error", error_); + } + if (!location_.empty()) { + LS.rawset(tab, "location", location_); + } + if (!mime_type_.empty() || !content_.empty()) { + LS.rawset(tab, "mimetype", mime_type_); + LS.rawset(tab, "content", content_); + if (mime_type_ == "application/json") { + json::decode(LS, djson, content_); + LS.rawset(tab, "jsonvalue", djson); + } + } + // We currently don't store the method, because + // our server only supports GET. Even if we + // do support more methods in the future, are + // unlikely to want to handle this from inside lua. + // + // if (!method_.empty()) { + // LS.rawset(tab, "method", method_); + // } + if (!path_.empty()) { + LS.rawset(tab, "path", path_); + LS.newtable(ptab); + LS.rawset(tab, "params", ptab); + for (const auto &pair : params_) { + LS.rawset(ptab, pair.first, pair.second); + } + } + + // Debugging fields. Do not use for lua programming. + if (content_length_ >= 0) { + LS.rawset(tab, "dbg_contentlength", content_length_); + } + if (!transfer_encoding_.empty()) { + LS.rawset(tab, "dbg_transferencoding", transfer_encoding_); + } + if (!charset_.empty()) { + LS.rawset(tab, "dbg_charset", charset_); + } + if (comm_length_ != 0) { + LS.rawset(tab, "dbg_commlength", comm_length_); + } +} + +eng::string HttpParser::debug_string() const { + eng::ostringstream oss; + if (request_id_ != 0) { + oss << " request_id: " << request_id_ << std::endl; + } + oss << " status_code: " << status_ << std::endl; + oss << " error: " << error_ << std::endl; + if (content_length_ >= 0) { + oss << " content_length: " << content_length_ << std::endl; + } + if (!transfer_encoding_.empty()) { + oss << " transfer_encoding: " << transfer_encoding_ << std::endl; + } + if (!location_.empty()) { + oss << " location: " << location_ << std::endl; + } + if (!mime_type_.empty()) { + oss << " mime_type: " << mime_type_ << std::endl; + } + if (!charset_.empty()) { + oss << " charset: " << charset_ << std::endl; + } + if (!content_.empty()) { + oss << " content: " << content_ << std::endl; + } + if (!method_.empty()) { + oss << " method: " << method_ << std::endl; + } + if (!path_.empty()) { + oss << " path: " << path_ << std::endl; + } + for (const auto &pair : params_) { + oss << " param: " << pair.first << "=" << pair.second << std::endl; + } + if (comm_length_ > 0) { + oss << " comm_length: " << comm_length_ << std::endl; + } + return oss.str(); +} + +void HttpParser::parse_response(std::string_view view, bool closed, std::string_view method) { + std::string_view original_view = view; + is_request_ = false; + method_ = method; + + // Parse the status line. + if (!parse_status_line(view, closed)) { + return; + } + + // Parse the headers. + if (!parse_headers(view, closed)) { + return; + } + + // Process the content. + if (!parse_content(view, closed)) { + return; + } + + // Calculate the response length. + comm_length_ = original_view.size() - view.size(); + + // If it's not a redirect, ignore location. + if ((status_ < 300) || (status_ > 399)) { + location_.clear(); + } + + // If it's multipart, reject it. + if (sv::has_prefix(mime_type_, "multipart/")) { + syntax("multipart messages not supported"); + return; + } +} + +void HttpParser::parse_request(std::string_view view, bool closed) { + std::string_view original_view = view; + is_request_ = true; + + // Parse the request line. + if (!parse_request_line(view, closed)) { + return; + } + + // Parse the headers. + if (!parse_headers(view, closed)) { + return; + } + + // Process the content, if any. + if (!parse_content(view, closed)) { + return; + } + + // Calculate the comm length. + comm_length_ = original_view.size() - view.size(); + + // Always ignore location. + location_.clear(); + + // If it's multipart, reject it. + if (sv::has_prefix(mime_type_, "multipart/")) { + syntax("multipart messages not supported"); + return; + } + + // If we've made it this far, and there's no + // status code, set it to 200 OK. + if (status_ == 0) status_ = 200; +} + +void HttpParser::store_fail(LuaCoreStack &LS, LuaSlot tab, int status_code, std::string_view error) { + HttpParser parser; + parser.fail(status_code, error); + parser.store(LS, tab); +} + +void HttpClientRequestMap::serialize(StreamBuffer *sb) const { + sb->write_int32(size()); + for (const auto &pair : *this) { + pair.second.serialize(sb); + } +} + +void HttpClientRequestMap::deserialize(StreamBuffer *sb) { + int32_t count = sb->read_int32(); + clear(); + HttpClientRequest req; + for (int i = 0; i < count; i++) { + req.deserialize(sb); + (*this)[req.request_id()] = req; + } +} + +LuaDefine(http_fixurl, "url", "validate URL and repair minor flaws in the URL syntax") { + LuaArg url; + LuaRet fixed; + LuaDefStack LS(L, url, fixed); + ParsedURL parsed(LS.ckstring(url)); + if (!parsed.valid) { + luaL_error(L, "invalid URL, not fixable"); + return LS.result(); + } + LS.set(fixed, parsed.str()); + return LS.result(); +} + + +LuaDefine(http_clientrequest, "request", + "|Takes an HTTP client request in the form of a lua table." + "|The table may contain these fields:" + "|" + "| method (ie, 'GET', 'POST', etc)" + "| host (ie, 'google.com')" + "| port (default: 443)" + "| path (ie, '/index.html')" + "| params (a table of url parameters)" + "| verifycertificate (default: true)" + "| content (the body to send to the server, if any)" + "| mimetype (mime type of the body, if any)" + "|" + "|The table can also contain this field, which sets" + "|host, port, path, and params in a single directive." + "|" + "| url (ie, 'https://host:port/path.html?a=b&c=d')" + "|" + "|The table can also contain these fields, which set" + "|both the content and the mimetype in a single directive:" + "|" + "| html - content as a string (text/html)" + "| text - content as a string (text/plain)" + "| json - content as a string (application/json)" + "| bytes - content as a string (application/octet-stream)" + "|" + "|You can specify url components separately (host, port, path," + "|and params), or you can specify the entire url as a unit. " + "|If you specify components, they must not be url-encoded. " + "|If you specify the url as a whole, it must already be url-encoded." + "|" + "|You can omit the port, in which case it defaults to the" + "|standard https port. You can omit verifycertificate, in which" + "|case it defaults to true. You can omit the method if the" + "|method is implied by the function you called (eg, 'http.get')." + "|" + "|Note that unencrypted http is not supported - we only allow https." + "|However, you can talk to a server that has a dummy certificate" + "|by specifying verifycertificate=false." + "|" + "|This routine, http.clientrequest, returns a debug string for the " + "|request. The debug string looks like the actual http headers" + "|that would be sent.") { + LuaArg tab; + LuaRet str; + LuaDefStack LS(L, tab, str); + LuaKeywordParser kp(LS, tab); + HttpClientRequest req; + req.configure(kp); + kp.final_check_throw(); + req.set_defaults(); + eng::string error = req.check(); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + return 0; + } + LS.set(str, req.debug_string()); + return LS.result(); +} + +LuaDefine(http_clientresponse, "response", + "|Returns an HTTP client response in the form of a lua table." + "|The table will contain these important fields:" + "|" + "| status - 3-digit HTTP response code." + "| error - an error message, or nil if no error." + "| content - on success, the content, as a string." + "| mimetype - on success, the mime type of the content." + "| location - for HTTP redirects, the target url." + "|" + "|If the mimetype is a text mimetype, then the content" + "|is automatically converted to utf-8." + "|" + "|The table may also contain these debugging-only fields." + "|" + "| dbg_transferencoding - If there was a Transfer-Encoding header." + "| dbg_contentlength - If there was a Content-length header." + "| dbg_charset - Original character set for text mime types." + "| dbg_commlength - Total bytes in the communication." + "|" + "|None of the dbg fields is needed to understand the response." + "|For example, consider dbg_charset. When text content is" + "|passed to lua, the content is automatically converted to utf-8." + "|So dbg_charset only tells you what the character set used" + "|to be, before it was converted to utf-8." + "|" + "|If an http routine generates an error, that error will be" + "|expressed as a status code. These locally-generated status" + "|codes can be:" + "|" + "| 400 (bad request) - the request was malformed" + "| 503 (service unavailable) - dns fail, connect fail, or ssl fail" + "| 500 (internal server error)- the response contains invalid HTTP" + "| 413 (payload too large) - we refuse to download something so big" + "| 425 (can't resume) - reloaded a save game with a pending request" + "|" + "|Error messages that are generated locally consist of " + "|the standard message (eg, 'bad request') followed by more " + "|detailed information." + "|" + "|This routine, http.clientresponse, generates a response by parsing" + "|an actual HTTP response string. This is for debugging only.") { + LuaArg text; + LuaRet tab; + LuaDefStack LS(L, text, tab); + HttpParser parser; + parser.parse_response(LS.ckstring(text), true, "GET"); + parser.store(LS, tab); + return LS.result(); +} + +LuaDefine(http_serverrequest, "request", + "|Returns an HTTP server request in the form of a lua table." + "|The table will contain these important fields:" + "|" + "| status - 3-digit HTTP response code." + "| error - an error message, or nil if no error." + "| method - GET, HEAD, or POST" + "| path - the url-decoded path, eg, '/index.html'" + "| params - a table of url-decoded URL parameters" + "| content - the content, as a string (POST only)" + "| mimetype - the mime type of the content (POST only)" + "|" + "|If the mimetype is a text mimetype, then the content" + "|is automatically converted to utf-8." + "|" + "|The table may also contain these debugging-only fields." + "|" + "| dbg_transferencoding - If there was a Transfer-Encoding header." + "| dbg_contentlength - If there was a Content-length header." + "| dbg_charset - Original character set for text mime types." + "| dbg_commlength - Total bytes in the communication." + "|" + "|None of the dbg fields is needed to understand the request." + "|For example, consider dbg_charset. When text content is" + "|passed to lua, the content is automatically converted to utf-8." + "|So dbg_charset only tells you what the character set used" + "|to be, before it was converted to utf-8." + "|" + "|When the engine is functioning as a webserver, bad requests " + "|are never passed to lua. Therefore, a request that is passed " + "|to lua will always contain status=200 and error=nil. However, " + "|when debugging server requests using http.serverrequest, " + "|it is possible to see certain other errors:" + "|" + "| 400 (bad request) - the request was malformed" + "| 503 (service unavailable) - dns fail, connect fail, or ssl fail" + "| 500 (internal server error)- the response contains invalid HTTP" + "| 413 (payload too large) - we refuse to download something so big" + "| 425 (can't resume) - reloaded a save game with a pending request" + "|" + "|Error messages that are generated locally consist of " + "|the standard message (eg, 'bad request') followed by more " + "|detailed information." + "|" + "|This routine, http.serverrequest, generates a request by parsing" + "|an actual HTTP request string. This is for debugging only.") { + LuaArg text; + LuaRet tab; + LuaDefStack LS(L, text, tab); + HttpParser parser; + parser.parse_request(LS.ckstring(text), true); + parser.store(LS, tab); + return LS.result(); +} + +LuaDefine(http_serverresponse, "response", + "|Takes an HTTP server response in the form of a lua table." + "|The table may contain these fields:" + "|" + "| status - status result" + "| content - content as a string" + "| mimetype - mime type of the content" + "| maxage - enables client-side caching" + "|" + "|The table can also contain these fields, which set" + "|both the content and the mimetype in a single directive:" + "|" + "| html - content as a string (text/html)" + "| text - content as a string (text/plain)" + "| json - content as a string (application/json)" + "| bytes - content as a string (application/octet-stream)" + "|" + "|To send a successful response to a GET or HEAD request," + "|you must supply content and mimetype (you can supply both" + "|in a single field, if desired). We recommend omitting the" + "|status code." + "|" + "|To send a successful response to a POST request, you only" + "|need to supply status=201 or status='Created'." + "|" + "|To send an error response, you must supply a status" + "|code indicating the error. The status must be one of the" + "|standard HTTP status codes, expressed either as a 3-digit" + "|number (eg, 404), or a string (eg, 'Not Found')." + "|" + "|Not every HTTP status code is currently supported:" + "|" + "| * Success codes 200 and 201 are supported" + "| * Other success codes (202-299) are not supported" + "| * Informational codes (100-199) are not supported" + "| * Redirects (300-399) are not supported" + "| * All error codes (400-599) are supported" + "|" + "|By default, this server asks the client not to cache" + "|anything. If you specify maxage, however, then you" + "|are giving the client permission to cache." + "|" + "|If the lua server throws an error, or returns an" + "|invalid response, the error will be reported through" + "|the browser, as a 500 Internal Server Error." + "|" + "|This routine, http.serverresponse, returns a debug string for the " + "|response. The debug string looks like the actual http response" + "|that would be sent.") { + LuaArg tab; + LuaRet str; + LuaDefStack LS(L, tab, str); + LuaKeywordParser kp(LS, tab); + HttpServerResponse resp; + resp.configure(kp); + kp.final_check_throw(); + resp.set_defaults(); + LS.set(str, resp.debug_string()); + return LS.result(); +} + + +LuaDefine(http_validmime, "(mt)", "") { + LuaArg str; + LuaRet ok; + LuaDefStack LS(L, str, ok); + LS.set(ok, valid_mime_type(LS.ckstring(str))); + return LS.result(); +} + +LuaDefine(http_statusstring, "(statuscode)", "Convert a 3-digit status code to a string") { + LuaArg code; + LuaRet str; + LuaDefStack LS(L, code, str); + int icode = LS.ckint(code); + LS.set(str, status_code_to_string(icode)); + return LS.result(); +} + +LuaDefine(http_statuscode, "(statusstring)", "Convert a string to a 3-digit status code") { + LuaArg str; + LuaRet code; + LuaDefStack LS(L, code, str); + eng::string sstr = LS.ckstring(str); + LS.set(code, status_code_from_string(sstr)); + int iresult = LS.result(); + return iresult; +} diff --git a/luprex/cpp/core/http.hpp b/luprex/cpp/core/http.hpp new file mode 100644 index 00000000..4f37c2ac --- /dev/null +++ b/luprex/cpp/core/http.hpp @@ -0,0 +1,457 @@ +///////////////////////////////////////////////////////// +// +// HTTP Implementation. +// +// This is a fairly limited implementation of HTTP. +// +// It only supports HTTP 1.1 +// +// It only supports GET, POST, and HEAD. +// +///////////////////////////////////////////////////////// + +#ifndef HTTP_HPP +#define HTTP_HPP + +#include "eng-malloc.hpp" +#include "wrap-string.hpp" +#include "wrap-vector.hpp" +#include "wrap-map.hpp" +#include "luastack.hpp" +#include "streambuffer.hpp" +#include "drivenengine.hpp" +#include + +using UrlParameters = eng::map; + +class HttpClientRequest : public eng::nevernew { +private: + // Request IDs. + int64_t request_id_; + int64_t place_id_; + int64_t thread_id_; + + // When we detect that we generated an invalid + // outgoing request, the error is stored here. + eng::string check_fail_; + + // If true, verify the server's certificate. + // True is the default. + bool verify_certificate_; + + // Method: GET, HEAD, or POST. Must be all-caps. + eng::string method_; + + // The hostname. Not yet url-encoded. + eng::string host_; + + // Port number. + int port_; + + // The path. Not yet url-encoded. Can not include URL parameters. + eng::string path_; + + // URL parameters to append to the path. Not yet url-encoded. + UrlParameters params_; + + // The mime type of the content, only for POST requests. + eng::string mime_type_; + + // The content as a string, only for POST requests. + eng::string content_; + bool content_assigned_; + +private: + void check_fail(std::string_view error); + void send_internal(StreamBuffer *target, bool debug_string) const; + +public: + // Construct an empty HTTP request. + // All of the fields have empty values. + HttpClientRequest(); + + // Populate an request-related fields one piece at a time. + // If you pass an invalid value, or if the field is + // already set, the routine will generate an error message + // and store it in the error field. In that case, the set + // will not happen. If there's already an error in the error + // field, it will not be overwritten. + // + void set_verify_certificate(bool flag); + void set_method(const eng::string &method); + void set_host(const eng::string &host); + void set_port(int port); + void set_path(std::string_view path); + void set_param(const eng::string &key, const eng::string &value); + void set_url(std::string_view url); + void set_mime_type(const eng::string &mime_type); + void set_content(const eng::string &content); + + void set_verify_certificate(LuaCoreStack &LS, LuaSlot val); + void set_method(LuaCoreStack &LS, LuaSlot val); + void set_host(LuaCoreStack &LS, LuaSlot val); + void set_port(LuaCoreStack &LS, LuaSlot val); + void set_path(LuaCoreStack &LS, LuaSlot path); + void set_param(LuaCoreStack &LS, LuaSlot key, LuaSlot val); + void set_params(LuaCoreStack &LS, LuaSlot tab); + void set_url(LuaCoreStack &LS, LuaSlot val); + void set_mime_type(LuaCoreStack &LS, LuaSlot val); + void set_content(LuaCoreStack &LS, LuaSlot val); + void set_jsonvalue(LuaCoreStack &LS, LuaSlot val); + + // Set default values for method and port. + // This must be done after setting regular values. + void set_defaults(); + + // Populate request-related fields from a Lua table. + // Doesn't throw errors, instead, returns errors via check(). + void configure(LuaKeywordParser &kp); + + // Get or Set the request IDs. + // + // This class does not use these fields, it just stores + // them as a convenience. + // + int64_t request_id() const { return request_id_; } + int64_t place_id() const { return place_id_; } + int64_t thread_id() const { return thread_id_; } + void set_request_id(int64_t request_id) { request_id_ = request_id; } + void set_place_id(int64_t place_id) { place_id_ = place_id; } + void set_thread_id(int64_t thread_id) { thread_id_ = thread_id; } + + // Get the network target, eg, "cert:host:port" + // + eng::string target() const; + + // Verify that the request is error free and that + // defaults have been set. + // + eng::string check() const; + + // Get the method. + // + eng::string method() const { return method_; } + + // Put the request into the stream, assuming HTTP/1.1 + // + void send(StreamBuffer *target) const { send_internal(target, false); } + + // Serialize and deserialize. + // + void serialize(StreamBuffer *sb) const; + void deserialize(StreamBuffer *sb); + + // Get the request as a debug string. + // + eng::string debug_string(); +}; + +class HttpServerResponse { +private: + // When we detect that our own response is + // invalid, the error is stored here. + // + eng::string check_fail_; + + // The status code to send back to the client + // as part of the response status line. + // + int status_; + + // Maximum age for the cache-control directive. + // + int max_age_; + + // Mime type of the content. + // + eng::string mime_type_; + + // Content to send to the client. + // + eng::string content_; + bool content_assigned_; + + // If the keep-alive flag is set, then a connection: keep-alive + // header is sent. Otherwise, a connection: close is sent. + // The keep-alive flag cannot be controlled from Lua. It is + // designed to be handled automatically by the server code. + // + bool keep_alive_; + + // The protocol is taken from the request. + // + bool http11_; + +private: + void check_fail(std::string_view error); + void send_internal(StreamBuffer *target, bool debug_string) const; + +public: + // Construct an empty response. + // All of the fields have empty values. + // + HttpServerResponse(); + + void set_method(const eng::string &method); + void set_status(int status); + void set_max_age(int max_age); + void set_mime_type(const eng::string &mime_type); + void set_content(const eng::string &content); + + void set_status(LuaCoreStack &LS, LuaSlot val); + void set_max_age(LuaCoreStack &LS, LuaSlot val); + void set_mime_type(LuaCoreStack &LS, LuaSlot val); + void set_content(LuaCoreStack &LS, LuaSlot val); + void set_jsonvalue(LuaCoreStack &LS, LuaSlot val); + + // Set default values. + // + void set_defaults(); + + // Populate request-related fields from a Lua table. + // Doesn't throw errors, instead, returns them via check() + // + void configure(LuaKeywordParser &kp); + + // Set the keep_alive flag. + // + void set_keep_alive(bool k) { keep_alive_ = k; }; + + // Set the protocol. + // + void set_http11(bool k) { http11_= k; } + + // Store a fail response. + // + void fail(int status, const eng::string &message); + + // Verify that the response is error free. + // + eng::string check() const; + + // Get the status of the response. + // + int status() const { return status_; } + bool errstatus() const { return (status_ >= 400)&&(status_ <= 599); } + + // Put the response into the stream, assuming HTTP/1.1 + // + void send(StreamBuffer *target) const { send_internal(target, false); } + + // Get the response as a debug string. + // + eng::string debug_string(); +}; + +// HttpParser is used for parsing both requests and responses. +// +class HttpParser : public eng::nevernew { +protected: + // The request ID is not used by the parser, but + // you can store a request ID in there for convenience. + int64_t request_id_; + + // True if this parser parsed a request. If false, + // then it parsed a response. + bool is_request_; + + // The status code, a 3-digit number. + int status_; + + // If the communication contains an error, the error can + // be stored here. In the event of a successful communication, + // this should always be empty string. + eng::string error_; + + // Only if content-length header present, otherwise, -1. + int64_t content_length_; + + // If empty, it means there was no transfer-encoding header. + eng::string transfer_encoding_; + + // If empty, it means there was no content-encoding header. + eng::string content_encoding_; + + // Only if location header present. + eng::string location_; + + // MIME type of the content. + eng::string mime_type_; + + // Charset of the content before the content was translated to utf-8. + eng::string charset_; + + // The protocol: true for HTTP/1.1, false for HTTP/1.0 + bool http11_; + + // The method. In a response, this is assigned before parsing. + eng::string method_; + + // The URL path, not url-encoded. (only when parsing requests) + eng::string path_; + + // The URL parameters, not url-encoded (only when parsing requests) + UrlParameters params_; + + // The content as string. If it's text, it's been translated to utf-8. + eng::string content_; + + // The length in bytes of the entire communication. + // If the response is complete, but the comm_length_ + // is zero, it means we couldn't find the end of + // the request. + int comm_length_; + +protected: + // Store a message indicating that we haven't received enough + // bytes yet. If the connection is closed and we still haven't + // received enough bytes, that's a fatal error. + // + void incomplete(bool closed); + + // Store a message indicating that we couldn't parse because + // something was syntactically malformed, ie, not valid HTTP. + // + void syntax(std::string_view detail); + + // Store a message indicating that the content was too large + // and we refused to download it. + // + void oversized(); + + // Parse a response status line, such as "HTTP/1.1 200 OK" + // returns false if we couldn't parse the whole line. + // + bool parse_status_line(std::string_view &view, bool closed); + + // Parse a request line, such as "GET /index.html HTTP/1.1" + // returns false if we couldn't parse the whole line. + // + bool parse_request_line(std::string_view &view, bool closed); + + // Parse specific headers. + // + void parse_content_encoding(std::string_view value); + void parse_content_length(std::string_view value); + void parse_content_type(std::string_view value); + void parse_location(std::string_view value); + void parse_transfer_encoding(std::string_view value); + + // Parse a single response header. Most headers are ignored. + // If the header contains an error, the error is stored. + // + void parse_header(std::string_view header, std::string_view value); + + // Parse all of the headers, including the blank line after + // the headers. Return true if the view is at the end of the headers. + // + bool parse_headers(std::string_view &view, bool closed); + + // parse the content, for different transfer encodings. + // Returns true if the view is at the end of the content. + // + bool parse_content_basic(std::string_view &view, bool closed); + bool parse_content_chunked(std::string_view &view, bool closed); + + // Parse the content. Return true if the view is at the end of the + // content. + // + // - Uses the appropriate transfer-encoding specified in the headers. + // - Decompresses the content using the appropriate content-encoding. + // - Guesses a mimetype and charset if one was not specified in headers. + // - If it's text, converts the charset to utf-8. + // + bool parse_content(std::string_view &view, bool closed); + +public: + // Construct a blank parser. + // + HttpParser(); + + // Get the parsed status or error message. + // + int status() const { return status_; } + bool errstatus() const { return (status_ >= 400) && (status_ <= 599); } + eng::string error() const { return error_; } + + // Return true if the communication was complete. + // + bool complete() const { return status_ != 0; } + + // Get the first component of the path. + // If the path is empty, return defval. + eng::string first_path_component(std::string_view defval) const; + + // Get the parsed protocol. + // + bool http11() const { return http11_; } + + // Store the parsed fields into a lua table. + // + void store(LuaCoreStack &LS, LuaSlot tab) const; + + // The parser will not try to parse content longer than this. + // + static constexpr int64_t MAX_CONTENT_LENGTH = 1000000; + + // Parse a request or a response. + // + // You can only parse once. If you need to parse again, + // construct a new parser. + // + void parse_request(std::string_view view, bool closed); + void parse_response(std::string_view view, bool closed, std::string_view method); + + // Store a status code and an error message, and clear the content. + // This is generally used when the client detects an error, + // such as a DNS lookup fail, a connection failed, an SSL negotiation + // failed, or the like. + // + void fail(int status, std::string_view error); + + // Get or set the request ID. + // + int64_t request_id() const { return request_id_; } + void set_request_id(int64_t v) { request_id_ = v; } + + // Return a debug string. + // + eng::string debug_string() const; + + // Synthesize an error and store it in lua. + // + static void store_fail(LuaCoreStack &LS, LuaSlot tab, int status, std::string_view error); + + // Convert a path component into a lua identifier. + // + // Returns the empty string if the path component cannot be converted + // to a lua identifier. + // + static eng::string to_lua_identifier(std::string_view pathcomp); +}; + +class HttpClientRequestMap : public eng::map { +public: + void serialize(StreamBuffer *sb) const; + void deserialize(StreamBuffer *sb); +}; + +using HttpParserVec = eng::vector; + +// This class is used by LpxServer to store the +// incoming and outgoing http channels. +// +class HttpChannel { +public: + SharedChannel channel_; + eng::string method_; + int64_t parsed_bytes_; + + bool marked_for_deletion() const { return channel_ == nullptr; } + HttpChannel() : parsed_bytes_(0) {} +}; + +using HttpChannelMap = eng::map; +using HttpChannelVec = eng::vector; + +#endif // HTTP_HPP diff --git a/luprex/cpp/core/idalloc.cpp b/luprex/cpp/core/idalloc.cpp new file mode 100644 index 00000000..b77c7baa --- /dev/null +++ b/luprex/cpp/core/idalloc.cpp @@ -0,0 +1,433 @@ +#include "wrap-map.hpp" +#include "wrap-sstream.hpp" +#include "wrap-deque.hpp" + +#include "idalloc.hpp" + +#include + + +static bool ranges_equal(const eng::deque &dq, int64_t a, int64_t b, int64_t c) { + if (dq.size() != 3) return false; + if (dq[0] != a) return false; + if (dq[1] != b) return false; + if (dq[2] != c) return false; + return true; +} + +IdGlobalPool::IdGlobalPool() { + salvaged_.clear(); + next_batch_ = 0; + next_id_ = 0; + next_seqno_ = 0; +} + +IdGlobalPool::~IdGlobalPool() { +} + +void IdGlobalPool::init_master() { + salvaged_.clear(); + next_batch_ = 0x0001000000000000; + next_id_ = 0x0010000000000000; +} + +void IdGlobalPool::init_synch() { + salvaged_.clear(); + next_batch_ = 0; + next_id_ = 0x001E000000000000; +} + +int64_t IdGlobalPool::get_one() { + return next_id_++; +} + +int64_t IdGlobalPool::get_batch() { + int64_t batch; + if (salvaged_.empty()) { + if (next_batch_ == 0) { + batch = 0; + } else { + batch = next_batch_; + next_batch_ += 256; + } + } else { + batch = salvaged_.back(); + salvaged_.pop_back(); + } + return batch; +} + +void IdGlobalPool::salvage(int64_t batch) { + if (batch == 0) return; + if (next_batch_ == 0) return; + if ((batch & 0xFF) >= 128) return; + salvaged_.push_back(batch); +} + +void IdGlobalPool::serialize(StreamBuffer *sb) const { + sb->write_int64(next_batch_); + sb->write_int64(next_id_); + sb->write_uint64(next_seqno_); + sb->write_uint32(salvaged_.size()); + for (int64_t batch : salvaged_) { + sb->write_int64(batch); + } +} + +void IdGlobalPool::deserialize(StreamBuffer *sb) { + next_batch_ = sb->read_int64(); + next_id_ = sb->read_int64(); + next_seqno_ = sb->read_uint64(); + uint32_t salvaged_size = sb->read_uint32(); + salvaged_.resize(salvaged_size); + for (int i=0; i < int(salvaged_size); i++) { + salvaged_[i] = sb->read_int64(); + } +} + +eng::string IdGlobalPool::debug_string() const { + eng::ostringstream oss; + oss << "next_batch:" << util::hex64.val(next_batch_) << " "; + oss << "next_id:" << util::hex64.val(next_id_) << " "; + oss << "next_seqno: " << util::hex64.val(next_seqno_) << " "; + oss << "salvaged:"; + for (const int64_t val : salvaged_) { + oss << " " << util::hex64.val(val); + } + return oss.str(); +} + +IdPlayerPool::IdPlayerPool(IdGlobalPool *g) { + global_ = g; + fifo_capacity_ = 0; + next_seqno_ = 0; +} + +IdPlayerPool::~IdPlayerPool() { +} + +void IdPlayerPool::set_fifo_capacity(int n) { + assert((n >= 0) && (n <= 250)); + fifo_capacity_ = n; + while (int(ranges_.size()) > n) { + global_->salvage(ranges_.back()); + ranges_.pop_back(); + } +} + +void IdPlayerPool::refill() { + while (int(ranges_.size()) < fifo_capacity_) { + int64_t batch = global_->get_batch(); + if (batch == 0) break; + ranges_.push_back(batch); + } +} + +void IdPlayerPool::test_push_back(int64_t range) { + ranges_.push_back(range); +} + +void IdPlayerPool::test_pop_front() { + ranges_.pop_front(); +} + +void IdPlayerPool::test_clear_ranges() { + ranges_.clear(); +} + +int64_t IdPlayerPool::get_one() { + refill(); + if (ranges_.empty()) { + return global_->get_one(); + } else { + int64_t id = ranges_.front(); + if ((id & 0xFF) == 0xFF) { + ranges_.pop_front(); + refill(); + } else { + ranges_.front() = id + 1; + } + return id; + } +} + +void IdPlayerPool::serialize(StreamBuffer *sb) const { + sb->write_uint8(fifo_capacity_); + sb->write_uint8(ranges_.size()); + sb->write_uint64(next_seqno_); + for (int64_t batch : ranges_) { + sb->write_int64(batch); + } +} + +void IdPlayerPool::deserialize(StreamBuffer *sb) { + fifo_capacity_ = sb->read_uint8(); + int ranges_size = sb->read_uint8(); + next_seqno_ = sb->read_uint64(); + ranges_.resize(ranges_size); + for (int i=0; i < ranges_size; i++) { + ranges_[i] = sb->read_int64(); + } +} + +bool IdPlayerPool::exactly_equal(const IdPlayerPool &other) const { + if (fifo_capacity_ != other.fifo_capacity_) return false; + if (ranges_.size() != other.ranges_.size()) return false; + if (next_seqno_ != other.next_seqno_) return false; + for (int i = 0; i < int(ranges_.size()); i++) { + if (ranges_[i] != other.ranges_[i]) { + return false; + } + } + return true; +} + +bool IdPlayerPool::valid() const { + if ((fifo_capacity_ < 0) || (fifo_capacity_ > 250)) return false; + if (int(ranges_.size()) > fifo_capacity_) return false; + return true; +} + +void IdPlayerPool::diff(const IdPlayerPool &auth, StreamBuffer *sb) const { + assert(valid()); + assert(auth.valid()); + + // Special case: there's nothing to fix. + if (exactly_equal(auth)) { + sb->write_uint8(255); + return; + } + + // Write the fifo capacity, nranges, and next seqno + assert(auth.fifo_capacity_ != 255); + sb->write_uint8(auth.fifo_capacity_); + sb->write_uint8(auth.ranges_.size()); + sb->write_uint64(auth.next_seqno_); + + // Build up an index of the known IDs. + eng::map index; + for (int i = 0; i < int(ranges_.size()); i++) { + index[ranges_[i]] = i; + } + + // Write the ranges, but encode known IDs in one byte. + for (int i = 0; i < int(auth.ranges_.size()); i++) { + int64_t n = auth.ranges_[i]; + auto iter = index.find(n); + if (iter == index.end()) { + sb->write_uint8(255); + sb->write_int64(n); + } else { + int slot = iter->second; + sb->write_uint8(slot); + } + } +} + +void IdPlayerPool::patch(StreamBuffer *sb, DebugCollector *dbc) { + // read the header byte + int fifo_cap = sb->read_uint8(); + // If the fifo_cap is 255, it means there's nothing to fix. + if (fifo_cap == 255) { + return; + } + DebugLine(dbc) << "IdPlayerPool modified"; + fifo_capacity_ = fifo_cap; + int nranges = sb->read_uint8(); + next_seqno_ = sb->read_uint64(); + eng::deque old = std::move(ranges_); + ranges_.clear(); + for (int i = 0; i < nranges; i++) { + int index = sb->read_uint8(); + if (index < 255) { + assert(index < int(old.size())); + ranges_.push_back(old[index]); + } else { + ranges_.push_back(sb->read_int64()); + } + } +} + +eng::string IdPlayerPool::debug_string() const { + eng::ostringstream oss; + oss << "cap:" << fifo_capacity_ << " ids:"; + for (int i = 0; i < int(ranges_.size()); i++) { + if (i > 0) oss << ","; + oss << util::hex64.val(ranges_[i]); + } + oss << " seqno:" << util::hex64.val(next_seqno_); + return oss.str(); +} + +static int64_t nthbatch(int64_t n) { + return int64_t(0x0001000000000000) + n*256; +} + +LuaDefine(unittests_idalloc, "", "some unit tests") { + IdGlobalPool gp; + IdPlayerPool pp(&gp); + IdGlobalPool gpds; + IdPlayerPool ppds(&gpds); + StreamBuffer sb; + + // Synchronous pools produce IDs starting at 0x001E000000000000 + gp.init_synch(); + LuaAssert(L, gp.get_one() == 0x001E000000000000); + LuaAssert(L, gp.get_one() == 0x001E000000000001); + LuaAssert(L, gp.get_one() == 0x001E000000000002); + + // Master pools produce IDs starting at 0x0010000000000000 + gp.init_master(); + LuaAssert(L, gp.get_one() == 0x0010000000000000); + LuaAssert(L, gp.get_one() == 0x0010000000000001); + LuaAssert(L, gp.get_one() == 0x0010000000000002); + + // Synchronous pools produce only null batches. + gp.init_synch(); + LuaAssert(L, gp.get_batch() == 0); + LuaAssert(L, gp.get_batch() == 0); + gp.salvage(nthbatch(5)); + LuaAssert(L, gp.get_batch() == 0); + + // Simple fetch batches with a few salvages. + gp.init_master(); + LuaAssert(L, gp.get_batch() == nthbatch(0)); + LuaAssert(L, gp.get_batch() == nthbatch(1)); + LuaAssert(L, gp.get_batch() == nthbatch(2)); + gp.salvage(nthbatch(182)); + gp.salvage(nthbatch(183)); + LuaAssert(L, gp.get_batch() == nthbatch(183)); + LuaAssert(L, gp.get_batch() == nthbatch(182)); + LuaAssert(L, gp.get_batch() == nthbatch(3)); + + // Salvage of a zero-batch does nothing. + gp.init_master(); + LuaAssert(L, gp.get_batch() == nthbatch(0)); + LuaAssert(L, gp.get_batch() == nthbatch(1)); + gp.salvage(0); + LuaAssert(L, gp.get_batch() == nthbatch(2)); + + // Salvage of a partial batch. + gp.init_master(); + LuaAssert(L, gp.get_batch() == nthbatch(0)); + LuaAssert(L, gp.get_batch() == nthbatch(1)); + gp.salvage(nthbatch(142) + 10); + LuaAssert(L, gp.get_batch() == nthbatch(142) + 10); + LuaAssert(L, gp.get_batch() == nthbatch(2)); + + // Salvage of a half-empty batch does nothing. + gp.init_master(); + LuaAssert(L, gp.get_batch() == nthbatch(0)); + LuaAssert(L, gp.get_batch() == nthbatch(1)); + gp.salvage(nthbatch(142) + 145); + LuaAssert(L, gp.get_batch() == nthbatch(2)); + + // In the synchronous model, refill should do nothing. + // The player pool should shunt through to the global. + pp.test_clear_ranges(); + gp.init_synch(); + pp.set_fifo_capacity(3); + pp.refill(); + LuaAssert(L, pp.size() == 0); + LuaAssert(L, pp.get_one() == 0x001E000000000000); + LuaAssert(L, pp.size() == 0); + + // In the master model, with fifo disabled. Fifo should remain + // empty, and the player pool should shunt through to the global. + gp.init_master(); + pp.test_clear_ranges(); + pp.set_fifo_capacity(0); + pp.refill(); + LuaAssert(L, pp.size() == 0); + LuaAssert(L, pp.get_one() == 0x0010000000000000); + LuaAssert(L, pp.size() == 0); + + // Test refill from master (with enabled fifo). + gp.init_master(); + pp.test_clear_ranges(); + pp.set_fifo_capacity(3); + pp.refill(); + LuaAssert(L, ranges_equal(pp.ranges_, nthbatch(0), nthbatch(1), nthbatch(2))); + + // Now test that get_one() keeps the pool filled from master. + for (int i = 0; i < 256; i++) { + LuaAssert(L, pp.get_one() == nthbatch(0) + i); + } + for (int i = 0; i < 256; i++) { + LuaAssert(L, pp.get_one() == nthbatch(1) + i); + } + LuaAssert(L, ranges_equal(pp.ranges_, nthbatch(2), nthbatch(3), nthbatch(4))); + + // Test unqueueing the batches. + LuaAssert(L, gp.get_batch() == nthbatch(5)); + LuaAssert(L, gp.get_batch() == nthbatch(6)); + pp.set_fifo_capacity(0); + LuaAssert(L, gp.get_batch() == nthbatch(2)); + LuaAssert(L, gp.get_batch() == nthbatch(3)); + LuaAssert(L, gp.get_batch() == nthbatch(4)); + LuaAssert(L, gp.get_batch() == nthbatch(7)); + + // Serialize and deserialize a global pool. + gp.init_master(); + gpds.init_master(); + LuaAssert(L, gp.get_one() == 0x0010000000000000); + LuaAssert(L, gp.get_batch() == nthbatch(0)); + gp.salvage(nthbatch(182)); + gp.salvage(nthbatch(183)); + gp.serialize(&sb); + gpds.deserialize(&sb); + LuaAssert(L, gpds.get_one() == 0x0010000000000001); + LuaAssert(L, gpds.get_batch() == nthbatch(183)); + LuaAssert(L, gpds.get_batch() == nthbatch(182)); + LuaAssert(L, gpds.get_batch() == nthbatch(1)); + + // Serialize and deserialize a player pool. + gp.init_master(); + gpds.init_synch(); + LuaAssert(L, gp.get_batch() == nthbatch(0)); + pp.test_clear_ranges(); + pp.set_fifo_capacity(3); + pp.refill(); + ppds.test_clear_ranges(); + pp.serialize(&sb); + ppds.deserialize(&sb); + LuaAssert(L, ppds.get_fifo_capacity()==3); + LuaAssert(L, ppds.size() == 3); + LuaAssert(L, ranges_equal(ppds.ranges_, nthbatch(1), nthbatch(2), nthbatch(3))); + + // Difference transmit compare two empty pools. + gp.init_master(); + gpds.init_master(); + pp.test_clear_ranges(); + ppds.test_clear_ranges(); + pp.set_fifo_capacity(3); + ppds.set_fifo_capacity(3); + + // Check case: no differences. + sb.clear(); + ppds.diff(pp, &sb); + ppds.patch(&sb, nullptr); + LuaAssert(L, ppds.exactly_equal(pp)); + + // Add some values to master pool + pp.test_push_back(123); + pp.test_push_back(456); + + // transmit and compare. + sb.clear(); + ppds.diff(pp, &sb); + ppds.patch(&sb, nullptr); + LuaAssert(L, ppds.exactly_equal(pp)); + + // Pop a value from master pool + pp.test_pop_front(); + pp.test_push_back(789); + + // transmit and compare. + sb.clear(); + ppds.diff(pp, &sb); + ppds.patch(&sb, nullptr); + LuaAssert(L, ppds.exactly_equal(pp)); + + return 0; +} diff --git a/luprex/cpp/core/idalloc.hpp b/luprex/cpp/core/idalloc.hpp new file mode 100644 index 00000000..c59fa197 --- /dev/null +++ b/luprex/cpp/core/idalloc.hpp @@ -0,0 +1,221 @@ +/////////////////////////////////////////////////////////////////// +// +// THE ID ALLOCATOR +// +// This ID allocator's goal is to allocate IDs in such a way that the +// synchronous model gets the same IDs as the master model. +// +// DESIGN PRINCIPLES +// +// There are two classes defined here: IdGlobalPool, and IdPlayerPool. +// +// Every logged-in player maintains an IdPlayerPool. That's basically a fifo +// queue of ID batches. An ID batch is a contiguous range of IDs, containing +// between 128 and 256 contiguous IDs. The IdPlayerPool is difference +// transmitted, to ensure that the synchronous model has the same batches as the +// master model. +// +// When a player creates a thread, that thread gets an ID batch from the +// IdPlayerPool. To make this possible, the Lua runtime has been modified so +// that a thread can contain an ID batch. When that thread allocates IDs, it +// uses the batch it was allocated. In the unlikely event that a thread's batch +// is used up, the thread falls back to using the IdGlobalPool. Such fallback +// IDs are not likely to be predicted correctly. +// +// When a player creates a thread, he uses up one batch from his IdPlayerPool. +// In the master model, the player pool is 'refilled' by asking the IdGlobalPool +// to create a new batch. In the synchronous model, the IdPlayerPool is not +// directly refilled, but the difference transmitter effectively refills it. It +// is imperative that this difference transmission happen before the player's +// asynchronous model runs out of batches, otherwise we'll get prediction +// failures. +// +// It is common that a thread will only use 1 or 2 IDs. If a thread exits +// without using up most of its IDs, then the batch it contains is still a +// pretty usable batch. The batch gets returned to the IdGlobalPool, which will +// eventually use that salvaged batch to satisfy an IdPlayerPool refill request. +// +// THE NUMERIC RANGES +// +// The largest integer that can be stored losslessly in a lua_Number is: +// +// * 0x0020000000000000 +// +// In other words, any 53-bit number can be stored. We subdivide the range as +// follows: +// +// * 0x0000+ : manually created IDs. +// * 0x0001+ : used by master model's IdGlobalPool to create batches. +// * 0x0010+ : used by master model's IdGlobalPool to create individual IDs. +// * 0x001E+ : used by sync model's IdGlobalPool to create individual IDs. +// +// If you exhaust the Master Model's invididual pool at a rate of 10,000,000 IDs +// per second, the individual pool will last for 12 years. +// +// BATCH REPRESENTATION +// +// A batch is represented as a 64-bit integer. The batch contains all IDs +// starting with that integer, and incrementing, until the the two lowest digits +// are 0x00. +// +// For example, consider the batch ID 0x11111111111111FC. That batch includes +// these IDs: +// +// * 0x11111111111111FC +// * 0x11111111111111FD +// * 0x11111111111111FE +// * 0x11111111111111FF +// +// But it does not include 0x1111111111111200. +// +// As a special case, the number 0 is used to indicate "invalid batch". +// +// SEQUENCE NUMBERS +// +// If you allocate IDs from a player pool rapidly, you will exhaust the fifo in +// the player pool. As a result, ID allocation will fall back to the global +// pool, which will cause prediction failures. +// +// Depending on your requirements, you may be able to use sequence numbers +// instead of IDs. The player pool contains a monotonically increasing counter +// for generating sequence numbers. These can be allocated extremely fast +// and they'll never run out. These are highly predictable as long as the +// only person fetching sequence numbers is the player himself. The downside +// is that sequence numbers are not globally unique - they're only locally +// unique for a single player. That may be enough for your requirements. +// +// Currently, sequence numbers are used by the random number generator. +// +/////////////////////////////////////////////////////////////////// + +#ifndef IDALLOC_HPP +#define IDALLOC_HPP + +#include "wrap-map.hpp" +#include "wrap-sstream.hpp" +#include "wrap-deque.hpp" +#include "luastack.hpp" +#include "streambuffer.hpp" +#include "debugcollector.hpp" + +#include +#include + +class IdGlobalPool : public eng::nevernew { +public: + // Construct and destroy global pools. Note that after constructing + // a global pool, it is generally also necessary to initialize it + // for Master or Synchronous operation using init_master or init_synch. + // Any attempt to use a pool that hasn't been init_master or init_synch + // will fail. + IdGlobalPool(); + ~IdGlobalPool(); + + // Initialize the pool for use in a master model. + void init_master(); + + // Initialize the pool for use in a synchronous model. + void init_synch(); + + // Create a single unique ID. Ids allocated this way are + // unlikely to be predicted correctly. + int64_t get_one(); + + // Obtain a batch of IDs from the global pool. In a master + // model, the batch is guaranteed to contain at least 128 IDs. + // In a synchronous model, the batch is always zero (invalid). + int64_t get_batch(); + + // Try to return the specified batch to the global pool. + // The salvage operation quietly does nothing if the batch is + // zero, or the batch contains fewer than 128 IDs. + void salvage(int64_t batch); + + // Get a global sequence number. These are not the same as IDs. + // Sequence numbers are unlikely to be predicted correctly. + int64_t get_seqno() { return next_seqno_++; } + + // Serialize to or deserialize from a streambuffer. + void serialize(StreamBuffer *sb) const; + void deserialize(StreamBuffer *sb); + + // Generate a debug string. + eng::string debug_string() const; + +private: + eng::vector salvaged_; + int64_t next_batch_; + int64_t next_id_; + int64_t next_seqno_; + friend int unittests_idalloc(lua_State *L); +}; + +class IdPlayerPool : public eng::nevernew { +public: + // Construct a player pool. + // + // The fifo is initially in the disabled state. In the disabled state, the + // fifo is not kept filled. You can still use the allocator when the fifo is + // disabled - doing so just fetches a batch from the global pool. + // + IdPlayerPool(IdGlobalPool *g); + ~IdPlayerPool(); + + // Set the fifo capacity. Max=255. + void set_fifo_capacity(int n); + int get_fifo_capacity() const { return fifo_capacity_; } + + // Return all batches from the fifo to the global pool. + void unqueue(); + + // Refill the fifo of batches from the global pool. + void refill(); + + // Get a single ID from the fifo. Also refills the fifo. + int64_t get_one(); + + // Get a player sequence number. This is not the same as an ID: player + // sequence numbers are unique per player, but not globally. + int64_t get_seqno() { return next_seqno_++; } + + // Return the size of the queue. + int size() { return ranges_.size(); } + + // Return true if the two pools are identical. + bool exactly_equal(const IdPlayerPool &other) const; + + // Check that the pool is valid. + bool valid() const; + + // unit testing functions. + // + // These let you manipulate the deque explicitly. But that's + // only useful for unit testing. + // + void test_push_back(int64_t range); + void test_pop_front(); + void test_clear_ranges(); + + // Serialize to or deserialize from a streambuffer. + // Caution: the pointer to the global pool is not serialized or deserialized. + void serialize(StreamBuffer *sb) const; + void deserialize(StreamBuffer *sb); + + // Difference transmission + // + void diff(const IdPlayerPool &auth, StreamBuffer *sb) const; + void patch(StreamBuffer *sb, DebugCollector *dbc); + + // Debug string. + eng::string debug_string() const; + +private: + IdGlobalPool *global_; + int fifo_capacity_; + int64_t next_seqno_; + eng::deque ranges_; + friend int lfn_unittests_idalloc(lua_State *L); +}; + +#endif // IDALLOC_HPP + diff --git a/luprex/cpp/core/invocation.cpp b/luprex/cpp/core/invocation.cpp new file mode 100644 index 00000000..0d63497f --- /dev/null +++ b/luprex/cpp/core/invocation.cpp @@ -0,0 +1,47 @@ + +#include "wrap-string.hpp" +#include "wrap-map.hpp" +#include "wrap-deque.hpp" +#include "wrap-sstream.hpp" + +#include "invocation.hpp" + +Invocation::Invocation() : kind_(InvocationKind::INVALID), actor_(0), place_(0) {} + +Invocation::Invocation(InvocationKind kind, int64_t actor, int64_t place, std::string_view datapack) + : kind_(kind), actor_(actor), place_(place), datapack_(datapack) {} + + +void Invocation::serialize(StreamBuffer *sb) const { + sb->write_uint8(int64_t(kind_)); + sb->write_int64(actor_); + sb->write_int64(place_); + sb->write_string(datapack_); +} + +void Invocation::deserialize(StreamBuffer *sb) { + kind_ = InvocationKind(sb->read_uint8()); + actor_ = sb->read_int64(); + place_ = sb->read_int64(); + datapack_ = sb->read_string(); +} + +eng::string Invocation::debug_string() const { + eng::ostringstream oss; + oss << "inv["; + switch (kind_) { + case InvocationKind::INVALID: oss << "invalid"; break; + case InvocationKind::LUA_INVOKE: oss << "lua_invoke"; break; + case InvocationKind::LUA_PROBE: oss << "lua_probe"; break; + case InvocationKind::LUA_EXPR: oss << "lua_expr"; break; + case InvocationKind::LUA_SOURCE: oss << "lua_source"; break; + case InvocationKind::FLUSH_PRINTS: oss << "flush_prints"; break; + case InvocationKind::TICK: oss << "tick"; break; + default: oss << "UNKNOWN"; break; + } + oss << " a=" << actor_; + oss << " p=" << place_; + oss << " dp=" << datapack_.size() << " bytes"; + oss << "]"; + return oss.str(); +} diff --git a/luprex/cpp/core/invocation.hpp b/luprex/cpp/core/invocation.hpp new file mode 100644 index 00000000..3ade9986 --- /dev/null +++ b/luprex/cpp/core/invocation.hpp @@ -0,0 +1,77 @@ +////////////////////////////////////////////////////////////////////////// +// +// DATAPACK +// +// Invocations contain a field 'datapack' which contains additional +// data for the invocation, packed up in a serialized format. Executing +// the invocation involves unpacking the datapack. +// +// The actual contents of the datapack depends on the type of invocation: +// +// InvocationKind::INVALID: +// +// Nothing. +// +// InvocationKind::LUA_CALL: +// +// A lua function call. The class name, the function name, and then +// the function arguments. +// +// InvocationKind::LUA_EXPR: +// +// A block of lua source code, in plaintext. +// +// InvocationKind::FLUSH_PRINTS: +// +// Line number in ascii. +// +// InvocationKind::TICK: +// +// Nothing. +// +// InvocationKind::LUA_SOURCE: +// +// Packaged lua sourcecode. See drvutil::package_lua_source. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef INVOCATION_HPP +#define INVOCATION_HPP + +#include "wrap-string.hpp" +#include "wrap-map.hpp" +#include "wrap-deque.hpp" +#include "streambuffer.hpp" +#include "enginewrapper.hpp" + + +class Invocation : public eng::opnew { +public: +private: + InvocationKind kind_; + int64_t actor_; + int64_t place_; + eng::string datapack_; +public: + Invocation(); + Invocation(InvocationKind kind, int64_t actor, int64_t place, std::string_view datapack); + + bool valid() const { return kind_ != InvocationKind::INVALID; } + InvocationKind kind() const { return kind_; } + int64_t actor() const { return actor_; } + int64_t place() const { return place_; } + const eng::string &datapack() const { return datapack_; } + + void serialize(StreamBuffer *sb) const; + void deserialize(StreamBuffer *sb); + + eng::string debug_string() const; +}; + +using UniqueInvocation = std::unique_ptr; + +class InvocationQueue : public eng::deque { +}; + + +#endif // INVOCATION_HPP diff --git a/luprex/cpp/core/json.cpp b/luprex/cpp/core/json.cpp new file mode 100644 index 00000000..cb7d1625 --- /dev/null +++ b/luprex/cpp/core/json.cpp @@ -0,0 +1,729 @@ +#include "json.hpp" +#include "luastack.hpp" +#include "util.hpp" +#include +#include +#include +#include +#include +#include + + +#define NOINDENT_LEVEL 1000 + +LuaTokenConstant(json_null, "null", ""); +LuaTokenConstant(json_object, "object", ""); +LuaTokenConstant(json_error, "error", ""); + +static void indent(eng::ostringstream &oss, int level) { + if (level < NOINDENT_LEVEL) { + oss << std::endl; + for (int i = 0; i < level; i++) { + oss << " "; + } + } +} + +static bool length_exceeded(eng::ostringstream &oss, int maxlen) { + return oss.tellp() > maxlen; +} + +template +inline void store_error(eng::ostringstream &oss, const ARGS & ... args) { + oss.str(""); + util::send_to_stream(oss, args...); +} + +static void store_length_error(eng::ostringstream &oss, int maxlen) { + store_error(oss, "maximum json length exceeded: ", maxlen); +} + +static bool use_array_representation(lua_State *L) { + int top = lua_gettop(L); + int nfound = 0; + while (true) { + lua_rawgeti(L, top, nfound + 1); + bool null = lua_isnil(L, -1); + lua_settop(L, top); + if (null) break; + nfound += 1; + } + return (nfound == lua_nkeys(L, top)); +} + +static bool encode_key(lua_State *L, eng::ostringstream &oss); +static bool encode_value(lua_State *L, eng::ostringstream &oss, int level, int maxlen); + + +// The goal here is to emit a double in such a way that +// when we read it back in, we get the *exact* same number. +// +// In the worst case, you can accomplish this by using 17 +// digits of precision - that's enough to uniquely identify +// all double values (see the following URL). However, 17 +// digits tends to produce unnecessary repeating decimals. +// So we try 16 digits first, which tends to remove those +// repeating decimals, but sometimes produces losses. +// If that doesn't work, we fall back to 17 digits. +// +// https://randomascii.wordpress.com/2012/03/08/float-precisionfrom-zero-to-100-digits-2/ +// +static void encode_double_lossless(double value, eng::ostringstream &oss) { + char buffer[80]; + sprintf(buffer, "%.16g", value); + if (strtod(buffer, nullptr) != value) { + sprintf(buffer, "%.17g", value); + assert(strtod(buffer, nullptr) == value); + } + oss << buffer; +} + +static bool encode_nil(lua_State *L, eng::ostringstream &oss) { + oss << "null"; + return true; +} + +static bool encode_token(lua_State *L, eng::ostringstream &oss) { + LuaToken token(lua_touserdata(L, -1)); + if (token == ltoken_json_null) { + oss << "null"; + return true; + } else { + store_error(oss, "cannot encode token: [", token.str(), "]"); + return false; + } +} + +static bool encode_number(lua_State *L, eng::ostringstream &oss) { + lua_Number value = lua_tonumber(L, -1); + if (std::isnan(value) || std::isinf(value)) { + store_error(oss, "cannot encode infinity or NAN"); + return false; + } + int64_t ivalue = int64_t(value); + if (double(ivalue) == value) { + oss << ivalue; + } else { + encode_double_lossless(value, oss); + } + return true; +} + +static bool encode_number_key(lua_State *L, eng::ostringstream &oss) { + lua_Number value = lua_tonumber(L, -1); + int64_t ivalue = int64_t(value); + if (double(ivalue) != value) { + store_error(oss, "cannot encode floating point numbers in table keys"); + return false; + } + if (ivalue >= 0) { + oss << "\"\\uE000+" << ivalue << '"'; + } else { + oss << "\"\\uE000-" << -ivalue << '"'; + } + return true; +} + +static bool encode_boolean(lua_State *L, eng::ostringstream &oss) { + int flag = lua_toboolean(L, -1); + oss << (flag ? "true" : "false"); + return true; +} + +static bool encode_string(lua_State *L, eng::ostringstream &oss) { + size_t len; + const char *s = lua_tolstring(L, -1, &len); + std::string_view str(s, len); + oss << '"'; + if (sv::valid_utf8(str) && !sv::has_prefix(str, "")) { + while (!str.empty()) { + int32_t cp = sv::read_codepoint_utf8(str); + assert(cp >= 0); + switch (cp) { + case '\\': oss << "\\\\"; break; + case '"' : oss << "\\\""; break; + case '\b': oss << "\\b"; break; + case '\f': oss << "\\f"; break; + case '\r': oss << "\\r"; break; + case '\n': oss << "\\n"; break; + case '\t': oss << "\\t"; break; + default: { + if (cp < 32) { + oss << "\\u" << util::hex16.val(cp); + } else { + bool ok = util::write_codepoint_utf8(cp, &oss); + assert(ok); + } + } + } + } + } else { + // Output as a base64-encoded string. + oss << "\\uE000="; + util::base64_encode(str, &oss); + } + oss << '"'; + return true; +} + +static bool encode_array(lua_State *L, eng::ostringstream &oss, int level, int maxlen) { + lua_checkstack(L, 20); + int top = lua_gettop(L); + oss << "["; + level ++; + int i = 1; + while (true) { + lua_rawgeti(L, top, i); + if (lua_isnil(L, -1)) break; + if (i > 1) oss << ","; + indent(oss, level); + bool ok = encode_value(L, oss, level, maxlen); + lua_settop(L, top); + if (!ok) return false; + if (length_exceeded(oss, maxlen)) { + store_length_error(oss, maxlen); + return false; + } + i += 1; + } + lua_settop(L, top); + level --; + indent(oss, level); + oss << "]"; + return true; +} + +static bool encode_object(lua_State *L, eng::ostringstream &oss, int level, int maxlen) { + lua_checkstack(L, 20); + int top = lua_gettop(L); + oss << "{"; + level ++; + lua_pushnil(L); + int i = 1; + while (lua_next(L, top) != 0) { + // Check for [json.object]=true, if so skip. + if (lua_islightuserdata(L, -2) && + lua_isboolean(L, -1) && + (LuaToken(lua_touserdata(L, -2)) == LuaToken("object")) && + (lua_toboolean(L, -1) == 1)) { + lua_pop(L, 1); + continue; + } + + lua_pushvalue(L, -2); + // Stack now has key, value, key + assert(lua_gettop(L) == top + 3); + if (i > 1) oss << ","; + indent(oss, level); + bool ok = encode_key(L, oss); + if (!ok) { + lua_settop(L, top); + return false; + } + if (length_exceeded(oss, maxlen)) { + store_length_error(oss, maxlen); + lua_settop(L, top); + return false; + } + lua_pop(L, 1); + // Stack now has key, value + assert(lua_gettop(L) == top + 2); + oss << ((level < NOINDENT_LEVEL) ? " : " : ":"); + ok = encode_value(L, oss, level, maxlen); + assert(lua_gettop(L) == top + 2); + if (!ok) { + lua_settop(L, top); + return false; + } + if (length_exceeded(oss, maxlen)) { + store_length_error(oss, maxlen); + lua_settop(L, top); + return false; + } + lua_pop(L, 1); + // Stack now just has key. + assert(lua_gettop(L) == top + 1); + i += 1; + } + // Stack should be back to where we started. + assert(lua_gettop(L) == top); + level --; + indent(oss, level); + oss << "}"; + return true; +} + +static bool encode_key(lua_State *L, eng::ostringstream &oss) { + int type = lua_type(L, -1); + switch (type) { + case LUA_TSTRING: return encode_string(L, oss); + case LUA_TNUMBER: return encode_number_key(L, oss); + case LUA_TBOOLEAN: + case LUA_TTABLE: { + store_error(oss, "cannot encode '", lua_typename(L, type), "' in table keys"); + return false; + } + default: { + store_error(oss, "cannot encode '", lua_typename(L, type), "'"); + return false; + } + } +} + +static bool encode_value(lua_State *L, eng::ostringstream &oss, int level, int maxlen) { + int type = lua_type(L, -1); + switch (type) { + case LUA_TNIL: return encode_nil(L, oss); + case LUA_TNUMBER: return encode_number(L, oss); + case LUA_TBOOLEAN: return encode_boolean(L, oss); + case LUA_TSTRING: return encode_string(L, oss); + case LUA_TLIGHTUSERDATA: return encode_token(L, oss); + case LUA_TTABLE: { + if (use_array_representation(L)) { + return encode_array(L, oss, level, maxlen); + } else { + return encode_object(L, oss, level, maxlen); + } + } + default: { + store_error(oss, "cannot encode '", lua_typename(L, type), "'"); + return false; + } + } +} + +static bool decode_value(lua_State *L, std::string_view &v); + +static bool decode_id(lua_State *L, std::string_view &v) { + std::string_view id = sv::read_ascii_identifier(v); + if (id == "null") lua_pushlightuserdata(L, LuaToken("null").voidvalue()); + else if (id == "true") lua_pushboolean(L, 1); + else if (id == "false") lua_pushboolean(L, 0); + else return false; + return true; +} + +static bool decode_number(lua_State *L, std::string_view &v) { + std::string_view n = sv::read_number(v, true, true, true, true); + if (n.empty()) return false; + + // If it's an integer, make sure it fits in a lua double + // losslessly. If it's a double, some loss in precision + // is OK. + if (sv::valid_number(n, true, true, false, false)) { + int64_t i = sv::to_int64(n); + if (!LuaCoreStack::validinteger(i)) return false; + lua_pushnumber(L, double(i)); + return true; + } else { + double d = sv::to_double(n); + if (std::isnan(d) || std::isinf(d)) return false; + lua_pushnumber(L, d); + return true; + } +} + +static bool decode_base64_string(lua_State *L, std::string_view &v) { + // We've already read the starting quote and the E000 + // escape sequence at this point. + + // Skip the equal sign. + if (!sv::read_prefix(v, "=")) return false; + + // Find the end of the quoted string. + const char *p = v.data(); + const char *l = p + v.size(); + while (true) { + if (p == l) return false; + if (*p < 32) return false; + if (*p == '"') break; + p++; + } + std::string_view b64 = v.substr(0, p - v.data()); + v.remove_prefix(b64.size() + 1); + eng::ostringstream oss; + if (!util::base64_decode(b64, &oss)) return false; + eng::string str = oss.str(); + lua_pushlstring(L, str.c_str(), str.size()); + return true; +} + +static bool decode_int_string(lua_State *L, std::string_view &v) { + // We've already read the starting quote and the E000 + // escape sequence at this point. + + // Parse the number and the closing quote. + std::string_view n = sv::read_number(v, true, true, false, false); + if (n.empty()) return false; + if (!sv::read_prefix(v, "\"")) { + return false; + } + + // Make sure the number fits in a lua double, + // and push it on the stack. + int64_t i = sv::to_int64(n); + if (!LuaCoreStack::validinteger(i)) { + return false; + } + lua_pushnumber(L, double(i)); + return true; +} + +static bool decode_standard_string(lua_State *L, std::string_view &v) { + // We've already read the starting quote at this point. + eng::ostringstream oss; + while (true) { + // Get the next codepoint. + int32_t c = sv::read_codepoint_utf8(v); + + // If it's a control character or invalid codepoint, reject. + if (c < 32) return false; + + // If it is an unescaped quote, that's end of string. + if (c == '"') break; + + // If it's a backslash, then deal with the escape sequence. + if (c == '\\') { + char next = sv::read_ascii_char(v); + switch (next) { + case '"': oss << '"'; break; + case '\\': oss << '\\'; break; + case '/': oss << '/'; break; + case 'r': oss << '\r'; break; + case 'n': oss <<'\n'; break; + case 'b': oss << '\b'; break; + case 'f': oss << '\f'; break; + case 't': oss << '\t'; break; + case 'u': { + std::string_view hexdigits = sv::read_nbytes(v, 4); + if (hexdigits.size() != 4) return false; + uint64_t codepoint = sv::to_hex64(hexdigits, 0x10000); + if (codepoint >= 0x10000) return false; + if (!util::write_codepoint_utf8(codepoint, &oss)) return false; + break; + } + default: return false; + } + continue; + } + + // Any other codepoint should be echoed into stream. + util::write_codepoint_utf8(c, &oss); + } + eng::string result = oss.str(); + lua_pushlstring(L, result.c_str(), result.size()); + return true; +} + +static bool decode_string(lua_State *L, std::string_view &v) { + if (!sv::read_prefix(v, "\"")) return false; + + // Check for codepoint E000, the escape sequence. + if (sv::read_prefix(v, "") || + sv::read_prefix(v, "\\uE000") || + sv::read_prefix(v, "\\ue000")) { + char c = sv::zfront(v); + if (c == '=') return decode_base64_string(L, v); + else if ((c=='-') || (c=='+')) return decode_int_string(L, v); + else return false; + } else { + return decode_standard_string(L, v); + } +} + +static bool decode_array(lua_State *L, std::string_view &v) { + if (!sv::read_prefix(v, "[")) return false; + lua_newtable(L); + int tabpos = lua_gettop(L); + int next = 1; + while (true) { + v = sv::ltrim(v); + if (sv::zfront(v) == ']') { + v.remove_prefix(1); + return true; + } + if (!decode_value(L, v)) { + return false; + } + v = sv::ltrim(v); + if (sv::zfront(v) == ',') { + v.remove_prefix(1); + } + lua_rawseti(L, tabpos, next++); + } +} + +static bool decode_object(lua_State *L, std::string_view &v) { + if (!sv::read_prefix(v, "{")) return false; + lua_newtable(L); + int tabpos = lua_gettop(L); + while (true) { + v = sv::ltrim(v); + if (sv::zfront(v) == '}') { + v.remove_prefix(1); + return true; + } + if (!decode_string(L, v)) { + return false; + } + v = sv::ltrim(v); + if (!sv::read_prefix(v, ":")) { + return false; + } + if (!decode_value(L, v)) { + return false; + } + v = sv::ltrim(v); + if (sv::zfront(v) == ',') { + v.remove_prefix(1); + } + lua_rawset(L, tabpos); + } +} + +// Decode a single value. +// +// On success, pushes the value on the stack and returns true. +// On failure, pushes NIL on the stack and returns false. +// +static bool decode_value(lua_State *L, std::string_view &v) { + lua_checkstack(L, 20); + int top = lua_gettop(L); + + // Skip blanks. + v = sv::ltrim(v); + + // Try to read something. + char c = sv::zfront(v); + bool result; + if (c == '"') result = decode_string(L, v); + else if (c == '[') result = decode_array(L, v); + else if (c == '{') result = decode_object(L, v); + else if (sv::ascii_isalpha(c)) result = decode_id(L, v); + else result = decode_number(L, v); + + // On failure, the decode routines may leave junk + // on the stack, in which case it's our job to clean up. + if (result == false) { + lua_settop(L, top); + lua_pushnil(L); + } + + // Now there should be exactly one new value on the stack. + assert(lua_gettop(L) == top + 1); + return result; +} + +namespace json { + +eng::string encode(LuaCoreStack &LS, LuaSlot in, eng::string &out, bool indent, int maxlen) { + eng::ostringstream oss; + + // Call the recursive encoder. Clean up any crap on the lua stack afterward. + int top = lua_gettop(LS.state()); + lua_pushvalue(LS.state(), in.index()); + bool ok = encode_value(LS.state(), oss, indent ? 0 : NOINDENT_LEVEL, maxlen); + lua_settop(LS.state(), top); + + // One last check for overruns. + if (ok && length_exceeded(oss, maxlen)) { + store_length_error(oss, maxlen); + ok = false; + } + + // Produce the return value. + if (ok) { + out = oss.str(); + return ""; + } else { + out = ""; + return oss.str(); + } +} + +bool decode(LuaCoreStack &LS, LuaSlot out, std::string_view v) { + lua_State *L = LS.state(); + + // Try to read a single value from the view. + int top = lua_gettop(L); + bool ok = decode_value(L, v); + lua_replace(L, out.index()); + lua_settop(L, top); + if (!ok) { + LS.set(out, LuaToken("error")); + return false; + } + + // Special case: if the top level value is 'null', change + // it to 'nil.' + if (LS.istoken(out)) { + LS.set(out, LuaNil); + } + + // There should be nothing left of the input text. + if (v.size() > 0) { + LS.set(out, LuaToken("error")); + return false; + } + return true; +} + +} // namespace util + + +LuaDefine(json_encode, "data, indent, maxlen", + "|Encode a lua data structure returning a json string." + "|" + "|Data is the value being encoded. Indent is a flag," + "|if it's true, then the json is indented nicely," + "|otherwise, it is packed tightly. Maxlen is the maximum" + "|length in bytes of the encoded json string." + "|" + "|Usually, Lua data translates straightforwardly to json." + "|However, there are a number of special cases to be" + "|aware of:" + "|" + "|- Closures and threads cannot be encoded. These will" + "| cause the encoder to abort." + "|" + "|- The numbers infinity and NAN cannot be encoded." + "| Both of these will cause the encoder to abort." + "|" + "|- You must specify a size-limit to the encoded" + "| string. Exceeding the size limit causes the" + "| encoder to abort." + "|" + "|- Recursive data structures will cause the encoder to" + "| loop infinitely until the size-limit is exceeded," + "| causing the encoder to abort." + "|" + "|- There is no way to represent math.huge or math.nan in" + "| json. Encoding math.nan will cause the encoder to abort," + "| as expected. However, encoding math.huge will emit null," + "| which is probably not what you would expect." + "|" + "|- Lua tables cannot contain 'nil', but json objects and" + "| arrays can contain null. If you want the encoder to" + "| emit a json object or array containing null, you must" + "| use token json.null to represent null." + "|" + "|- Json objects, like lua tables, are key-value stores." + "| However, json objects can only have string keys. Our" + "| encoder uses a workaround to transparently" + "| allow mixing string and integer keys in json tables." + "| See 'encoding difficult data' below." + "|" + "|- Json strings are required to be valid utf-8. Our encoder" + "| uses a workaround to transparently allow the use of" + "| arbitrary 8-bit-clean strings. See 'encoding difficult" + "| data' below." + "|" + "|- Lua tables containing contiguous integer keys from 1-n are" + "| autodetected to be json arrays. Empty tables are also" + "| emitted as json arrays. All other tables are emitted" + "| as json objects." + "|" + "|- You can force a table to be emitted as a json object" + "| by putting the key-value pair table[json.object]=true" + "| into the table. This special key is not emitted, but" + "| it triggers json object mode. This is the only way" + "| to emit an empty json object (a truly empty table is" + "| emitted as a json array.)" + "|" + "|Encoding Difficult Data:" + "|" + "|Normally, json doesn't allow integer table keys, and it" + "|doesn't allow strings that aren't valid utf-8. Our" + "|json encoder and decoder, on the other hand, can" + "|encode and decode integer table keys and 8-bit-clean" + "|strings transparently. This is accomplished without" + "|violating the json specification, by encoding such" + "|values as utf-8 strings:" + "|" + "| '123' (encoded integer 123)" + "| '=aGVsbG8=' (binary string encoded as base64)" + "|" + "|Those encodings start with utf-8 codepoint E000." + "|This codepoint probably shows up in your text editor" + "|as a little rectangle. When the decoder sees codepoint" + "|E000 at the beginning of a string, it automatically" + "|decodes the string back into its original form." + "|" + "|The one price for this behavior is that the encoder" + "|cannot literally emit strings that start" + "|with codepoint E000. If the encoder detects such a" + "|string, it will emit it as a base64-encoded string." + "|This should be uncommon, since codepoint E000 is" + "|reserved." + "|" + "|Note that integers are only encoded when they are" + "|used as table keys. Otherwise, numbers are emitted" + "|straightforwardly." + "|") { + LuaArg data, indent, maxlen; + LuaRet encoded; + LuaDefStack LS(L, data, indent, maxlen, encoded); + eng::string out; + eng::string error = json::encode(LS, data, out, LS.ckboolean(indent), LS.ckint(maxlen)); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + LS.set(encoded, LuaNil); + return LS.result(); + } else { + LS.set(encoded, out); + return LS.result(); + } +} + +LuaDefine(json_decode, "data", + "|Decode a json expression into a lua data structure." + "|" + "|Data that was generated by our own encoder is almost" + "|8-bit clean. That includes difficult cases, like" + "|binary strings, floating point numbers, and tables" + "|with mixed string and integer keys. The exception" + "|are the kinds of data that can't be encoded at all:" + "|See doc(json.encode) for details about what" + "|can and cannot be encoded." + "|" + "|Some json may contain 'null' inside objects and" + "|arrays. Lua tables can't store nil, so instead, we" + "|store the token json.null. If that's not what you" + "|want, you can use json.stripnulls to strip out" + "|the json.null values from a data structure and" + "|replace them with nil." + "|" + "|") { + LuaArg encoded; + LuaRet data; + LuaDefStack LS(L, encoded, data); + std::string_view v = LS.ckstringview(encoded); + bool ok = json::decode(LS, data, v); + if (!ok) { + luaL_error(L, "invalid json string."); + } + return LS.result(); +} + +// LuaDefine(base64_encode, "data", "") { +// LuaArg str; +// LuaRet ret; +// LuaDefStack LS(L, str, ret); +// eng::string cstr = LS.ckstring(str); +// eng::ostringstream oss; +// util::base64_encode(cstr, &oss); +// LS.set(ret, oss.str()); +// return LS.result(); +// } + +// LuaDefine(base64_decode, "data", "") { +// LuaArg str; +// LuaRet ret; +// LuaDefStack LS(L, str, ret); +// eng::string cstr = LS.ckstring(str); +// eng::ostringstream oss; +// util::base64_decode(cstr, &oss); +// LS.set(ret, oss.str()); +// return LS.result(); +// } + diff --git a/luprex/cpp/core/json.hpp b/luprex/cpp/core/json.hpp new file mode 100644 index 00000000..35e06cd0 --- /dev/null +++ b/luprex/cpp/core/json.hpp @@ -0,0 +1,34 @@ +// Encode lua data structure into json, and decode them again. +// +// See the doc(http.jsonencode) to read about limitations of the encoder. +// +#ifndef JSON_HPP +#define JSON_HPP + +#include "luastack.hpp" +#include "wrap-string.hpp" +#include + +namespace json { + // Encode json. + // + // See doc(http.jsonencode) for a lot more information. + // + // Returns an error message. If the error message is an + // empty string, then the encoding was successful. + // + eng::string encode(LuaCoreStack &LS, LuaSlot in, eng::string &out, bool indent, int maxlen); + + // Decode json. + // + // See doc(http.jsondecode) for a lot more information. + // + // The only error condition is syntactically invalid json. + // In that case, we return false and set 'out' to the + // token 'error'. + // + bool decode(LuaCoreStack &LS, LuaSlot out, std::string_view in); +} + +#endif // JSON_HPP + diff --git a/luprex/cpp/core/lpxclient.cpp b/luprex/cpp/core/lpxclient.cpp new file mode 100644 index 00000000..d0cb1cdd --- /dev/null +++ b/luprex/cpp/core/lpxclient.cpp @@ -0,0 +1,300 @@ +#include "wrap-string.hpp" +#include "wrap-vector.hpp" + +#include "drivenengine.hpp" +#include "world.hpp" +#include "luaconsole.hpp" +#include "invocation.hpp" +#include "util.hpp" +#include "printbuffer.hpp" + +#include + +class LpxClient : public DrivenEngine, public CommonCommands { +public: + using StringVec = LuaConsole::StringVec; + UniqueWorld world_; + int64_t actor_id_; + InvocationQueue unack_; + SharedChannel channel_; + LuaConsole console_; + PrintChanneler print_channeler_; + eng::vector delayed_invocations_; + +public: + void set_initial_state_connect(const eng::string &hostspec) { + // Create the world model. + world_.reset(new World(WORLD_TYPE_PREDICTIVE)); + + // Create the communication channel. + channel_ = new_outgoing_channel(hostspec); + + // This is a temporary actor that will be used only until the server sends + // us the first difference transmission. We do this only to establish + // the invariant that there's always an actor. When the first difference + // transmission arrives, this actor may be deleted, or it may just be + // ignored, at the server's discretion. + actor_id_ = world_->create_login_actor(); + + // Clear the unack command queue. + unack_.clear(); + + // Export stuff to the graphics engine. + set_visible_world_and_actor(world_.get(), actor_id_); + + // Reset the print channeler + print_channeler_.reset(); + } + + void set_initial_state_standalone(std::string_view srcpk) { + // Create the world model. + world_.reset(new World(WORLD_TYPE_MASTER)); + + // Update the source code of the master model. + world_->update_source(srcpk); + + // Make sure the channel is empty. + channel_.reset(); + + // Create the standalone actor. + actor_id_ = world_->create_login_actor(); + + // TODO: initialize the standalone actor. + + // Clear the unack command queue. + unack_.clear(); + + // Export stuff to the graphics engine. + set_visible_world_and_actor(world_.get(), actor_id_); + + // Reset the print channeler + print_channeler_.reset(); + } + + + // When the world is in synchronous mode, there's no + // snapshot, and the commands in the unack queue are not + // reflected in the world. In asynchronous mode, there is + // a snapshot that allows return to the synchronous mode, + // and the unack commands are in the world model. + + void world_to_synchronous() { + if (!world_->snapshot_empty()) { + world_->rollback(); + } + } + + void world_to_asynchronous() { + if (!world_->is_authoritative()) { + if (world_->snapshot_empty()) { + world_->snapshot(); + for (const Invocation &inv : unack_) { + world_->invoke(inv); + } + } + } + } + + void abandon_server() { + // When we abandon the server, we leave the world model + // hanging around to preserve the invariant that there + // is always a world model. Then, we trigger a rescan + // of the lua source. When the lua source shows up, then + // we will create a standalone model to replace the client + // model. + + channel_.reset(); + rescan_lua_source(); + } + + virtual void event_init(std::string_view srcpk, int argc, char *argv[]) { + // Put the world into the starting state. + set_initial_state_standalone(srcpk); + + // Set the console prompt + set_console_prompt(console_.get_prompt()); + } + + void send_invocation(const Invocation &inv) { + if (channel_ == nullptr) { + if ((!world_->is_authoritative()) && (inv.kind() == InvocationKind::LUA_SOURCE)) { + // We have a client model, but no client connection. That means we're + // in the process of shutting down a client model. The client model + // is supposed to linger until the lua source is reread. Once we have + // the lua source, we're supposed to throw out the client model and + // create a standalone model. + set_initial_state_standalone(inv.datapack()); + } else { + world_->invoke(inv); + } + } else { + world_to_asynchronous(); + world_->invoke(inv); + unack_.push_back(inv); + StreamBuffer *sb = channel_->out(); + sb->write_uint8(util::MSG_INVOKE); + inv.serialize(sb); + } + } + + virtual void do_syntax_error(std::string_view error) override { + stdostream() << "Syntax error: " << error << std::endl; + } + + virtual void do_unknown_command(std::string_view name) override { + stdostream() << "Unknown command: " << name << std::endl; + } + + virtual void do_view_command() override { + stdostream() << world_->tangibles_near_debug_string(actor_id_, 1000); + } + + virtual void do_moveto_command(int x, int y) override { + do_unknown_command("moveto"); + } + + virtual void do_quit_command() override { + abandon_server(); + stop_driver(); + } + + virtual void do_cpl_command() override { + rescan_lua_source(); + } + + virtual void do_work_command() override { + do_unknown_command("work"); + } + + virtual void do_display_command() override { + do_unknown_command("display"); + } + + virtual void do_aborthttp_command() override { + do_unknown_command("aborthttp"); + } + + virtual void do_connect_command(std::string_view hostname) override { + set_initial_state_connect(util::ss("nocert:", hostname, ":8085")); + } + + virtual void do_luainvoke_command(std::string_view cmd) override { + send_invocation(Invocation(InvocationKind::LUA_EXPR, actor_id_, actor_id_, cmd)); + } + + virtual void do_luaprobe_command(std::string_view cmd) override { + world_to_asynchronous(); + stdostream() << world_->probe_lua_expr(actor_id_, cmd); + world_to_synchronous(); + } + + void change_actor_id(int64_t actor_id) { + stdostream() << "Actor ID changing: " << actor_id << std::endl; + print_channeler_.reset(); + actor_id_ = actor_id; + set_visible_world_and_actor(world_.get(), actor_id_); + } + + void receive_ack_from_server(StreamBuffer *sb) { + // An ack is just a single byte, so there's nothing left to read. + if (unack_.empty()) { + // Invalid acknowledgement when theres' nothing in the unack queue. + abandon_server(); + return; + } + world_to_synchronous(); + world_->invoke(unack_.front()); + unack_.pop_front(); + } + + void receive_diff_from_server(StreamBuffer *sb) { + world_to_synchronous(); + try { + DebugCollector dbc(""); + int64_t nactor = world_->patch_everything(sb, &dbc); + if (nactor != actor_id_) change_actor_id(nactor); + dbc.dump(stdostream()); + } catch (const StreamException &sexcept) { + abandon_server(); + return; + } + } + + bool receive_message_from_server(StreamBuffer *sb) { + if (sb->fill() < 5) return false; + int64_t tr_before = sb->total_reads(); + uint8_t message_type = sb->read_uint8(); + uint32_t message_body_len = sb->read_uint32(); + if (sb->fill() < message_body_len) { + sb->unread_to(tr_before); + return false; + } + const char *message_body = sb->read_bytes(message_body_len); + StreamBuffer body(std::string_view(message_body, message_body_len)); + if (message_type == util::MSG_ACK) { + receive_ack_from_server(&body); + } else if (message_type == util::MSG_DIFF) { + receive_diff_from_server(&body); + } else { + abandon_server(); + return false; + } + if (!body.empty()) { + abandon_server(); + return false; + } + return true; + } + + virtual void event_call_function(InvocationKind kind, int64_t place_id, std::string_view datapk, StreamBuffer *retpk) override { + switch (kind) { + case InvocationKind::LUA_PROBE: { + world_to_asynchronous(); + world_->probe_lua_call(actor_id_, place_id, datapk, retpk); + break; + } + default: { + delayed_invocations_.emplace_back(kind, actor_id_, place_id, datapk); + } + } + } + + virtual void event_update() override { + // Send invocations. We execute these using predictive execution. + for (const Invocation &inv : delayed_invocations_) { + send_invocation(inv); + } + delayed_invocations_.clear(); + + // Check for keyboard input on stdin. + while (true) { + eng::string line = get_stdio_channel()->in()->readline(); + if (line == "") break; + console_.add(line); + set_console_prompt(console_.get_prompt()); + do_command(console_.get_command()); + } + + // Check for communication from server.. + if (channel_ != nullptr) { + if (channel_->closed()) { + stdostream() << "server closed connection: " << channel_->error() << std::endl; + abandon_server(); + } else { + while (true) { + if (!receive_message_from_server(channel_->in())) break; + if (channel_ == nullptr) break; + } + world_to_asynchronous(); + } + } + + // Channel print statements. + if (print_channeler_.channel(world_->get_printbuffer(actor_id_), stdostream())) { + send_invocation(print_channeler_.invocation(actor_id_)); + } + } +}; + +DrivenEngineDefine("lpxclient", LpxClient); + diff --git a/luprex/cpp/core/lpxclient.hpp b/luprex/cpp/core/lpxclient.hpp new file mode 100644 index 00000000..a1a2a1a5 --- /dev/null +++ b/luprex/cpp/core/lpxclient.hpp @@ -0,0 +1,4 @@ +#ifndef LPXCLIENT_HPP +#define LPXCLIENT_HPP + +#endif // LPXCLIENT_HPP diff --git a/luprex/cpp/core/lpxserver.cpp b/luprex/cpp/core/lpxserver.cpp new file mode 100644 index 00000000..785990af --- /dev/null +++ b/luprex/cpp/core/lpxserver.cpp @@ -0,0 +1,346 @@ +#include "wrap-string.hpp" +#include "wrap-vector.hpp" + +#include "world.hpp" +#include "drivenengine.hpp" +#include "luaconsole.hpp" +#include "util.hpp" +#include "printbuffer.hpp" + +#include + +class Client : public eng::opnew { +public: + int64_t actor_id_; + SharedChannel channel_; + UniqueWorld sync_; + double last_diff_; + bool async_diff_; +}; +using UniqueClient = std::unique_ptr; +using ClientVector = eng::vector; + +class LpxServer : public DrivenEngine, public CommonCommands { +public: + using StringVec = LuaConsole::StringVec; + UniqueWorld master_; + LuaConsole console_; + ClientVector clients_; + PrintChanneler print_channeler_; + HttpChannelMap http_client_channels_; + HttpChannelVec http_server_channels_; + int64_t admin_id_; + int next_diff_chan_; + double next_tick_; + eng::vector delayed_invocations_; + +public: + virtual void event_init(std::string_view srcpk, int argc, char *argv[]) override { + // Create the master world model. + master_.reset(new World(WORLD_TYPE_MASTER)); + + // Update the source code of the master model. + master_->update_source(srcpk); + + // Create an actor for administrative commands. + admin_id_ = master_->create_login_actor(); + + // TODO: initialize the admin actor. + + // Print out admin ID for debugging purposes. + stdostream() << "Admin actor id = " << admin_id_ << std::endl; + + // Enable listening on port 8085 (client connections) + listen_port(8085); + + // Enable listening on port 8080 (http server connections) + listen_port(8080); + + // Set the console prompt. + set_console_prompt(console_.get_prompt()); + + // Export stuff to the graphics engine. + set_visible_world_and_actor(master_.get(), admin_id_); + + // for ticking. + next_tick_ = 0.0; + } + + virtual void do_syntax_error(std::string_view error) override { + stdostream() << "Syntax error: " << error << std::endl; + } + + virtual void do_unknown_command(std::string_view name) override { + stdostream() << "Unknown command: " << name << std::endl; + } + + virtual void do_view_command() override { + stdostream() << master_->tangibles_near_debug_string(admin_id_, 1000); + } + + virtual void do_moveto_command(int x, int y) override { + do_unknown_command("moveto"); + } + + virtual void do_quit_command() override { + stop_driver(); + } + + virtual void do_cpl_command() override { + rescan_lua_source(); + } + + virtual void do_work_command() override { + master_->snapshot(); + StreamBuffer datapack; + StreamBuffer retvals; + datapack.write_string("engio"); + datapack.write_string("myfunction"); + datapack.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); + datapack.write_double(3.7); + datapack.write_simple_dynamic_tag(SimpleDynamicTag::STRING); + datapack.write_string("banana"); + datapack.write_simple_dynamic_tag(SimpleDynamicTag::BOOLEAN); + datapack.write_bool(true); + master_->probe_lua_call(admin_id_, admin_id_, datapack.view(), &retvals); + while (!retvals.empty()) { + SimpleDynamicValue value; + retvals.read_simple_dynamic(&value); + if (value.type == SimpleDynamicTag::NUMBER) { + util::dprint("retval: ", value.x); + } else if (value.type == SimpleDynamicTag::STRING) { + util::dprint("retval: ", value.s); + } else if (value.type == SimpleDynamicTag::BOOLEAN) { + util::dprint(value.x ? "retval: true" : "retval: false"); + } else if (value.type == SimpleDynamicTag::VECTOR) { + util::dprint("retval: ", value.x, " ", value.y, " ", value.z); + } else { + util::dprint("retval: invalid"); + break; + } + } + master_->rollback(); + } + + virtual void do_display_command() override { + do_unknown_command("display"); + } + + virtual void do_aborthttp_command() override { + master_->abort_all_http_requests(425, "http requests aborted from the server command line"); + } + + virtual void do_connect_command(std::string_view hostname) override { + do_unknown_command("connect"); + } + + virtual void do_luainvoke_command(std::string_view cmd) override { + master_->invoke(Invocation(InvocationKind::LUA_EXPR, admin_id_, admin_id_, cmd)); + } + + virtual void do_luaprobe_command(std::string_view cmd) override { + master_->snapshot(); + stdostream() << master_->probe_lua_expr(admin_id_, cmd); + master_->rollback(); + } + + void delete_client(UniqueClient &client) { + stdostream() << "Client closed: actor id=" << client->actor_id_ << std::endl; + master_->disconnected(client->actor_id_); + client.reset(); + } + + void send_diffs(UniqueClient &client) { + StreamBuffer *sb = client->channel_->out(); + sb->write_uint8(util::MSG_DIFF); + sb->write_uint32(0); + int64_t tw_1 = sb->total_writes(); + //stdostream() << "Sending diffs to client " << client->actor_id_ << std::endl; + client->sync_->diff_everything(client->actor_id_, master_.get(), sb); + int64_t tw_2 = sb->total_writes(); + sb->overwrite_int32(tw_1, tw_2 - tw_1); + } + + bool handle_invocation(UniqueClient &client) { + StreamBuffer *sb = client->channel_->in(); + int64_t tr_before = sb->total_reads(); + Invocation inv; + try { + uint8_t msg_type = sb->read_uint8(); + if (msg_type != util::MSG_INVOKE) { + delete_client(client); + return false; + } + inv.deserialize(sb); + } catch (const StreamEofOnRead &seof) { + sb->unread_to(tr_before); + return false; + } catch (const StreamException &sexcept) { + delete_client(client); + return false; + } + // Security check. + if (inv.actor() != client->actor_id_) { + delete_client(client); + return false; + } + //stdostream() << "Invoking: " << inv.debug_string() << std::endl; + master_->invoke(inv); + client->channel_->out()->write_uint8(util::MSG_ACK); + client->channel_->out()->write_uint32(0); + client->sync_->invoke(inv); + client->async_diff_ = true; + return true; + } + + virtual void event_call_function(InvocationKind kind, int64_t place_id, std::string_view datapk, StreamBuffer *retpk) override { + switch (kind) { + case InvocationKind::LUA_PROBE: { + master_->snapshot(); + master_->probe_lua_call(admin_id_, place_id, datapk, retpk); + master_->rollback(); + break; + } + default: { + delayed_invocations_.emplace_back(kind, admin_id_, place_id, datapk); + } + } + } + + virtual void event_update() override { + // Get the clock. + double clock = get_clock(); + + // Execute any queued invocations. + // We just feed these directly into the master model. + for (const Invocation &inv : delayed_invocations_) { + master_->invoke(inv); + } + delayed_invocations_.clear(); + + // Check for keyboard input on stdin. + while (true) { + eng::string line = get_stdio_channel()->in()->readline(); + if (line == "") break; + console_.add(line); + set_console_prompt(console_.get_prompt()); + do_command(console_.get_command()); + } + + // Anything in the administrator printbuffer should go to stdostream. + if (print_channeler_.channel(master_->get_printbuffer(admin_id_), stdostream())) { + master_->invoke(print_channeler_.invocation(admin_id_)); + } + + // Check for new incoming channels, set up client structures. + while (true) { + SharedChannel chan = new_incoming_channel(); + if (chan == nullptr) break; + if (chan->port() == 8085) { + Client *client = new Client; + client->actor_id_ = master_->create_login_actor(); + // TODO: initialize the login actor on the master. + client->channel_ = std::move(chan); + client->async_diff_ = true; + client->last_diff_ = -100.0; + client->sync_.reset(new World(WORLD_TYPE_PREDICTIVE)); + // This login actor is never used, it is just to preserve the invariant that + // the client model and the server synchronous model are identical. + client->sync_->create_login_actor(); + clients_.emplace_back(client); + stdostream() << "New client: actor id=" << client->actor_id_ << std::endl; + } else if (chan->port() == 8080) { + HttpChannel htchan; + htchan.channel_ = chan; + http_server_channels_.push_back(htchan); + } + } + + // If the clock has advanced far enough, tick the master model. + if (clock >= next_tick_) { + master_->invoke(Invocation(InvocationKind::TICK, 0, 0, "")); + for (UniqueClient &client : clients_) { + client->async_diff_ = true; + } + next_tick_ += 1.0; + if (next_tick_ < clock + 0.3) next_tick_ = clock + 0.3; + } + + // Traverse all existing channels, process any communication. + for (UniqueClient &client : clients_) { + if (client->channel_->closed()) { + delete_client(client); + continue; + } + + // Check for received invocations. + while (handle_invocation(client)); + + // Possibly send a diff. + double diffdelay = 5.0; + if (client->async_diff_) diffdelay = 0.1; + if (clock >= client->last_diff_ + diffdelay) { + send_diffs(client); + client->last_diff_ = clock; + client->async_diff_ = false; + } + } + util::remove_nullptrs(clients_); + + // Look for new outgoing HTTP client requests. + for (const auto &pair : master_->http_requests()) { + const HttpClientRequest &request = pair.second; + HttpChannel &channel = http_client_channels_[request.request_id()]; + if (channel.channel_ == nullptr) { + channel.channel_ = new_outgoing_channel(request.target()); + channel.method_ = request.method(); + channel.parsed_bytes_ = 0; + request.send(channel.channel_->out()); + } + } + + // Maintain existing outgoing HTTP client requests. + HttpParserVec http_responses; + for (auto &pair : http_client_channels_) { + HttpChannel &htchan = pair.second; + Channel &channel = *htchan.channel_; + if (channel.closed() || (channel.in()->fill() > htchan.parsed_bytes_)) { + HttpParser response; + if (!channel.error().empty()) { + response.fail(503, util::ss("Service Unavailable: ", channel.error())); + } else { + htchan.parsed_bytes_ = channel.in()->fill(); + response.parse_response(channel.in()->view(), channel.closed(), htchan.method_); + } + if (response.complete()) { + response.set_request_id(pair.first); + http_responses.push_back(response); + } + } + } + for (const HttpParser &response : http_responses) { + http_client_channels_.erase(response.request_id()); + } + master_->http_responses(http_responses); + + // Maintain incoming HTTP server channels. + for (HttpChannel &htchan : http_server_channels_) { + SharedChannel &chan = htchan.channel_; + if (chan->in()->fill() > htchan.parsed_bytes_) { + HttpParser parser; + parser.parse_request(chan->in()->view(), chan->closed()); + htchan.parsed_bytes_ = chan->in()->fill(); + if (parser.complete()) { + StreamBuffer *sb = chan->out(); + HttpServerResponse resp = master_->http_serve(parser); + resp.send(sb); + htchan.channel_ = nullptr; + } + } + } + util::remove_marked_items(http_server_channels_); + } +}; + +DrivenEngineDefine("lpxserver", LpxServer); + diff --git a/luprex/cpp/core/lpxserver.hpp b/luprex/cpp/core/lpxserver.hpp new file mode 100644 index 00000000..e2fbfa32 --- /dev/null +++ b/luprex/cpp/core/lpxserver.hpp @@ -0,0 +1,5 @@ +#ifndef LPXSERVER_HPP +#define LPXSERVER_HPP + +#endif // LPXSERVER_HPP + diff --git a/luprex/cpp/core/luaconsole.cpp b/luprex/cpp/core/luaconsole.cpp new file mode 100644 index 00000000..f58fefb4 --- /dev/null +++ b/luprex/cpp/core/luaconsole.cpp @@ -0,0 +1,200 @@ +#include "wrap-string.hpp" +#include "wrap-vector.hpp" +#include "eng-malloc.hpp" +#include "luastack.hpp" +#include "luaconsole.hpp" +#include "util.hpp" + +#include +#include + +LuaConsole::LuaConsole() { + lua_state_ = LuaCoreStack::newstate(eng::l_alloc); + clear_raw_input(); +} + +LuaConsole::~LuaConsole() { + lua_close(lua_state_); +} + +static LuaConsole::StringVec split_words(const eng::string &raw) { + LuaConsole::StringVec result; + eng::string acc; + for (char c : raw) { + if ((c == ' ')||(c == '\n')||(c == '\r')) { + if (!acc.empty()) { + result.push_back(acc); + acc = ""; + } + } else { + acc += c; + } + } + if (!acc.empty()) { + result.push_back(acc); + } + return result; +} + +LuaConsole::StringVec LuaConsole::get_command() { + StringVec result = words_; + words_.clear(); + return result; +} + +void LuaConsole::clear_raw_input() { + raw_input_ = ""; + lines_ = 0; + prompt_ = "> "; +} + +void LuaConsole::add(eng::string line) { + for (int i = 0; i < int(line.size()); i++) { + if (line[i] == '\n') line[i] = ' '; + } + raw_input_ += line; + raw_input_ += '\n'; + lines_ += 1; + prompt_ = ">> "; + words_.clear(); + + // Try to interpret it as a slash-command. + if ((lines_ == 1)&&(raw_input_[0] == '/')) { + words_ = split_words(raw_input_.substr(1)); + if ((words_.size() == 1) && (sv::valid_int64(words_[0]))) { + eng::string num = words_[0]; + words_.clear(); + words_.push_back("choose"); + words_.push_back(num); + } + clear_raw_input(); + return; + } + + // Translate lua expression, deal with initial prefix. + eng::string partial; + eng::string lua_mode; + if (sv::has_prefix(raw_input_, "?=")) { + lua_mode = "luaprobe"; + partial = eng::string("return ") + raw_input_.substr(2); + } else if (sv::has_prefix(raw_input_, "?")) { + lua_mode = "luaprobe"; + partial = raw_input_.substr(1); + } else if (sv::has_prefix(raw_input_, "=")) { + lua_mode = "luainvoke"; + partial = eng::string("return ") + raw_input_.substr(1); + } else { + lua_mode = "luainvoke"; + partial = raw_input_; + } + + // Try to parse the lua expression + int top = lua_gettop(lua_state_); + int status = luaL_loadbuffer(lua_state_, partial.c_str(), partial.size(), "=stdin"); + if (status == LUA_ERRSYNTAX) + { + const char *eof = ""; + int leof = strlen(eof); + size_t lmsg; + const char *msg = lua_tolstring(lua_state_, -1, &lmsg); + const char *tp = msg + lmsg - leof; + if (strstr(msg, eof) != tp) { + words_.push_back("syntax"); + words_.push_back(msg); + clear_raw_input(); + } + } else { + words_.push_back(lua_mode); + words_.emplace_back(sv::rtrim(partial)); + clear_raw_input(); + } + lua_settop(lua_state_, top); +} + +void CommonCommands::do_command(const StringVec &words) { + if (words.size() == 0) { + return; + } + + if (words[0] == "view") { + if (words.size() == 1) { + return do_view_command(); + } else { + return do_syntax_error("/view takes no arguments"); + } + } + + if (words[0] == "moveto") { + if ((words.size() == 3) && sv::valid_int64(words[1]) && sv::valid_int64(words[2])) { + return do_moveto_command(sv::to_int64(words[1]), sv::to_int64(words[2])); + } else { + return do_syntax_error("/moveto [x] [y]"); + } + } + + if (words[0] == "quit") { + if (words.size() == 1) { + return do_quit_command(); + } else { + return do_syntax_error("/quit takes no arguments"); + } + } + + if (words[0] == "cpl") { + if (words.size() == 1) { + return do_cpl_command(); + } else { + return do_syntax_error("/cpl takes no arguments"); + } + } + + if (words[0] == "work") { + if (words.size() == 1) { + return do_work_command(); + } else { + return do_syntax_error("/work takes no arguments"); + } + } + + if (words[0] == "display") { + if (words.size() == 1) { + return do_display_command(); + } else { + return do_syntax_error("/display takes no arguments"); + } + } + + if (words[0] == "aborthttp") { + if (words.size() == 1) { + return do_aborthttp_command(); + } else { + return do_syntax_error("/aborthttp takes no arguments"); + } + } + + if (words[0] == "connect") { + if ((words.size() == 2)&&(sv::valid_hostname(words[1]))) { + return do_connect_command(words[1]); + } else { + return do_syntax_error("/connect [hostname]"); + } + } + + if (words[0] == "luainvoke") { + if (words.size() == 2) { + return do_luainvoke_command(words[1]); + } else { + return do_syntax_error("/luainvoke [command]"); + } + } + + if (words[0] == "luaprobe") { + if (words.size() == 2) { + return do_luaprobe_command(words[1]); + } else { + return do_syntax_error("/luainvoke [command]"); + } + } + + return do_unknown_command(words[0]); +} diff --git a/luprex/cpp/core/luaconsole.hpp b/luprex/cpp/core/luaconsole.hpp new file mode 100644 index 00000000..2fca16c2 --- /dev/null +++ b/luprex/cpp/core/luaconsole.hpp @@ -0,0 +1,118 @@ +////////////////////////////////////////////////////// +// +// LuaConsole: +// +// Used to parse commands that are being interactively typed +// in by a user. The common usage pattern is: +// +// 1. Print the prompt suggested by 'get_prompt'. +// 2. Read a line of text from stdin. +// 3. Add the line to the LuaConsole using 'add'. +// 4. Get the command word using get_command. +// 5. If the command is empty, do nothing. +// 6. If the command is nonempty, get the args and execute it. +// +// The LuaConsole expects you to type a lua command, or one +// of the following slash-commands: +// +// /quit - exit the program +// /view - display the nearby tangibles +// /menu [tanid] - display the menu for tangible +// /tick [timevalue] - advance the simulation clock +// /choose [number] - choose menu item number +// /1234 - choose menu item 1234 +// +// If you type anything else, the LuaConsole will generate a +// syntax error. Note that not all of the commands above are +// supported in all interpreters. +// +// Lua commands can be one of the following: +// +// - invoke """ +// = - invoke "return " +// ? - probe "" +// ?= - probe "return " +// +// Once a command has been typed (or a syntax error has been +// typed), the LuaConsole will return the command. The command +// can be fetched using command(), int_arg(), and str_arg() +// +////////////////////////////////////////////////////// + +#ifndef LUACONSOLE_HPP +#define LUACONSOLE_HPP + +#include "wrap-string.hpp" +#include "wrap-vector.hpp" + +#include "luastack.hpp" + +class LuaConsole : public eng::nevernew { +public: + using StringVec = eng::vector; + +private: + lua_State *lua_state_; + eng::string raw_input_; + int lines_; + eng::string prompt_; + StringVec words_; + + void clear_raw_input(); + +public: + LuaConsole(); + ~LuaConsole(); + + // Fetch the recommended prompt. + // + // You should update the prompt immediately after 'add'. + // + const eng::string &get_prompt() { return prompt_; } + + // Get the command words. + // + StringVec get_command(); + + // Add a line of text that was just read from the console. + // + void add(eng::string line); +}; + +////////////////////////////////////////////////////// +// +// CommonCommands is parses slash-commands that are understood by +// lpxclient and lpxserver. That way, these two separate +// sourcefiles don't have to duplicate the code for parsing. +// +// After parsing, this module calls a virtual function +// to execute the command. lpxserver and lpxclient +// implement these virtual functions in different ways. +// +////////////////////////////////////////////////////// + +class CommonCommands { +public: + using StringVec = eng::vector; + +protected: + virtual void do_syntax_error(std::string_view error) = 0; + virtual void do_unknown_command(std::string_view name) = 0; + virtual void do_view_command() = 0; + virtual void do_moveto_command(int x, int y) = 0; + virtual void do_quit_command() = 0; + virtual void do_cpl_command() = 0; + virtual void do_work_command() = 0; + virtual void do_display_command() = 0; + virtual void do_aborthttp_command() = 0; + virtual void do_connect_command(std::string_view hostname) = 0; + virtual void do_luainvoke_command(std::string_view cmd) = 0; + virtual void do_luaprobe_command(std::string_view cmd) = 0; + +public: + // Parse a command and call one of the virtual command functions above. + // + void do_command(const StringVec &command); +}; + +#endif // LUACONSOLE_HPP diff --git a/luprex/cpp/core/luasnap.cpp b/luprex/cpp/core/luasnap.cpp new file mode 100644 index 00000000..6f3f8e4f --- /dev/null +++ b/luprex/cpp/core/luasnap.cpp @@ -0,0 +1,140 @@ + +#include "wrap-sstream.hpp" +#include "wrap-string.hpp" + +#include "luasnap.hpp" +#include "luastack.hpp" +#include "streambuffer.hpp" + +#include + + +LuaSnap::LuaSnap() { + state_ = LuaCoreStack::newstate(eng::l_alloc); + LuaExtStack LS(state_); + + // Create the persist table and the unpersist table. + // + // These tables need to contain all C functions. Whenever + // the source module inserts a C function into the lua environment, + // it also needs to register the C function in these tables. + // + // I don't think anything else needs to go in these tables. + // + LS.rawset(LuaRegistry, "persist", LuaNewTable); + LS.rawset(LuaRegistry, "unpersist", LuaNewTable); +} + +LuaSnap::~LuaSnap() { + lua_close(state_); +} + +void LuaSnap::serialize(StreamBuffer *sb) { + // Lua stack should be empty. + assert(lua_gettop(state_) == 0); + + // We'll allocate stack slots manually, to ensure + // that the thread serializes as we expect it to. + lua_pushnil(state_); // permstable + lua_pushnil(state_); // regcopy + lua_pushnil(state_); // key + lua_pushnil(state_); // value + LuaSpecial permstable(1); + LuaSpecial regcopy(2); + LuaSpecial key(3); + LuaSpecial value(4); + LuaCoreStack LS(state_); + + // Construct a copy of the registry table. + LS.set(regcopy, LuaNewTable); + LS.set(key, LuaNil); + while (LS.next(LuaRegistry, key, value) != 0) { + LS.rawset(regcopy, key, value); + } + + // Remove certain things from the copy. These items + // don't get included in the snapshot. + // + // From a modularity perspective, this is bad. + // + LS.rawset(regcopy, LUA_RIDX_MAINTHREAD, LuaNil); + LS.rawset(regcopy, "persist", LuaNil); + LS.rawset(regcopy, "unpersist", LuaNil); + LS.rawset(regcopy, "world", LuaNil); + LS.rawset(regcopy, "gui", LuaNil); + LS.rawset(regcopy, "tnmap", LuaNil); + LS.rawset(regcopy, "ntmap", LuaNil); + + // Get the eris permanents table from the registry. + LS.rawget(permstable, LuaRegistry, "persist"); + assert(LS.istable(permstable)); + + // Remove key and value from stack, leaving permstable and regcopy. + lua_settop(state_, 2); + + // Write dummy length, use eris to write data, then overwrite length. + sb->write_int64(0); + int64_t pos1 = sb->total_writes(); + + eris_dump(state_, lua_writer_into_streambuffer, sb); + int64_t pos2 = sb->total_writes(); + sb->overwrite_int64(pos1, pos2 - pos1); + lua_settop(state_, 0); +} + +void LuaSnap::deserialize(StreamBuffer *sb) { + // Lua stack should be empty. + assert(lua_gettop(state_) == 0); + + // Get the eris data and convert it to a LuaByteReader. + int64_t eris_len = sb->read_int64(); + const char *eris_bytes = sb->read_bytes(eris_len); + LuaByteReader bytereader(eris_bytes, eris_len); + + // Call eris with the permanents table and passing the snapshot as a lua_Reader. + lua_pushstring(state_, "unpersist"); + lua_rawget(state_, LUA_REGISTRYINDEX); + eris_undump(state_, bytereader.lua_reader, bytereader.lua_reader_userdata()); + + // Set up a stack frame manually. Eris deserialization + // should have left permstable and regcopy on the stack. + assert(lua_gettop(state_) == 2); + LuaSpecial permstable(1); + LuaSpecial regcopy(2); + lua_pushnil(state_); + lua_pushnil(state_); + LuaSpecial key(3); + LuaSpecial value(4); + LuaCoreStack LS(state_); + assert(LS.istable(regcopy)); + + // Copy the contents of the snapshot over to the registry. + LS.set(key, LuaNil); + while (LS.next(regcopy, key, value) != 0) { + LS.rawset(LuaRegistry, key, value); + } + lua_settop(state_, 0); +} + +// Snapshot and rollback can trivially be implemented on top of serialize and +// deserialize. However, it's also possible to implement snapshot and rollback +// using an alternative technique: +// +// 1. When constructing the lua interpreter, use a custom memory allocator that +// keeps track of all the memory blocks used by lua. +// +// 2. Snapshot simply copies all the memory blocks used by lua into a buffer. +// +// 3. Rollback restores lua's memory blocks back to their previous state. This +// has the effect of restoring lua's state. +// +// A proof-of-concept implementation of the memory-snapshotting design was +// created, and it worked. It is probably faster than using serialize and +// deserialize. +// +// Note: even if we implement this alternative design, we still need to keep +// serialize and deserialize around in order to implement the save-game +// functionality. So for now, we're sticking with this design, which doesn't +// require us to maintain any additional code. + + diff --git a/luprex/cpp/core/luasnap.hpp b/luprex/cpp/core/luasnap.hpp new file mode 100644 index 00000000..0f152ef3 --- /dev/null +++ b/luprex/cpp/core/luasnap.hpp @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////// +// +// LUASNAP +// +// A lua interpreter that can be checkpointed (snapshotted). This makes it +// possible to roll the entire interpreter back to a previously-snapshotted +// state. +// +// To accomplish this, we use eris serialization. This is messy and not +// very modular, but it does work. +// +///////////////////////////////////////////////////////////////////////////// + +#ifndef LUASNAP_HPP +#define LUASNAP_HPP + +#include "streambuffer.hpp" +#include "luastack.hpp" + +class LuaSnap : public eng::nevernew { +private: + lua_State *state_; + +public: + LuaSnap(); + ~LuaSnap(); + + // Get the lua intepreter. + // + lua_State *state() const { return state_; } + + // Serialize the state of the lua interpreter. + // + void serialize(StreamBuffer *sb); + + // Restore the the lua interpreter given a serialized state. + // + void deserialize(StreamBuffer *sb); +}; + + +#endif // LUASNAP_HPP diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp new file mode 100644 index 00000000..cdb78d53 --- /dev/null +++ b/luprex/cpp/core/luastack.cpp @@ -0,0 +1,709 @@ +#include "luastack.hpp" +#include +#include +#include +#include "wrap-string.hpp" +#include "wrap-set.hpp" +#include "wrap-sstream.hpp" +#include "util.hpp" + +LuaSpecial LuaRegistry(LUA_REGISTRYINDEX); +LuaNilMarker LuaNil; +LuaNewTableMarker LuaNewTable; + +LuaFunctionReg::LuaFunctionReg(const char *n, const char *a, const char *d, bool s, lua_CFunction f) { + name_ = n; + args_ = a; + docs_ = d; + func_ = f; + sandbox_ = s; + next_ = All; + All = this; +} + +LuaConstantReg::LuaConstantReg(const char *n, const char *d, LuaToken tokenvalue, lua_Number numbervalue) { + name_ = n; + docs_ = d; + tokenvalue_ = tokenvalue; + numbervalue_ = numbervalue; + next_ = All; + All = this; +} + +const LuaFunctionReg *LuaFunctionReg::lookup(lua_CFunction fn) { + for (const LuaFunctionReg *r = All; r != 0; r = r->next_) { + if (r->func_ == fn) { + return r; + } + } + return nullptr; +} + +LuaFunctionReg *LuaFunctionReg::All; +LuaConstantReg *LuaConstantReg::All; + + +eng::string LuaToken::str() const { + uint64_t token = (uint64_t)value; + char buffer[9]; + for (int i = 0; i < 8; i++) { + unsigned char c = token; + buffer[7-i] = c; + token >>= 8; + } + buffer[8] = 0; + return eng::string(buffer); +} + +static int panicf(lua_State *L) { + const char *p = lua_tostring(L, -1); + fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", p); + fflush(stderr); + exit(1); +} + +// An allocator for lua states that uses system malloc and system free. +static void *l_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { + if (nsize == 0) { + free(ptr); + return NULL; + } else { + return realloc(ptr, nsize); + } +} + +lua_State *LuaCoreStack::newstate (lua_Alloc allocf) { + if (allocf == nullptr) allocf = l_alloc; + lua_State *L = lua_newstate(allocf, NULL); + assert(L != nullptr); + lua_atpanic(L, &panicf); + + // We want all states to have a classes table and + // a tangibles table so that LS.makeclass and LS.maketan + // work out-of-the-box. + + lua_pushstring(L, "classes"); + lua_newtable(L); + lua_rawset(L, LUA_REGISTRYINDEX); + lua_pushstring(L, "classnames"); + lua_newtable(L); + lua_rawset(L, LUA_REGISTRYINDEX); + lua_pushstring(L, "tangibles"); + lua_newtable(L); + lua_rawset(L, LUA_REGISTRYINDEX); + lua_pushstring(L, "worldtype"); + lua_pushnumber(L, WORLD_TYPE_MASTER); + lua_rawset(L, LUA_REGISTRYINDEX); + + return L; +} + +void LuaCoreStack::argerr(const char *nm, const char *tp) const { + luaL_error(L_, "'%s' should be %s", nm, tp); +} + +std::optional LuaCoreStack::tryboolean(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TBOOLEAN) { + return lua_toboolean(L_, s); + } + return std::nullopt; +} + +std::optional LuaCoreStack::tryinteger(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TNUMBER) { + lua_Number result = lua_tonumber(L_, s); + if (lua_Integer(result) == result) { + return lua_Integer(result); + } + } + return std::nullopt; +} + +std::optional LuaCoreStack::tryint(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TNUMBER) { + lua_Number result = lua_tonumber(L_, s); + if (int(result) == result) { + return int(result); + } + } + return std::nullopt; +} + +std::optional LuaCoreStack::trynumber(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TNUMBER) { + return lua_tonumber(L_, s); + } + return std::nullopt; +} + +std::optional LuaCoreStack::trystring(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TSTRING) { + size_t len; + const char *str = lua_tolstring(L_, s, &len); + return eng::string(str, len); + } + return std::nullopt; +} + +std::optional LuaCoreStack::trystringview(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TSTRING) { + size_t len; + const char *str = lua_tolstring(L_, s, &len); + return std::string_view(str, len); + } + return std::nullopt; +} + +std::optional LuaCoreStack::trythread(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TTHREAD) { + return lua_tothread(L_, s); + } + return std::nullopt; +} + +std::optional LuaCoreStack::trytoken(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TLIGHTUSERDATA) { + return LuaToken(lua_touserdata(L_, s)); + } + return std::nullopt; +} + +std::optional LuaCoreStack::tryxyz(LuaSlot s) const { + if (lua_istable(L_, s) && (lua_nkeys(L_, s) == 3)) { + int top = lua_gettop(L_); + lua_rawgeti(L_, s, 3); + lua_rawgeti(L_, s, 2); + lua_rawgeti(L_, s, 1); + if ((lua_type(L_, -1)==LUA_TNUMBER) && (lua_type(L_, -2)==LUA_TNUMBER) && (lua_type(L_, -3)==LUA_TNUMBER)) { + util::DXYZ result; + result.x = lua_tonumber(L_, -1); + result.y = lua_tonumber(L_, -2); + result.z = lua_tonumber(L_, -3); + lua_settop(L_, top); + return result; + } + lua_settop(L_, top); + } + return std::nullopt; +} + +bool LuaCoreStack::trytable(LuaSlot s) const { + return lua_istable(L_, s); +} + +bool LuaCoreStack::trynil(LuaSlot s) const { + return lua_isnil(L_, s); +} + +bool LuaCoreStack::tryfunction(LuaSlot s) const { + return lua_isfunction(L_, s); +} + +bool LuaCoreStack::trycfunction(LuaSlot s) const { + return lua_iscfunction(L_, s); +} + +bool LuaCoreStack::trytangible(LuaSlot s) const { + return (lua_istable(L_, s) && gettabletype(s) == LUA_TT_TANGIBLE); +} + + + +bool LuaCoreStack::ckboolean(LuaSlot s, const char *argname) const { + auto result = tryboolean(s); + if (!result) argerr(argname, "boolean"); + return *result; +} + +lua_Integer LuaCoreStack::ckinteger(LuaSlot s, const char *argname) const { + auto result = tryinteger(s); + if (!result) argerr(argname, "integer"); + return *result; +} + +int LuaCoreStack::ckint(LuaSlot s, const char *argname) const { + auto result = tryint(s); + if (!result) argerr(argname, "int"); + return *result; +} + +lua_Number LuaCoreStack::cknumber(LuaSlot s, const char *argname) const { + auto result = trynumber(s); + if (!result) argerr(argname, "number"); + return *result; +} + +eng::string LuaCoreStack::ckstring(LuaSlot s, const char *argname) const { + auto result = trystring(s); + if (!result) argerr(argname, "string"); + return *result; +} + +std::string_view LuaCoreStack::ckstringview(LuaSlot s, const char *argname) const { + auto result = trystringview(s); + if (!result) argerr(argname, "string"); + return *result; +} + +lua_State * LuaCoreStack::ckthread(LuaSlot s, const char *argname) const { + auto result = trythread(s); + if (!result) argerr(argname, "thread"); + return *result; +} + +LuaToken LuaCoreStack::cktoken(LuaSlot s, const char *argname) const { + auto result = trytoken(s); + if (!result) argerr(argname, "token"); + return *result; +} + +util::DXYZ LuaCoreStack::ckxyz(LuaSlot s, const char *argname) const { + auto result = tryxyz(s); + if (!result) argerr(argname, "xyz"); + return *result; +} + +void LuaCoreStack::cktable(LuaSlot s, const char *argname) const { + if (!trytable(s)) argerr(argname, "table"); +} + +void LuaCoreStack::cknil(LuaSlot s, const char *argname) const { + if (!trynil(s)) argerr(argname, "nil"); +} + +void LuaCoreStack::ckfunction(LuaSlot s, const char *argname) const { + if (!tryfunction(s)) argerr(argname, "function"); +} + +void LuaCoreStack::ckcfunction(LuaSlot s, const char *argname) const { + if (!trycfunction(s)) argerr(argname, "c-function"); +} + +void LuaCoreStack::cktangible(LuaSlot s, const char *argname) const { + if (!trytangible(s)) argerr(argname, "tangible"); +} + + +void LuaCoreStack::clearmetatable(LuaSlot tab) const { + lua_pushnil(L_); + lua_setmetatable(L_, tab); +} + +void LuaCoreStack::setmetatable(LuaSlot tab, LuaSlot mt) const { + lua_pushvalue(L_, mt); + lua_setmetatable(L_, tab); +} + +bool LuaCoreStack::getmetatable(LuaSlot mt, LuaSlot tab) const { + if (lua_getmetatable(L_, tab)) { + lua_replace(L_, mt); + return true; + } else { + lua_pushnil(L_); + lua_replace(L_, mt); + return false; + } +} + +bool LuaCoreStack::next(LuaSlot tab, LuaSlot key, LuaSlot value) const { + lua_pushvalue(L_, key); + int ret = lua_next(L_, tab); + if (ret != 0) { + lua_replace(L_, value); + lua_replace(L_, key); + } + return (ret != 0); +} + +void LuaCoreStack::getglobaltable(LuaSlot target) const { + lua_pushglobaltable(L_); + lua_replace(L_, target); +} + +void LuaCoreStack::newtable(LuaSlot target) const { + lua_newtable(L_); + lua_replace(L_, target); +} + +void LuaCoreStack::createtable(LuaSlot target, int narr, int nrec) const { + lua_createtable(L_, narr, nrec); + lua_replace(L_, target); +} + +lua_State *LuaCoreStack::newthread(LuaSlot target) const { + lua_State *result = lua_newthread(L_); + lua_replace(L_, target); + return result; +} + +eng::string LuaCoreStack::classname(LuaSlot input) const { + LuaVar lookup, classtab, classname; + LuaExtStack LS(L_, lookup, classtab, classname); + + int xt = xtype(input); + if (xt == LUA_TSTRING) { + LS.rawget(lookup, LuaRegistry, "classes"); + LS.rawget(classtab, lookup, input); + if (xtype(classtab) != LUA_TT_CLASS) { + return ""; + } + return LS.ckstring(input); + } else if (xt == LUA_TT_TANGIBLE) { + if (!LS.getmetatable(lookup, input)) { + return ""; + } + LS.rawget(classtab, lookup, "__index"); + if (xtype(classtab) != LUA_TT_CLASS) { + return ""; + } + LS.rawget(lookup, LuaRegistry, "classnames"); + LS.rawget(classname, lookup, classtab); + if (!LS.isstring(classname)) { + return ""; + } + return LS.ckstring(classname); + } else if (xt == LUA_TT_CLASS) { + LS.rawget(lookup, LuaRegistry, "classnames"); + LS.rawget(classname, lookup, input); + if (!LS.isstring(classname)) { + return ""; + } + return LS.ckstring(classname); + } else { + return ""; + } +} + +static eng::string nonexistentclass(eng::string name) { + eng::string err; + if (!sv::is_lua_classname(name)) { + err = "invalid class name: " + name; + } else { + err = "not a class: " + name; + } + return err; +} + +eng::string LuaCoreStack::getclass(LuaSlot classtab, LuaSlot input) const { + LuaVar lookup; + LuaExtStack LS(L_, lookup); + + int xt = xtype(input); + if (xt == LUA_TSTRING) { + LS.rawget(lookup, LuaRegistry, "classes"); + LS.rawget(classtab, lookup, input); + if (xtype(classtab) != LUA_TT_CLASS) { + eng::string classname = LS.ckstring(input); + return nonexistentclass(LS.ckstring(input)); + } + return ""; + } else if (xt == LUA_TT_TANGIBLE) { + if (!LS.getmetatable(lookup, input)) { + eng::string err = "inactive tangible has no class"; + return err; + } + LS.rawget(classtab, lookup, "__index"); + if (xtype(classtab) != LUA_TT_CLASS) { + eng::string err = "tangible has invalid class"; + } + return ""; + } else if (xt == LUA_TT_CLASS) { + LS.set(classtab, input); + return ""; + } else { + eng::string err = "getclass must be passed a string, class, or tangible"; + return err; + } +} + +eng::string LuaCoreStack::getclass(LuaSlot classtab, std::string_view classname) const { + LuaVar lookup; + LuaExtStack LS(L_, lookup); + LS.rawget(lookup, LuaRegistry, "classes"); + LS.rawget(classtab, lookup, classname); + if (xtype(classtab) != LUA_TT_CLASS) { + return nonexistentclass(eng::string(classname)); + } + return ""; +} + +void LuaCoreStack::makeclass(LuaSlot classtab, LuaSlot classname) const { + LuaVar classes, classnames, globtab, cname; + LuaExtStack LS(L_, classes, classnames, globtab, cname); + + // Validate the class name. + assert(LS.isstring(classname)); + assert(sv::is_lua_classname(LS.ckstring(classname))); + + // Fetch the classes table from the registry. + LS.rawget(classes, LuaRegistry, "classes"); + assert(LS.istable(classes)); + + // Fetch the classnames table from the registry. + LS.rawget(classnames, LuaRegistry, "classnames"); + assert(LS.istable(classnames)); + + // Fetch the global environment from the registry. + LS.getglobaltable(globtab); + + // Get the classtab from the classes table. + LS.rawget(classtab, classes, classname); + + // Make a new table if necessary. + if (!LS.istable(classtab)) { + LS.newtable(classtab); + LS.rawset(classes, classname, classtab); + LS.rawset(classnames, classtab, classname); + } + + // Put the table into the global environment. + LS.rawset(globtab, classname, classtab); + + // Repair the special fields. + LS.settabletype(classtab, LUA_TT_CLASS); + LS.rawset(classtab, "__index", classtab); +} + +void LuaCoreStack::makeclass(LuaSlot tab, std::string_view name) const { + push_any_value(name); + LuaSpecial classname(lua_gettop(L_)); + makeclass(tab, classname); + lua_pop(L_, 1); +} + +void LuaCoreStack::maketan(LuaSlot tab, int64_t id) const { + assert(validpositiveinteger(id)); + + LuaVar tangibles, metatab; + LuaExtStack LS(L_, tangibles, metatab); + + // Try to get the existing tangible. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(tab, tangibles, id); + + // If we succeeded, return it. + if (LS.istable(tab)) { + return; + } + + // Create the tangible's database and metatable. + LS.set(tab, LuaNewTable); + LS.set(metatab, LuaNewTable); + LS.setmetatable(tab, metatab); + + // Mark the tangible using the tabletype field. + LS.settabletype(tab, LUA_TT_TANGIBLE); + LS.settabletype(metatab, LUA_TT_TANGIBLEMETA); + + // Store the tangible ID and lock the metatable. + LS.rawset(metatab, "id", id); + LS.rawset(metatab, "__metatable", false); + + // Store the database into the tangibles table. + LS.rawset(tangibles, id, tab); +} + + +bool LuaCoreStack::tanblank(LuaSlot tab) const { + bool result = true; + if (istable(tab) && gettabletype(tab) == LUA_TT_TANGIBLE) { + if (lua_getmetatable(L_, tab.index())) { + lua_pushstring(L_, "threads"); + lua_rawget(L_, -2); + if (lua_type(L_, -1) == LUA_TTABLE) { + result = false; + } + lua_pop(L_, 2); + } + } + return result; +} + +int64_t LuaCoreStack::tanid(LuaSlot tab) const { + int64_t result = 0; + if (istable(tab) && gettabletype(tab) == LUA_TT_TANGIBLE) { + if (lua_getmetatable(L_, tab.index())) { + lua_pushstring(L_, "id"); + lua_rawget(L_, -2); + if (lua_type(L_, -1) == LUA_TNUMBER) { + result = int64_t(lua_tonumber(L_, -1)); + } + lua_pop(L_, 2); + } + } + return result; +} + +bool LuaCoreStack::tangetclass(LuaSlot classobj, LuaSlot tab) { + if (istable(tab) && (gettabletype(tab) == LUA_TT_TANGIBLE) && lua_getmetatable(L_, tab.index())) { + lua_pushstring(L_, "__index"); + lua_rawget(L_, -2); + lua_replace(L_, classobj); + lua_pop(L_, 1); + if (istable(classobj) && (gettabletype(classobj) == LUA_TT_CLASS)) { + return true; + } + } + set(classobj, LuaNil); + return false; +} + +bool LuaCoreStack::issortablekey(LuaSlot s) const { + int type = lua_type(L_, s); + return (type == LUA_TBOOLEAN) || (type == LUA_TNUMBER) || (type == LUA_TSTRING); +} + +void LuaCoreStack::movesortablekey(LuaSlot key, LuaCoreStack &otherstack, LuaSlot otherslot) { + int type = lua_type(L_, key); + switch (type) { + case LUA_TBOOLEAN: + lua_pushboolean(otherstack.L_, lua_toboolean(L_, key)); + lua_replace(otherstack.L_, otherslot); + break; + case LUA_TNUMBER: + lua_pushnumber(otherstack.L_, lua_tonumber(L_, key)); + lua_replace(otherstack.L_, otherslot); + break; + case LUA_TSTRING: { + size_t len; + const char *str = lua_tolstring(L_, key, &len); + lua_pushlstring(otherstack.L_, str, len); + lua_replace(otherstack.L_, otherslot); + break; + } + default: + assert(false && "movesortablekey: not a sortable key"); + } +} + +void LuaCoreStack::cleartable(LuaSlot tab, bool clearmeta) const { + assert(istable(tab)); + lua_pushnil(L_); + while (lua_next(L_, tab.index()) != 0) { + lua_pop(L_, 1); // Pop the old value. + lua_pushvalue(L_, -1); // Clone the key + lua_pushnil(L_); // Push the new value. + lua_rawset(L_, tab.index()); + } + if (clearmeta) { + lua_pushnil(L_); + lua_setmetatable(L_, tab.index()); + } +} + +int LuaCoreStack::rawlen(LuaSlot obj) const { + return lua_rawlen(L_, obj.index()); +} + +int LuaCoreStack::nkeys(LuaSlot obj) const { + return lua_nkeys(L_, obj.index()); +} + +int LuaCoreStack::gettabletype(LuaSlot tab) const { + uint16_t bits = lua_getflagbits(L_, tab.index()); + return LUA_TT_GENERAL + (bits & 0x000F); +} + +void LuaCoreStack::settabletype(LuaSlot tab, int t) const { + assert((t >= LUA_TT_GENERAL) && (t <= LUA_TT_CLASS)); + int offset = (t - LUA_TT_GENERAL); + lua_modflagbits(L_, tab.index(), 0x000F, offset); +} + +int LuaCoreStack::xtype(LuaSlot slot) const { + int t = lua_type(L_, slot); + if (t != LUA_TTABLE) return t; + uint16_t bits = lua_getflagbits(L_, slot); + return LUA_TT_GENERAL + (bits & 0x000F); +} + +bool LuaCoreStack::getvisited(LuaSlot tab) const { + uint16_t bits = lua_getflagbits(L_, tab.index()); + return (bits & 0x0010); +} + +void LuaCoreStack::setvisited(LuaSlot tab, bool visited) const { + lua_modflagbits(L_, tab.index(), 0x0010, visited ? 0x0010 : 0); +} + +WorldType LuaCoreStack::get_world_type() const { + lua_pushstring(L_, "worldtype"); + lua_rawget(L_, LUA_REGISTRYINDEX); + lua_Integer n = lua_tointeger(L_, -1); + lua_pop(L_, 1); + assert(n != 0); + return (WorldType)n; +} + +void LuaCoreStack::set_world_type(WorldType t) const { + lua_pushstring(L_, "worldtype"); + lua_pushnumber(L_, (int)t); + lua_rawset(L_, LUA_REGISTRYINDEX); +} + +void LuaCoreStack::guard_nopredict(const char *fn) { + if (lua_isyieldable(L_)) { + if (!is_authoritative()) { + lua_yield(L_, 0); + luaL_error(L_, "unexplained nopredict failure in %s", fn); + } + } +} + +LuaKeywordParser::LuaKeywordParser(lua_State *L, int slot) { + L_ = L; + slot_ = slot; + not_table_ = !lua_istable(L_, slot_); + if (not_table_) { + lua_newtable(L_); + lua_replace(L_, slot_); + } +} + +bool LuaKeywordParser::parse(LuaSlot out, const char *kw) { + lua_pushstring(L_, kw); + lua_rawget(L_, slot_); + lua_replace(L_, out.index()); + if (!lua_isnil(L_, out.index())) { + parsed_.insert(kw); + return true; + } else { + return false; + } +}; + +eng::string LuaKeywordParser::final_check() { + if (not_table_) { + return "expected a keyword table"; + } + lua_pushnil(L_); + while (lua_next(L_, slot_) != 0) { + lua_pop(L_, 1); // Don't need the value. + if (!lua_isstring(L_, -1)) { + return "keyword table contains non-string key"; + } + const char *kw = lua_tostring(L_, -1); + if (parsed_.find(kw) == parsed_.end()) { + eng::ostringstream oss; + oss << "keyword " << kw << " not known"; + return oss.str(); + } + } + return ""; +} + +void LuaKeywordParser::final_check_throw() { + eng::string err = final_check(); + if (!err.empty()) { + luaL_error(L_, "%s", err.c_str()); + } +} + +const char *LuaByteReader::lua_reader(lua_State *L, void *ud, size_t *size) { + LuaByteReader *reader = (LuaByteReader*)ud; + *size = reader->size_; + const char *data = reader->data_; + reader->data_ = 0; + reader->size_ = 0; + return data; +} diff --git a/luprex/cpp/core/luastack.hpp b/luprex/cpp/core/luastack.hpp new file mode 100644 index 00000000..1eed44ad --- /dev/null +++ b/luprex/cpp/core/luastack.hpp @@ -0,0 +1,1353 @@ +///////////////////////////////////////////////////////// +// +// +// GENERAL CONCEPT +// +// The original lua C API asks you to work with a stack machine. When you +// use the original API, you're manually pushing and popping in every +// line of code. I find it hard to remember what stack position contains +// what value. This wrapper is designed to alleviate that problem. +// +// However, the lua garbage collector demands that we keep all lua values +// on the lua stack. I can't change that. I can create a wrapper, but I +// still have to keep all lua values on the lua stack. So, here's how +// this wrapper works: +// +// At the beginning of any C++ lua function, we allocate enough space on +// the lua stack to hold all the arguments, all the return values, and all +// the temporary values that we need. We give each of the stack slots +// that we allocate a human-readable name. From that point forward, +// we don't push or pop anything on the lua stack. Instead, we do all our +// lua value manipulation using the stack slots that we allocated and named +// at the beginning of the function. +// +// +///////////////////////////////////////////////////////// +// +// LUAARG, LUARET, LUAVAR, and LUADEFSTACK +// +// The best way to explain this wrapper is with an example. This +// function, 'table_removevalue', takes a table and a 'removethis' +// value. It searches the table for any (key,val) pairs +// where val==removethis, and it removes those pairs. +// +// int table_removevalue(lua_State *L) { +// LuaArg table, removethis; +// LuaRet returnvalue; +// LuaVar key, val; +// LuaDefStack LS(L, table, removethis, returnvalue, key, val); +// LS.set(key, LuaNil); +// while (LS.next(table, key, val)) { +// if (LS.rawequal(val, removethis)) { +// LS.rawset(table, key, LuaNil); +// } +// } +// LS.set(returnvalue, table); +// return LS.result(); +// } +// +// Now I'll explain this function. First, you have some declarations for +// LuaArg (lua function arguments), LuaRet (lua function return values), and +// LuaVar (lua local variables). These are small structs each containing +// an 'int' indicating an absolute position on the lua stack. These are all +// collectively called lua stack slots. Class LuaArg, LuaRet, and +// LuaVar all derive from a base class LuaSlot. +// +// At construction time, all the LuaSlots are initialized to zero, which +// means they aren't ready to use yet. They don't point to anywhere on the +// lua stack. Remember, lua numbers everything starting at 1, so zero is +// not a valid lua stack position. +// +// After the LuaSlot declarations, you have the LuaDefStack constructor. +// This takes the lua stack as a parameter, and then all the LuaSlots. +// This allocates space on the lua stack for all the LuaSlots. When it's +// done, the lua stack will contain exactly five values, one for each of the +// five LuaSlots. The LuaSlot structs will contain the stack positions of +// these values. The LuaRet and LuaVar stack slots will be initialized to nil. +// The LuaArg stack slots will be initialized with the function arguments. +// After the LuaDefStack constructor, you are ready to do lua calculations. +// +// Next, you have the loop that iterates over the table. To iterate over +// a table in lua, you initialize 'key' to nil, then you call the 'next' +// operator to get the next (key,val) pair. The 'next' operator will return +// false if there is no next pair. For each (key,val) pair in the table, +// we check if the val is equal to the thing we want to remove, and if so, +// we change the table entry to nil. After the loop, we set the returnvalue +// slot to the table that was passed in. +// +// The last line, return LS.result(), is a piece of boilerplate, every lua +// C function must end with this. This function removes everything but the +// return values from the stack, and returns the number of return values. +// +// +///////////////////////////////////////////////////////// +// +// CLASS LUAEXTSTACK. +// +// Class LuaDefStack, which I showed above, is meant to be used at the +// top level of a C++ lua function. +// +// Class LuaExtStack is meant to be used when you want to allocate +// some MORE stack slots halfway through a C++ lua function. +// Class LuaExtStack is particularly useful when you want to write +// a recursive implementation. Typically you would put LuaDefStack +// in the top-level function, and LuaExtStack in the recursive +// implementation. +// +// Class LuaExtStack differs in two ways: first of all, it doesn't allow +// LuaArg and LuaRet slots, only LuaVar. Second, it has a destructor that +// automatically puts the stack back the way it was when it was constructed. +// +// You might wonder why class LuaDefStack doesn't have a destructor that +// cleans up the lua stack. It's because of return values: it can't remove +// everything from the stack, because it has to leave the return values +// on the stack. +// +// I call these two classes the 'LuaStack' classes. When I say that +// the LuaStack classes do something, I mean that both LuaDefStack +// and LuaExtStack do that thing. +// +// +///////////////////////////////////////////////////////// +// +// THE LIBRARY OF BUILTIN OPERATORS +// +// The LuaStack classes provide a whole library of methods to operate +// on lua values. Roughly speaking, there is one function in LuaStack +// for every function in the raw lua API, and they are similarly named. +// +// However, the functions in the raw lua API push and pop values on the +// lua stack. The equivalent functions in LuaStack take inputs from +// LuaSlots, and store their outputs into other LuaSlots. Nothing is +// pushed or popped. +// +// To get the complete list, you will have to scroll down to class +// LuaCoreStack, below. All the prototypes are there, and they are all +// documented. +// +// +///////////////////////////////////////////////////////// +// +// AUTOMATIC TYPE CONVERSION +// +// In some cases, LuaStack can do automatic conversions of C++ values into +// lua values. This is supported in any function where it makes sense. +// One function that supports automatic conversion is 'LuaStack::set': +// +// LS.set(val1, val2); +// +// This is actually a copy operation that copies from one lua local variable to +// another. But using auto conversions, it can also be used to assign C++ +// values to lua slots. These are all valid: +// +// LS.set(value, 1); // int and int64_t can be converted. +// LS.set(value, "banana"); // char*, string, and string_view can be converted. +// LS.set(value, true); // bool can be converted. +// LS.set(value, 2.39); // float and double can be converted. +// LS.set(value, LuaNil); // This special token stores nil. +// +// Automatic conversion is generally allowed in any context where it would +// make sense. For example, these are all valid: +// +// LS.rawget(value, mytable, 1); +// LS.rawget(value, mytable, "banana"); +// LS.rawget(value, mytable, true); +// LS.rawget(value, mytable, 2.39); +// LS.rawget(value, mytable, LuaNil); +// +// There's also the 'LuaNewTable' constant. This is handled by the +// auto-conversion system, but it's not really a conversion: +// +// LS.set(value, LuaNewTable); +// +// This will cause a new table to be created, and stored in value. +// +// +///////////////////////////////////////////////////////// +// +// API INTEROPERABILITY +// +// This wrapper can intentionally be mixed with the standard, raw lua API. +// You still have an explicit handle to the lua stack, and you can get the +// stack addresses out of the LuaSlot variables using LuaSlot::index. So +// if there's some tricky thing you can't do with this wrapper, you can always +// fall back to the raw API for just the section of code that you need to. +// +// I also sometimes use the raw lua API for code that is doing truly +// unusual things, that aren't easy to do with this wrapper. +// +///////////////////////////////////////////////////////// +// +// +// ON RAWGET AND METAMETHODS +// +// The raw lua API provides functions like lua_gettab and lua_equal +// which automatically call metamethods. I do not think it is wise to +// habitually use functions like that, because some of the code we write +// is not executed in a protected context. That means that a metamethod +// that generates an error would bring down the whole system rather than +// just stopping a script thread. It also means that a metamethod could +// return an invalid piece of data, corrupting a system data structure +// rather than just a script data structure. +// +// Because there are so many contexts where it is just not safe to call +// metamethods, I've made it deliberately difficult to call metamethods. +// Our API includes 'rawget' which doesn't call metamethods, +// but it omits 'gettab' which does. +// +// If someday we actually need metamethod support, we can do that, +// but I'll have to write a safe wrapper for them. I know how to do that, +// but it's a lot of work and I'm not going to bother unless it's needed. +// +// +///////////////////////////////////////////////////////// +// +// LUA TABLE TYPES +// +// We have modified the lua interpreter to allow us to assign +// table subtypes to different tables. Most lua tables are +// marked as 'general' tables. If you create a table using the +// lua newtable operator, you'll get a general table. +// +// Aside from general tables, we have special table types for the +// lua registry, for lua global environment tables, for tangibles, +// for tangible metatables, and for classes. These tables get +// handled specially in various parts of the code. +// +// +///////////////////////////////////////////////////////// +// +// LIGHTUSERDATA AND CLASS LUATOKEN +// +// Before I tell you what tokens are, I'm going to tell you what problem +// I was trying to solve that led me to create tokens. +// +// I was trying to write a JSON to LUA converter. That's mostly +// straightforward. Json tables are very similar to lua tables. +// For example, consider this JSON: +// +// { +// "name": "John Smith", +// "alive": true, +// "address": { +// "city": "New York", +// "state": "NY", +// }, +// "spouse": null +// } +// +// That converts very straightforwardly to a lua table: +// +// { +// name = "John Smith", +// alive = true, +// address = { +// city = "New York", +// state = "NY", +// }, +// spouse = nil +// } +// +// There's only one problem here: that "spouse" line doesn't really work. +// Setting a key to nil in lua is the same as not setting that key at all. +// That's not correct. Instead of storing json null as lua nil, +// we could store json null as the string "null". But that would be make it +// impossible to parse and store the literal string "null". That's not correct +// either. +// +// Lua has a datatype called 'lightuserdata'. A lightuserdata holds an +// int64. That gives me an option: I can store json null as +// lightuserdata(0x6E756C6C00000000). When we see this lightuserdata +// value, we would know we have a json null. Why 0x6E756C6C00000000? +// Because if you interpret those 8 bytes as 8 ascii characters, it's the +// string "null". +// +// So that finally brings me to what a "token" is. A token is a lightuserdata +// containing up to 8 ascii characters. So in effect, it's a short string, +// but it's a string that's distinguishable from a normal lua string. It +// doesn't have the same type as a lua string (it shows up as a lightuserdata). +// The purpose of tokens is to represent special unique values, like json null. +// +// To make working with tokens easy, I've created a C++ class 'LuaToken'. +// It stores an int64. You can construct a LuaToken in two different ways: +// +// LuaToken(0x6E756C6C00000000) +// LuaToken("null") +// +// Those are equivalent. The second form is just as fast as the first, +// because of C++ constexpr magic. You can use our automatic type conversion +// system to automatically convert C++ LuaToken structs into lua lightuserdata +// values, like this: +// +// LS.set(value, LuaToken("null")) +// +// When our pretty-printer is printing out lua data structures, and it +// encounters a lightuserdata, it prints it out as a token, ie, as a short +// string, but using brackets instead of quotation marks. +// +///////////////////////////////////////////////////////// +// +// LUA CLASSES +// +// This module adds the concept of a 'class' to lua. The function +// LuaStack::makeclass creates a class. This function is exposed to lua. +// For example, you could create a lua class to manipulate deques: +// +// makeclass("deque") +// +// This creates a new table, which you will find in the global +// environment under the name 'deque'. The class is a table for +// storing functions related to deques. You can now add lua functions +// to the class: +// +// function deque.insertr(...) +// end +// +// If you call makeclass a second time with the same classname, this +// is a no-op. This is useful because if you have two sourcefiles that +// both add functions to class 'deque', they can both makeclass 'deque', +// and no conflict will occur. +// +// Class tables have a distinct LuaTableType: LUA_TT_CLASS. That +// way, it is easy to tell that the table is intended as a class. +// Class tables are treated uniquely by our engine in several ways. +// +// A class also has a field "__index" which points to itself. +// C.__index = C. That makes it possible to use the class as a +// metatable, and any attempt to look up a name in the table +// that fails will then attempt to look up that name in the class. +// +// +///////////////////////////////////////////////////////// +// +// LUADEFINE +// +// LuaDefine is a macro that defines a C function which is exposed to lua. +// In addition to actually defining the C function, it also creates a global +// registry of all such functions. The registry is used to actually +// know which functions to inject into lua. +// +// Here is an example of a typical LuaDefine: +// +// LuaDefine(table_removevalue, "table, removethis", +// "|This function removes a specified value from a table." +// "|", +// "|Iterates over all the (key, val) pairs in a table and" +// "|removes the ones where val == removethis." +// ) { +// LuaArg table, removethis; +// LuaRet returnvalue; +// LuaVar key, val; +// LuaDefStack LS(L, table, removethis, returnvalue, key, val); +// LS.set(key, LuaNil); +// while (LS.next(table, key, val)) { +// if (LS.rawequal(val, removethis)) { +// LS.rawset(table, key, LuaNil); +// } +// } +// LS.set(returnvalue, table); +// return LS.result(); +// } +// +// So you might notice that this is the same example function from +// earlier, but this time with LuaDefine. If you omit the LuaDefine +// and write the function as it was shown earlier, you will get a +// function that can be called from C++, and which works fine when +// called from C++, but it won't be visible from lua. +// +// The example function above will show up in lua as 'table.removevalue'. +// This lua function name is derived straightforwardly from the C++ +// function name. +// +// Note that both of the string parameters to LuaDefine are part of the +// function documentation, neither has any effect on how the lua function +// behaves. The function documentation goes into the registry and from +// there is accessible through the lua documentation system. +// +///////////////////////////////////////////////////////// + + +#ifndef LUASTACK_HPP +#define LUASTACK_HPP + +#include "util.hpp" +#include "wrap-string.hpp" +#include "wrap-set.hpp" +#include +#include +#include +#include + +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" +#include "eris.h" + +//////////////////////////////////////////////////////////////////// +// +// LUA TABLE TYPES +// +//////////////////////////////////////////////////////////////////// + +enum LuaTableType { + LUA_TT_GENERAL = LUA_NUMTAGS, + LUA_TT_REGISTRY, + LUA_TT_GLOBALENV, + LUA_TT_TANGIBLE, + LUA_TT_TANGIBLEMETA, + LUA_TT_CLASS, + + LUA_TT_SENTINEL +}; + +//////////////////////////////////////////////////////////////////// +// +// LuaToken +// +//////////////////////////////////////////////////////////////////// + +struct LuaToken { +public: + uint64_t value; + + // Get rid of the default constructors. + // + template + LuaToken(T arg) = delete; + + // Construct a token from a short string. + // + constexpr LuaToken(const char *str) : value(literal_to_token(str)) {} + + // Construct a token from an int64. + // + LuaToken(uint64_t v) : value(v) {} + + // Construct a token from a void pointer. + // + LuaToken(void *v) : value((uint64_t)v) {} + + // Default constructor: construct the empty token. + // + LuaToken() : value(0) {} + + // Empty: return true if the token is all zero bytes. + // + bool empty() const { return value == 0; } + + // Compare two tokens for equality. + // + bool operator ==(const LuaToken &other) const { return value == other.value; } + + // Convert the token to a void pointer. + // + void *voidvalue() const { return (void*)value; } + + // Convert the token to a string. + // + eng::string str() const; + +private: + static constexpr uint64_t literal_to_token(const char *str) { + uint64_t result = 0; + for (int i = 0; i < 8; i++) { + unsigned char c = *str; + result = (result << 8) + c; + if (*str) str++; + } + return result; + } +}; + +//////////////////////////////////////////////////////////////////// +// +// LuaSlots. +// +//////////////////////////////////////////////////////////////////// + +class LuaSlot : public eng::nevernew { +protected: + int index_; + + constexpr LuaSlot(int n) : index_(n) {} + +public: + // LuaSlots are normally constructed without arguments. + // They are uninitialized until the LuaStack constructor runs. + // + constexpr LuaSlot() : index_(0) {} + + // You can fetch the stack index from the LuaSlot. + // + inline int index() const { + return index_; + } + +private: + // Our code can fetch the stack index using an implicit conversion. + // + inline operator int() const { + return index_; + } + + friend class LuaCoreStack; + friend class LuaDefStack; + friend class LuaExtStack; +}; + +class LuaArg : public LuaSlot {}; +class LuaRet : public LuaSlot {}; +class LuaVar : public LuaSlot {}; + +class LuaSpecial : public LuaSlot { +public: + constexpr LuaSpecial(int n) : LuaSlot(n) {} +}; + +extern LuaSpecial LuaRegistry; + +//////////////////////////////////////////////////////////////////// +// +// LuaExtraArgs +// +//////////////////////////////////////////////////////////////////// + +class LuaExtraArgs { +private: + int index_; + int size_; + +public: + LuaExtraArgs() { + index_ = 0; + size_ = 0; + } + + LuaSpecial operator[] (int n) const { return LuaSpecial(index_ + n); } + int size() const { return size_; } + + friend class LuaCoreStack; + friend class LuaDefStack; +}; + +//////////////////////////////////////////////////////////////////// +// +// LuaNil and LuaNewTable +// +//////////////////////////////////////////////////////////////////// + +class LuaNilMarker {}; +extern LuaNilMarker LuaNil; + +class LuaNewTableMarker {}; +extern LuaNewTableMarker LuaNewTable; + +//////////////////////////////////////////////////////////////////// +// +// LuaCoreStack +// +// This is the common base class for LuaDefStack and LuaExtStack. +// You should use one of those classes in your code, not this class. +// However, this class is where all the interesting operators on lua +// local variables resides. +// +//////////////////////////////////////////////////////////////////// + +class LuaCoreStack : public eng::nevernew { +protected: + lua_State *L_; + +public: + // Constructor. You should almost never use this, instead, + // you should construct a LuaDefStack or a LuaExtStack. + // + LuaCoreStack(lua_State *L) : L_(L) {} + + // Get the raw pointer to the lua_State. + // + lua_State *state() const { return L_; } + + // Turn a Lua value into a C++ value, if possible. + // + // If the lua value doesn't match the desired type, then these return + // an empty optional. The ones that return bool only verify + // the value's type, they don't actually fetch the value. + // + std::optional tryboolean(LuaSlot s) const; + std::optional tryinteger(LuaSlot s) const; + std::optional tryint(LuaSlot s) const; + std::optional trynumber(LuaSlot s) const; + std::optional trystring(LuaSlot s) const; + std::optional trystringview(LuaSlot s) const; + std::optional trythread(LuaSlot s) const; + std::optional trytoken(LuaSlot s) const; + std::optional tryxyz(LuaSlot s) const; + bool trytable(LuaSlot s) const; + bool trynil(LuaSlot s) const; + bool tryfunction(LuaSlot s) const; + bool trycfunction(LuaSlot s) const; + bool trytangible(LuaSlot s) const; + + // Turn a lua value into a C++ value, or throw an error. + // + // If the lua value doesn't match the desired type, + // then these throw a lua error. It is invalid to use these + // outside of a protected context. The argname is used + // for making a nice error message. + // + bool ckboolean(LuaSlot s, const char *argname = "value") const; + lua_Integer ckinteger(LuaSlot s, const char *argname = "value") const; + int ckint(LuaSlot s, const char *argname = "value") const; + lua_Number cknumber(LuaSlot s, const char *argname = "value") const; + eng::string ckstring(LuaSlot s, const char *argname = "value") const; + std::string_view ckstringview(LuaSlot s, const char *argname = "value") const; + lua_State * ckthread(LuaSlot s, const char *argname = "value") const; + LuaToken cktoken(LuaSlot s, const char *argname = "value") const; + util::DXYZ ckxyz(LuaSlot s, const char *argname = "value") const; + void cktable(LuaSlot s, const char *argname = "value") const; + void cknil(LuaSlot s, const char *argname = "value") const; + void ckfunction(LuaSlot s, const char *argname = "value") const; + void ckcfunction(LuaSlot s, const char *argname = "value") const; + void cktangible(LuaSlot s, const char *argname = "value") const; + + // Check if a lua value can be converted to C++. + // + // These functions check if a value can be converted + // to a C++ value. They don't actually return the C++ value. + // It is more efficient to use the 'try' or 'ck' functions above if + // you also want the value. + // + bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; } + bool isinteger(LuaSlot s) const { return bool(tryinteger(s)); } + bool isint(LuaSlot s) const { return bool(tryint(s)); } + bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; } + bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } + bool isstringview(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } + bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; } + bool istoken(LuaSlot s) const { return lua_type(L_, s) == LUA_TLIGHTUSERDATA; } + bool isxyz(LuaSlot s) const { return bool(tryxyz(s)); } + bool istable(LuaSlot s) const { return lua_istable(L_, s); } + bool isnil(LuaSlot s) const { return lua_isnil(L_, s); } + bool isfunction(LuaSlot s) const { return lua_isfunction(L_, s); } + bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s); } + bool istangible(LuaSlot s) const { return bool(trytangible(s)); } + + // Create a new interpreter using the specified allocator. + // + // Typically, the allocator used would be eng::l_alloc. + // You can also pass nullptr to use a default allocator based + // on malloc. + // + static lua_State *newstate (lua_Alloc allocf); + + // Create a new thread. + // + // The target parameter is an output parameter, this will contain + // the new thread. Also returns a C++ pointer to the thread. Remember + // that the C++ pointer by itself doesn't prevent garbage collection, + // you must keep the thread in the LuaSlot or in some other lua data + // structure to prevent it from getting garbage collected. + // + lua_State *newthread(LuaSlot target) const; + + // Get the type of a LuaSlot. + // + // Returns one of the standard lua type tags. These include: + // + // LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, + // LUA_TTHREAD, LUA_TLIGHTUSERDATA, LUA_TUSERDATA. + // + int type(LuaSlot s) const { return lua_type(L_, s); } + + // Get the extended type of a LuaSlot. + // + // If the variable contains a table, returns one of the LuaTableType + // constants. Search for this enum above. If it is not a table, + // returns one of the standard lua type tags. See the 'type' + // method above. + // + int xtype(LuaSlot slot) const; + + // Get the table type of a lua table. + // + // Tab must contain a lua table. Returns a value from enum LuaTableType. + // + int gettabletype(LuaSlot tab) const; + + // Set the table type of a lua table. + // + // Tab must contain a lua table. T must be a value from enum LuaTableType. + // + void settabletype(LuaSlot tab, int t) const; + + // Get the length of a lua string. + // + // Techically, you can also use this on tables, but it's not recommended. + // Instead, use 'nkeys' below. The semantics of rawlen on tables + // is just plain weird: see lua documentation if you are curious. + // + int rawlen(LuaSlot val) const; + + // Get the number of key/value pairs in a lua table. + // + // This works on any table, even tables where the keys aren't integers. + // + int nkeys(LuaSlot tab) const; + + // Get the metatable of a table. + // + // Tab must be a table. The metatable of tab is stored in mt. + // + bool getmetatable(LuaSlot mt, LuaSlot tab) const; + + // Set the metatable of a table. + // + // Tab must be a table. Mt must be a table or nil. + // + void setmetatable(LuaSlot tab, LuaSlot mt) const; + + // Remove the metatable from a table. + // + // Tab must be a table. The metatable, if any, is removed from tab. + // + void clearmetatable(LuaSlot tab) const; + + // Create a new table. + // + // The new table is stored in target. + // + void newtable(LuaSlot target) const; + + // Create a new table with a storage hint. + // + // The new table is stored in target. The new table has space + // pre-allocated for narr array elements and nrec non-array elements. + // + void createtable(LuaSlot target, int narr, int nrec) const; + + // Get the global environment table. + // + // The global environment table is stored in gltab. + // + void getglobaltable(LuaSlot gltab) const; + + // Delete everything in a table. + // + // Tab must be a table. Removes all (key,val) pairs from tab. + // If clearmeta is true, then the metatable is also removed from tab. + // + void cleartable(LuaSlot tab, bool clearmeta) const; + + // Iterate over the key,val pairs in a table. + // + // Before the iteration begins, you should initialize 'key' to nil. + // Then, you should call 'next' to fetch the next key,val pair in + // the table. You can keep calling 'next' to obtain successive key,val + // pairs until 'next' returns false. + // + // Do not alter 'key' during the iteration, if you do, then the 'next' + // function will return the wrong next-value. + // + bool next(LuaSlot tab, LuaSlot key, LuaSlot val) const; + + // Return true if the int64 can be stored losslessly in a lua_Number. + // + // Lua numbers are actually double-precision floating point. double + // can hold integers losslessly as long as they're small enough to + // fit within the double's mantissa. The mantissa of an IEEE double + // is big enough to hold a 53-bit integer. + // + static bool validinteger(int64_t value) { return (value <= MAXINT) && (value >= -MAXINT); } + + // Return true if the int64 can be stored losslessly and is positive. + // + // This returns true if the number is a validinteger (see above), and is + // a positive number. + // + static bool validpositiveinteger(int64_t value) { return (value <= MAXINT) && (value >= 1); } + + // Get the class name given a class table. + // + // Return the class name if x is a valid class table. Otherwise, returns + // empty string. If nonempty, the result is guaranteed to be a + // validclassname. This can also function as an "isclass" operator. + // + eng::string classname(LuaSlot x) const; + + // Look for a class by class-name. + // + // If there is a problem, returns an error message. There are lots + // of error conditions, including such things as no such class, corrupted + // class, classname invalid, etc. + // + eng::string getclass(LuaSlot tab, LuaSlot name) const; + eng::string getclass(LuaSlot tab, std::string_view name) const; + + // Create a class, or look up an existing class. + // + // Creates a new class, unless the class already exists. Stores the + // class in the global environment table. This routine assert-fails if the + // parameter is not a valid classname. + // + void makeclass(LuaSlot tab, LuaSlot name) const; + void makeclass(LuaSlot tab, std::string_view name) const; + + // Create a tangible, or look up an existing tangible. + // + // If the tangible doesn't exist yet, this creates a tangible stub. + // A stub tangible is an empty table with a metatable containing the + // tangible's ID. Nothing else is present in the stub. It is up to + // the World module to transform stubs into full-blown tangibles. + // Assert-fails if the tangible ID is not a validpositiveinteger. + // + void maketan(LuaSlot tab, int64_t id) const; + + // Return true if a tangible is empty, ie, a stub. + // + bool tanblank(LuaSlot tab) const; + + // Get the ID of a tangible. + // + // This works on both full-blown tangibles and stubs. If tab + // is not a valid tangible, returns zero. + // + int64_t tanid(LuaSlot tab) const; + + // Get the class of a tangible. + // + // If the tangible has been assigned a class, then puts the class + // table into classobj and returns true. Otherwise, sets classobj + // to nil and returns false. + // + bool tangetclass(LuaSlot classobj, LuaSlot tan); + + // Assign a lua variable. + // + // Copies value into target. The 'value' parameter can be a LuaSlot or + // any lua-convertible C++ type. + // + template + void set(LuaSlot target, VT value) const { + push_any_value(value); + lua_replace(L_, target); + } + + // Return true if two values are equal. + // + // Checks if the two values are equal. Note that in lua, if two strings + // contain the same text, then they're equal. The 'value' parameter + // can be a LuaSlot or any lua-convertible C++ type. + // + // This could possibly be faster if we were to write some specializations + // for strings, numbers, and bools. + // + template + bool rawequal(LuaSlot v1, VT value) const { + push_any_value(value); + bool result = lua_rawequal(L_, v1, -1); + lua_pop(L_, 1); + return result; + } + + // Return true if val1 is less than val2. + // + // This is NOT the same as the lua_lessthan function. This is a more + // general function that can compare any two lua objects. + // + // Numbers are compared in the obvious numeric manner. + // Strings are compared alphabetically. + // Booleans are compared with false being less than true. + // Tables are all considered equal to other tables. + // Functions are all considered equal to other functions. + // Coroutines are all considered equal to other coroutines. + // + // Numbers are less than strings. + // Strings are less than booleans. + // Booleans are less than functions. + // Functions are less than coroutines. + // Coroutines are less than tables. + // + // Does not call metamethods. + // + bool genlt(LuaSlot val1, LuaSlot val2) const { return lua_genlt(L_, val1, val2); } + + // Return true if the value is a sortable key. + // + // Sortable keys are: strings, booleans, and numbers. + // These three types can be meaningfully compared using genlt, + // above. They also can be meaningfully transferred from lua to C++ + // and back without losing anything. + // + bool issortablekey(LuaSlot s) const; + + // Move a sortable key to another lua environment. + // + // This is used when you've created two lua interpreters and you + // want to move data from one to the other. The only kinds of data + // you can move are strings, booleans, and numbers. + // + void movesortablekey(LuaSlot val, LuaCoreStack &other, LuaSlot otherslot); + + // Fetch a value from a table. + // + // Fetches the specified key from the table tab, and stores the + // result in target. The key parameter can be a LuaSlot or any lua- + // convertible C++ value. + // + template + void rawget(LuaSlot target, LuaSlot tab, KT key) const { + push_any_value(key); + lua_rawget(L_, tab); + lua_replace(L_, target); + } + + // Store a value in a table. + // + // Sets the table entry for key to value. The key and val + // parameters can be LuaSlots, or they can be any lua-convertible + // C++ value. + // + template + void rawset(LuaSlot tab, KT key, VT value) const { + push_any_value(key); + push_any_value(value); + lua_rawset(L_, tab); + } + + // Get the 'visited' bit from a lua table. + // + bool getvisited(LuaSlot tab) const; + + // Set the 'visited' bit in a lua table. + // + void setvisited(LuaSlot tab, bool visited) const; + + // Store the world type in the registry. + // + // This just stores the enum value in the registry key "worldtype". + // + void set_world_type(WorldType t) const; + + // Return the world type from the registry. + // + // This just fetches the enum value from the registry key "worldtype". + // + WorldType get_world_type() const; + + // Return true if this world is authoritative. + // + // This fetches the enum value from the registry key "worldtype", + // then it checks if the world type is authoritative. + // + bool is_authoritative() { return util::is_authoritative(get_world_type()); } + + // Yield this thread with zero if in a nonauth model, and not a probe. + // + // The function name is just used for generating better error messages. + // + void guard_nopredict(const char *fn); + + // This is the largest integer that can be stored in a lua_Number. + // In other words, any 53-bit number can be stored. + // + static const int64_t MAXINT = 0x001FFFFFFFFFFFFF; + + // Template Specializations. + // + // These are all specializations of functions that are defined above. + // These are typically here purely to make the functions above faster. + // + void set(LuaSlot target, LuaSlot value) const { + lua_copy(L_, value, target); + } + + bool rawequal(LuaSlot v1, LuaSlot v2) const { + return lua_rawequal(L_, v1, v2); + } + + void rawget(LuaSlot target, LuaSlot tab, int key) const { + lua_rawgeti(L_, tab, key); + lua_replace(L_, target); + } + + template + void rawset(LuaSlot tab, int key, VT value) const { + push_any_value(value); + lua_rawseti(L_, tab, key); + } + +protected: + + // Assign slots: this is used by the LuaDefStack and LuaExtStack + // constructors to assign stack indices to LuaSlots. + // + template + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaRet &v, SS & ... stackslots) { + v.index_ = retp; + vassign_slots(retp+1, argp, varp, extrap, extrac, stackslots...); + } + template + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaArg &v, SS & ... stackslots) { + v.index_ = argp; + vassign_slots(retp, argp+1, varp, extrap, extrac, stackslots...); + } + template + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaVar &v, SS & ... stackslots) { + v.index_ = varp; + vassign_slots(retp, argp, varp+1, extrap, extrac, stackslots...); + } + template + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaExtraArgs &v, SS & ... stackslots) { + v.index_ = extrap; + v.size_ = extrac; + vassign_slots(retp, argp, varp, extrap, extrac, stackslots...); + } + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac) {} + + + // Push any value on the stack, by type. + // + inline void push_any_value(LuaNewTableMarker s) const { lua_newtable(L_); } + inline void push_any_value(LuaNilMarker s) const { lua_pushnil(L_); } + inline void push_any_value(LuaSlot s) const { lua_pushvalue(L_, s); } + inline void push_any_value(const eng::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); } + inline void push_any_value(std::string_view s) const { lua_pushlstring(L_, s.data(), s.size()); } + inline void push_any_value(const char *s) const { lua_pushstring(L_, s); } + inline void push_any_value(float s) const { lua_pushnumber(L_, s); } + inline void push_any_value(double s) const { lua_pushnumber(L_, s); } + inline void push_any_value(int s) const { lua_pushinteger(L_, s); } + inline void push_any_value(lua_Integer s) const { lua_pushinteger(L_, s); } + inline void push_any_value(lua_CFunction s) const { lua_pushcfunction(L_, s); } + inline void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); } + inline void push_any_value(LuaToken token) const { lua_pushlightuserdata(L_, (void*)(token.value)); } + + // Push multiple values on the stack, in order, by type. + // + template + void push_any_values(T0 arg0, T... args) { + push_any_value(arg0); + push_any_values(args...); + } + void push_any_values() { + } + + // Throw a lua error message + void argerr(const char *arg, const char *tp) const; +}; + +//////////////////////////////////////////////////////////////////// +// +// Argument Counting Templates. +// +// These are internal functions used by LuaDefStack and LuaExtStack +// to help with the processing of constructor arguments. +// +//////////////////////////////////////////////////////////////////// + +struct LuaArgCounts { + int nret; + int narg; + int nvar; + int nextra; + constexpr LuaArgCounts(int nr, int na, int nv, int ne) : nret(nr), narg(na), nvar(nv), nextra(ne) {} + constexpr LuaArgCounts operator +(LuaArgCounts b) const { + return LuaArgCounts(nret + b.nret, narg + b.narg, nvar + b.nvar, nextra + b.nextra); + } +}; + + +template +struct LuaCountArgs; +template<> +struct LuaCountArgs<> { + static constexpr LuaArgCounts value = LuaArgCounts(0,0,0,0); +}; +template +struct LuaCountArgs { + static constexpr LuaArgCounts value = LuaArgCounts(1, 0, 0, 0) + LuaCountArgs::value; +}; +template +struct LuaCountArgs { + static constexpr LuaArgCounts value = LuaArgCounts(0, 1, 0, 0) + LuaCountArgs::value; +}; +template +struct LuaCountArgs { + static constexpr LuaArgCounts value = LuaArgCounts(0, 0, 1, 0) + LuaCountArgs::value; +}; +template +struct LuaCountArgs { + static constexpr LuaArgCounts value = LuaArgCounts(0, 0, 0, 1) + LuaCountArgs::value; +}; + +//////////////////////////////////////////////////////////////////// +// +// LuaDefStack +// +// This version of LuaStack should only be used inside a LuaDefine. It can +// assign stack slots to LuaArg, LuaRet, LuaVar, and LuaExtraArgs. It +// arranges for the arguments to be in the LuaArg variables, and it arranges for +// the LuaRet variables to be returned. It also makes sure that the function +// has the correct number of arguments. +// +// At the end of the LuaDefine function, you're supposed to return LS.result(). +// LS.result causes the allocated stack slots to be freed except for the LuaRet +// values, which have to stay on the stack in order to pass them back as return +// values. LS.result returns the number of LuaRet variables left on the stack. +// +// If you terminate a LuaDefine by calling lua_error or lua_yield, then +// obviously, you don't get a chance to call LS.result. That's not a problem. +// The lua interpreter will clean up after an error or yield. +// +// Implementation note: LuaDefStack doesn't have a destructor to deallocate +// stack slots. That's deliberate: you shouldn't expect this class to clean up +// its stack frame, because after all, it has to leave return values on the +// stack. It would be deceptive to put a destructor, which then doesn't +// actually clean up anyway. Better to just let it be known that this class +// doesn't clean up its stack frame. +// +//////////////////////////////////////////////////////////////////// + + +class LuaDefStack : public LuaCoreStack { +private: + int nret_; + + +public: + template + inline LuaDefStack(lua_State *L, SS & ... stackslots) : LuaCoreStack(L) { + constexpr LuaArgCounts counts = LuaCountArgs::value; + int nargs = lua_gettop(L); + if (counts.nextra == 0) { + if (nargs != counts.narg) { + luaL_error(L_, "function expects exactly %d arguments", counts.narg); + } + } else { + if (nargs < counts.narg) { + luaL_error(L_, "function expects at least %d arguments", counts.narg); + } + } + lua_checkstack(L, counts.nret + counts.nvar + 20); + lua_insert_frame(L, counts.nret + counts.nvar); + vassign_slots(1, 1 + counts.nret + counts.nvar, 1 + counts.nret, 1 + counts.nret + counts.nvar + counts.narg, nargs - counts.narg, stackslots...); + nret_ = counts.nret; + } + + int result() { + lua_settop(L_, nret_); + return nret_; + } + + ~LuaDefStack() { } +}; + +//////////////////////////////////////////////////////////////////// +// +// LuaExtStack +// +// This version of LuaStack is meant to be used in any context where +// you want to assign stack slots to some LuaVars, and then you want +// to automatically deallocate those LuaVars when the LuaExtStack +// goes out of scope. +// +// Unlike LuaDefStack, this version of LuaStack is meant to fully +// deallocate its stack frame when it goes out of scope, so it does +// have a destructor to do that. There is a special case in the +// destructor: if lua is throwing an error, the destructor leaves +// the stack alone, in order to preserve the error message that's +// on the stack. After an error throw, the lua interpreter will +// clean up the stack. +// +//////////////////////////////////////////////////////////////////// + +class LuaExtStack : public LuaCoreStack { +private: + int oldtop_; + +public: + template + LuaExtStack(lua_State *L, SS & ... stackslots) : LuaCoreStack(L) { + constexpr LuaArgCounts counts = LuaCountArgs::value; + static_assert(counts.narg == 0, "LuaExtStack only allows LuaVar, not LuaArg"); + static_assert(counts.nret == 0, "LuaExtStack only allows LuaVar, not LuaRet"); + static_assert(counts.nextra == 0, "LuaExtStack only allows LuaVar, not LuaExtraArgs"); + lua_checkstack(L_, counts.nvar + 20); + oldtop_ = lua_gettop(L_); + for (int i = 0; i < counts.nvar; i++) { + lua_pushnil(L_); + } + vassign_slots(0, 0, oldtop_ + 1, 0, 0, stackslots...); + } + + template + LuaExtStack(const LuaCoreStack &LS0, SS & ... stackslots) : LuaCoreStack(LS0.state(), stackslots...) {} + + int oldtop() const { return oldtop_; } + + ~LuaExtStack() { + if (!lua_isthrowing(L_)) { + lua_settop(L_, oldtop_); + } + } +}; + +//////////////////////////////////////////////////////////////////// +// +// LuaKeywordParser +// +// This is a helper class to help parse tables full of keywords. +// It is meant to make it easier to write LuaDefine functions that +// accept keyword arguments. It helps with the following tasks: +// +// * It makes sure the keyword table actually is a table. +// +// * It makes sure that you didn't put an unrecognized keyword +// into the keyword table. Unrecognized keywords are defined +// as keywords that are never checked using 'parse'. +// +// * It makes sure that you didn't put anything that isn't a +// keyword into the keyword table. +// +//////////////////////////////////////////////////////////////////// + +class LuaKeywordParser { + struct cmp_char { + bool operator () (const char *s1, const char *s2) const { + return strcmp(s1, s2) < 0; + }; + }; +private: + bool not_table_; + lua_State *L_; + int slot_; + eng::set parsed_; + + void init(const lua_State *L, int slot); +public: + // If the slot is not a table, sets the not_table + // flag and creates a dummy table in the slot. + LuaKeywordParser(lua_State *L, int slot); + LuaKeywordParser(const LuaCoreStack &LS, LuaSlot slot) : LuaKeywordParser(LS.state(), slot.index()) {} + + // Fetch a value from the table. This never throws. + // Return true if the value is non-nil. + bool parse(LuaSlot slot, const char *kw); + + // Check if there were any errors. If so, return an + // error message. + eng::string final_check(); + + // Check if there are any errors. If so, throw a lua error. + void final_check_throw(); + + // Fetch the state pointer. + lua_State *state() const { return L_; } +}; + +//////////////////////////////////////////////////////////////////// +// +// Lua Byte Reader +// +// Converts a block of bytes in RAM into a lua_reader. +// +//////////////////////////////////////////////////////////////////// + +class LuaByteReader { +private: + const char *data_; + int64_t size_; +public: + LuaByteReader(const char *d, int64_t s) : data_(d), size_(s) {} + void *lua_reader_userdata() { return this; } + static const char *lua_reader(lua_State *L, void *ud, size_t *size); +}; + +//////////////////////////////////////////////////////////////////// +// +// The Lua Constant Registry +// +//////////////////////////////////////////////////////////////////// + +class LuaConstantReg : public eng::nevernew { +private: + const char *name_; + const char *docs_; + LuaToken tokenvalue_; + lua_Number numbervalue_; + LuaConstantReg *next_; + +public: + static LuaConstantReg *All; + LuaConstantReg(const char *name, const char *docs, LuaToken tokenvalue, lua_Number numbervalue); + + const char *get_name() const { return name_; } + const char *get_docs() const { return docs_; } + LuaToken get_tokenvalue() const { return tokenvalue_; } + lua_Number get_numbervalue() const { return numbervalue_; } + LuaConstantReg *next() const { return next_; } +}; + +//////////////////////////////////////////////////////////////////// +// +// The Lua Function Registry +// +//////////////////////////////////////////////////////////////////// + +class LuaFunctionReg : public eng::nevernew { +private: + const char *name_; + const char *args_; + const char *docs_; + bool sandbox_; + lua_CFunction func_; + LuaFunctionReg *next_; + +public: + static LuaFunctionReg *All; + LuaFunctionReg(const char *name, const char *args, const char *docs, bool sand, lua_CFunction f); + static const LuaFunctionReg *lookup(lua_CFunction fn); + + const char *get_name() const { return name_; } + const char *get_args() const { return args_; } + const char *get_docs() const { return docs_; } + lua_CFunction get_func() const { return func_; } + bool get_sandbox() const { return sandbox_; } + LuaFunctionReg *next() const { return next_; } + void set_func(lua_CFunction fn) { func_ = fn; } +}; + +//////////////////////////////////////////////////////////////////// +// +// LuaDefine and friends. +// +//////////////////////////////////////////////////////////////////// + +#define LuaTokenConstant(name, tvalue, docs) \ + LuaToken ltoken_##name(tvalue); \ + LuaConstantReg reg_##name(#name, docs, LuaToken(tvalue), 0); + +#define LuaNumberConstant(name, nvalue, docs) \ + lua_Number lnumber_##name(nvalue); \ + LuaConstantReg reg_##name(#name, docs, LuaToken(), nvalue); + +#define LuaDefine(name, args, docs) \ + int lfn_##name(lua_State *L); \ + const char *lfnarg_##name = args; \ + const char *lfndoc_##name = docs; \ + LuaFunctionReg reg_##name(#name, lfnarg_##name, lfndoc_##name, false, lfn_##name); \ + int lfn_##name(lua_State *L) + +#define LuaDefineAlias(name1, name2) \ + LuaFunctionReg reg_##name1(#name1, lfnarg_##name2, lfndoc_##name2, false, lfn_##name2); \ + +#define LuaDefineBuiltin(name, args, docs) \ + LuaFunctionReg reg_##name(#name, args, docs, false, nullptr); + +#define LuaSandboxBuiltin(name, args, docs) \ + LuaFunctionReg reg_##name(#name, args, docs, true, nullptr); + +#define LuaStringify(x) #x +#define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); } +#define LuaAssertStrEq(L, x, y) { eng::string _s1_(x); eng::string _s2_(y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); } + +#endif // LUASTACK_HPP diff --git a/luprex/cpp/core/planemap.cpp b/luprex/cpp/core/planemap.cpp new file mode 100644 index 00000000..fb6ebb46 --- /dev/null +++ b/luprex/cpp/core/planemap.cpp @@ -0,0 +1,1354 @@ + + +// PlaneTree is an octree-like data structure. +// +// THE COORDINATE SPACE +// +// The tree stores objects which have (x,y,z) coordinates where each of x,y,z is +// an unsigned 16-bit integer. The API uses floating-point coordinates, but +// these are converted to uint16_t for use in the tree. +// +// In an octree, each node has a 2x2x2 array of children (total: 8 children). In +// this tree, however, each node has a 4x4x4 array of children (total: 64 +// children). +// +// The depth of the tree is fixed. It always has one leaf level (level 0), plus +// 8 internal levels (levels 1-8). The root of the tree is level 8. Tangibles +// are always are always stored in the leaf-level. Since there are 8 +// subdivisions, at 4x4x4 per subdivision, that divides the space into 65536 x +// 65536 x 65536. Hence the 16-bit integer coordinates. +// +// Here are the sizes of the levels: +// +// * Level 8 (internal) is 1 cubed. +// * Level 7 (internal) is 4 cubed. +// * Level 6 (internal) is 16 cubed. +// * Level 5 (internal) is 64 cubed. +// * Level 4 (internal) is 256 cubed. +// * Level 3 (internal) is 1024 cubed. +// * Level 2 (internal) is 4096 cubed. +// * Level 1 (internal) is 16385 cubed. +// * Level 0 (leaf) is 65536 cubed. +// +// NODE IDS AND THE NODE TABLES +// +// Every tree node has a unique "NodeID", which consists of it's level and the +// lowest (x,y,z) coordinate in its region. Every tree node is stored in a hash +// table that maps NodeID to the data for that tree node. +// +// Tree nodes don't contain any child pointers or parent pointers. Instead, you +// can calculate the NodeIDs of the parent and children from the NodeID of a +// node. So, you can just look up parents and children in the hash table. Even +// though PlaneTree really is a tree, it doesn't use pointers at all. +// +// There are two separate hash tables - one for storing internal nodes, and one +// for storing leaf nodes. The only data stored in an internal node is a +// bitvector indicating which of its 64 children are nonempty. The only data +// stored in a leaf node is a list of PlaneItems. The list of PlaneItems is +// stored as an intrusive doubly-linked circular list. +// +// THE PLANETREE BOUNDING BOX, OUTLIERS, AND ADAPTIVE RESIZING +// +// The planetree as a whole has a bounding box. For now, the bounding box is +// fixed at (-65536, -65536, -65536) to (65536, 65536, 65536). That makes the +// size of the leaf cells two meters. However, it is my plan to eventually have +// the bounding box selected adaptively. +// +// "Outliers" are objects that are outside the PlaneTree's bounding box. When an +// object is an outlier, its (X,Y,Z) coordinates are clamped to the tree's +// bounding box for purposes of storing it in the tree. This puts all the +// outliers into the cells on the edge of the tree. When we need to scan a +// region that includes areas outside the tree's bounding box, we scan the edge +// cells. +// +// The tree keeps a count of the number of outliers. The intended use for these +// outlier counts is to implement adaptive logic that says "are there too many +// outliers? If so, expand the tree's bounding box." But we haven't written the +// adaptive algorithm yet, so the outlier counts don't serve any purpose yet. +// +// We probably *don't* want to capture 100% of the outliers. It seems likely +// that the programmer will deliberately put a few objects way off in the +// distance. For example, you might imagine somebody creating a "LoginManager" +// object and putting it way off at (1000000, 0, 0) in order to get it out of +// the way. If we were to expand the bounding box to include such objects, we +// would make the tree's bounding box unreasonably large. I have an idea for a +// heuristic: expand the bounding box until it covers 90% of the tangibles, then +// expand it by 25% more, then stop. I have another idea for a different +// heuristic: keep expanding the bounding box as long as making a small +// increment will capture significantly more tangibles, then stop. I don't know +// if either of those heuristics will work well. +// +// Any adaptive algorithm we create must take another factor into account: we +// don't want the cell size to be too small. Overly small cell sizes create lots +// of extra work when an object moves around. So sometimes, it's advantageous +// to deliberately expand the bounding box way beyond what is actually needed to +// capture the outliers, just to make the cell size large enough. Doing this +// isn't wasteful: octrees are very good at handling big empty spaces inside the +// bounding box. As long as the cell size is large enough, but not too large, +// and there aren't too many outliers, everything will work well. Unfortunately, +// I haven't devised a good heuristic to decide when the cell size is "right." +// +// Another thought I have, periodically, is that writing a lot of logic just to +// choose a cell size seems like a lot of unnecessary work: we could just +// configure the cell size for the game in the script. +// +// BYPASSING THE TREE +// +// In many cases, it's possible to jump directly to a leaf node and start +// processing. For example, let's say you want to move a tangible. You know +// its current position, therefore, you know exactly the NodeID of the leaf node +// where it is stored. There is no need to walk the tree to get there: you can +// just access it directly using the hash table. +// + +#include "luastack.hpp" +#include "util.hpp" +#include "planemap.hpp" + +#include +#include + +using NodeID = uint64_t; +using ChildBits = uint64_t; +using IdVector = util::IdVector; + +// These need to be static floats to encourage gcc to generate +// efficient code in NodeInfo. +static float k_lo = 0.0f; +static float k_hi = 65535.0f; + +static constexpr ChildBits child_bit(int i) { + return (uint64_t(1) << i); +} + +static constexpr ChildBits child_bit(int x, int y, int z) { + return child_bit((x << 4) | (y << 2) | z); +} + +static constexpr uint8_t node_get_level(NodeID node) { + return node >> 48; +} + +static constexpr uint16_t node_get_x(NodeID node) { + return uint16_t(node >> 32); +} + +static constexpr uint16_t node_get_y(NodeID node) { + return uint16_t(node >> 16); +} + +static constexpr uint16_t node_get_z(NodeID node) { + return uint16_t(node >> 0); +} + +static constexpr NodeID node_lxyz(uint8_t level, uint16_t x, uint16_t y, uint16_t z) { + return (NodeID(level) << 48) | (NodeID(x) << 32) | (NodeID(y) << 16) | (NodeID(z) << 0); +} + +static constexpr NodeID node_parent(NodeID node) { + uint8_t level = node_get_level(node); + return ((node & node_lxyz(0, 0xFFFC, 0xFFFC, 0xFFFC)) >> 2) | node_lxyz(level + 1, 0, 0, 0); +} + +static constexpr uint8_t node_childindex(NodeID node) { + uint64_t masked = node & node_lxyz(0, 0x0003, 0x0003, 0x0003); + return (masked >> 0) | (masked >> 14) | (masked >> 28); +} + +static constexpr ChildBits node_childbit(NodeID node) { + return child_bit(node_childindex(node)); +} + +static constexpr NodeID node_child(NodeID node, uint16_t x, uint16_t y, uint16_t z) { + int level = node_get_level(node); + return ((node & node_lxyz(0, 0xFFFF, 0xFFFF, 0xFFFF)) << 2) | node_lxyz(level - 1, x, y, z); +} + +static constexpr NodeID node_nthchild(NodeID node, int i) { + return node_child(node, (i >> 4) & 3, (i >> 2) & 3, (i >> 0) & 3); +} + +static void print_node_id(NodeID node, std::ostream *os) { + int level = node_get_level(node); + + if (level >= 8) { + (*os) << "L" << level << ":root"; + return; + } + + uint16_t x = node_get_x(node); + uint16_t y = node_get_y(node); + uint16_t z = node_get_z(node); + auto fmt = util::hex.width(4 - (level >> 1)).fill('0'); + (*os) << "L" << level << ":" << fmt.val(x) << "," << fmt.val(y) << "," << fmt.val(z); +} + +enum BBoxCheck { INSIDE_BBOX, JUST_OUTSIDE_BBOX, WAY_OUTSIDE_BBOX }; + +struct NodeInfo { + NodeID node; + BBoxCheck bbcheck; + NodeInfo(float scale, float x, float y, float z) { + float sx = (x * scale); + float sy = (y * scale); + float sz = (z * scale); + float dist = std::max({std::abs(sx), std::abs(sy), std::abs(sz)}); + if (dist >= 32768.0f) { + bbcheck = (dist > (32768.0f + 8192.0f)) ? WAY_OUTSIDE_BBOX : JUST_OUTSIDE_BBOX; + float clampx = std::min(k_hi, std::max(k_lo, sx + 32768.0f)); + float clampy = std::min(k_hi, std::max(k_lo, sy + 32768.0f)); + float clampz = std::min(k_hi, std::max(k_lo, sz + 32768.0f)); + node = node_lxyz(0, clampx, clampy, clampz); + } else { + node = node_lxyz(0, sx + 32768.0f, sy + 32768.0f, sz + 32768.0f); + bbcheck = INSIDE_BBOX; + } + } +}; + +template +struct TreeLevel { + constexpr static int child() { return LEVEL - 1; } +}; + +template <> +struct TreeLevel<0> { + constexpr static int child() { return 0; } +}; + +// Class PlaneTree. Everything here is 'public', but this class +// is only visible inside this one C++ file. +class PlaneTree : public eng::opnew { +public: + void set_radius(float r); + + using NodeID = uint64_t; + using ChildBits = uint64_t; + + // The PlaneMap that this tree is a part of. + PlaneMap *planemap_; + + // The name of this plane. + std::string plane_; + + // Internal nodes in the tree just have bits indicating + // which children exist. + eng::bytell_hash_map internal_nodes_; + + // Leaf nodes in the tree contain a doubly-linked + // intrusive ring. + eng::bytell_hash_map leaf_nodes_; + + // The radius of the bounding box. + double radius_; + + // A conversion factor to convert float coordinates to + // integral coordinates. Equal to 32k / radius. + float scale_; + + // total number of items in the planetree. + int total_count_; + + // total number of outliers in the planetree. Outliers are + // classified as just outside or way outside the bbox. + int just_outside_bbox_; + int way_outside_bbox_; + +public: + // The following state is initialized whenever we do a scan. + // It is only relevant during the scan. + const PlaneScan *scan_config_; + IdVector *scan_result_; + util::XYZ scan_lo_; + util::XYZ scan_hi_; + util::XYZ scan_invradius_; + int scan_bbxlo_[9], scan_bbxhi_[9]; + int scan_bbylo_[9], scan_bbyhi_[9]; + int scan_bbzlo_[9], scan_bbzhi_[9]; + +public: + ChildBits get_internal_node(NodeID id) const { + auto iter = internal_nodes_.find(id); + if (iter == internal_nodes_.end()) return 0; + return iter->second; + } + + PlaneItem *get_leaf_node(NodeID id) const { + auto iter = leaf_nodes_.find(id); + if (iter == leaf_nodes_.end()) return 0; + return iter->second; + } + + // Untrack all planeitems. This is for unit testing and for destructors. We + // don't use PlaneItem::untrack, because that would create problems with + // removing items from a list while iterating over that list. + void untrack_all() { + for (auto &l : leaf_nodes_) { + PlaneItem *first = l.second; + PlaneItem *pi = first; + assert(pi != nullptr); + while (true) { + PlaneItem *next = pi->next_; + pi->tree_= nullptr; + pi = next; + if (pi == first) break; + } + } + leaf_nodes_.clear(); + internal_nodes_.clear(); + } + + // This just sets the radius. + // It verifies that the tree is empty first. + void set_radius_of_empty_tree(float r) { + assert(total_count_ == 0); + radius_ = r; + scale_ = 32768.0 / r; + } + + // Get a PlaneTree by plane name. + static PlaneTree *get(PlaneMap *pmap, const eng::string &plane) { + std::unique_ptr &result = pmap->planes_[plane]; + if (result == nullptr) { + result.reset(new PlaneTree(pmap, plane)); + } + return result.get(); + } + + // Remove an item from the specified leaf node. + // Returns true if we removed the last item from the leaf. + bool remove_planeitem_from_leaf(NodeID node, PlaneItem *item) { + if (item->next_ == item) { + leaf_nodes_.erase(node); + // Note: these next two assignments are only needed for sanity checking. + item->next_ = nullptr; + item->prev_ = nullptr; + return true; + } else { + item->prev_->next_ = item->next_; + item->next_->prev_ = item->prev_; + // Note: these next two assignments are only needed for sanity checking. + item->next_ = nullptr; + item->prev_ = nullptr; + return false; + } + } + + // Insert an item into the specified leaf node. + // Returns true if we inserted the first item into the leaf. + bool insert_planeitem_into_leaf(NodeID node, PlaneItem *item) { + PlaneItem *&newcell = leaf_nodes_[node]; + if (newcell == nullptr) { + newcell = item; + item->next_ = item; + item->prev_ = item; + return true; + } else { + PlaneItem *next = newcell; + PlaneItem *prev = newcell->prev_; + item->next_ = next; + item->prev_ = prev; + prev->next_ = item; + next->prev_ = item; + return false; + } + } + + // Update the parent to reflect the fact that the child was added. + // This will propagage all the way up the tree. + void insert_child_into_childbits(NodeID child) { + uint8_t childlevel = node_get_level(child); + uint8_t parentlevel = childlevel + 1; + NodeID parent = node_parent(child); + ChildBits &inode = internal_nodes_[parent]; + bool waszero = (inode == 0); + inode |= node_childbit(child); + if (waszero) { + if (parentlevel < 8) insert_child_into_childbits(parent); + } + } + + // Update the parent to reflect the fact that the child was removed. + // This will propagage all the way up the tree. + void remove_child_from_childbits(NodeID child) { + uint8_t childlevel = node_get_level(child); + uint8_t parentlevel = childlevel + 1; + NodeID parent = node_parent(child); + auto iter = internal_nodes_.find(parent); + assert(iter != internal_nodes_.end()); + iter->second &= (~node_childbit(child)); + if (iter->second == 0) { + internal_nodes_.erase(iter); + if (parentlevel < 8) remove_child_from_childbits(parent); + } + } + + // Update the counters to reflect the removal of one item from the tree. + void decrement_planeitem_counters(BBoxCheck bbcheck) { + total_count_ -= 1; + if (bbcheck == JUST_OUTSIDE_BBOX) just_outside_bbox_ -= 1; + if (bbcheck == WAY_OUTSIDE_BBOX) way_outside_bbox_ -= 1; + } + + // Update the counters to reflect the insertion of one item into the tree. + void increment_planeitem_counters(BBoxCheck bbcheck) { + total_count_ += 1; + if (bbcheck == JUST_OUTSIDE_BBOX) just_outside_bbox_ += 1; + if (bbcheck == WAY_OUTSIDE_BBOX) way_outside_bbox_ += 1; + } + + // Remove a planeitem from whatever tree it is in, preserving + // all invariants. The planeitem ends up being an untracked PlaneItem. + static void remove_planeitem(PlaneItem *item) { + PlaneTree *tree = item->tree_; + assert(tree != nullptr); + NodeInfo info(tree->scale_, item->x_, item->y_, item->z_); + tree->decrement_planeitem_counters(info.bbcheck); + if (tree->remove_planeitem_from_leaf(info.node, item)) { + tree->remove_child_from_childbits(info.node); + } + item->tree_ = nullptr; + } + + // Insert a planeitem into whatever tree is specified, preserving + // all invariants. The planeitem must be an untracked PlaneItem. + void insert_planeitem(PlaneItem *item) { + PlaneTree *tree = this; + assert(item->tree_ == nullptr); + NodeInfo info(tree->scale_, item->x_, item->y_, item->z_); + tree->increment_planeitem_counters(info.bbcheck); + if (tree->insert_planeitem_into_leaf(info.node, item)) { + tree->insert_child_into_childbits(info.node); + } + item->tree_ = tree; + item->plane_ = tree->plane_; + } + + void print_indented_internal_node(NodeID node, ChildBits cb, std::ostream *os) { + int level = node_get_level(node); + int indent = 8 - level; + (*os) << "|"; + for (int i = 0; i < indent; i++) (*os) << " "; + print_node_id(node, os); + if ((cb == 0) && (level != 8)) { + (*os) << " (invalid empty node)"; + } + } + + void print_indented_leaf_node(NodeID node, PlaneItem *first, std::ostream *os) { + (*os) << "| "; + print_node_id(node, os); + (*os) << " "; + IdVector ids; + collect_planeitem_ids(first, &ids); + std::sort(ids.begin(), ids.end()); + util::print_id_vector(ids, os); + } + + void print_tree_r(NodeID node, std::ostream *os) { + int level = node_get_level(node); + if (level == 0) { + print_indented_leaf_node(node, get_leaf_node(node), os); + } else { + ChildBits cb = get_internal_node(node); + if ((level & 1) == 0) { + print_indented_internal_node(node, cb, os); + } + for (int i = 0; i < 64; i++) { + if (cb & child_bit(i)) { + NodeID child = node_nthchild(node, i); + assert(node_childindex(child) == i); + print_tree_r(child, os); + } + } + } + } + + // The final filtering step sometimes uses the inverse of the + // radius. In the case that the radius is 0, we want to use a huge + // number for the inverse radius, but not infinity, because using infinity + // would result in the final filtering step calculating (inf*0). + // In the case that the radius is infinite, we want to use zero for the + // inverse radius. + static float inverse_radius(float f) { + if (f == 0) return std::numeric_limits::max(); + if (std::isinf(f)) return 0; + return 1.0f / f; + } + + // Given a PlaneScan, calculate the search bboxes, + // and all the other related configuration data. + void calculate_search_bboxes(const PlaneScan &sc) { + scan_config_ = ≻ + scan_lo_ = sc.center_ - sc.radius_; + scan_hi_ = sc.center_ + sc.radius_; + + scan_invradius_.x = inverse_radius(sc.radius_.x); + scan_invradius_.y = inverse_radius(sc.radius_.y); + scan_invradius_.z = inverse_radius(sc.radius_.z); + + // Convert the scan's bounding box to integral coordinates. + NodeInfo bblo(scale_, scan_lo_.x, scan_lo_.y, scan_lo_.z); + NodeInfo bbhi(scale_, scan_hi_.x, scan_hi_.y, scan_hi_.z); + + // Calculate the bounding box at each level of the tree. + NodeID ibblo = bblo.node; + NodeID ibbhi = bbhi.node; + for (int i = 0; i <= 8; i++) { + scan_bbxlo_[i] = node_get_x(ibblo); + scan_bbylo_[i] = node_get_y(ibblo); + scan_bbzlo_[i] = node_get_z(ibblo); + scan_bbxhi_[i] = node_get_x(ibbhi); + scan_bbyhi_[i] = node_get_y(ibbhi); + scan_bbzhi_[i] = node_get_z(ibbhi); + ibblo = node_parent(ibblo); + ibbhi = node_parent(ibbhi); + } + } + + // Calculate the size of the search bboxes. + int64_t scan_xsize(int i) const { return 1 + scan_bbxhi_[i] - scan_bbxlo_[i]; } + int64_t scan_ysize(int i) const { return 1 + scan_bbyhi_[i] - scan_bbylo_[i]; } + int64_t scan_zsize(int i) const { return 1 + scan_bbzhi_[i] - scan_bbzlo_[i]; } + + eng::string search_bboxes_debug_string(const PlaneScan &scan) { + calculate_search_bboxes(scan); + eng::ostringstream oss; + for (int i = 8; i >= 0; i -= 2) { + auto fmt = util::hex.width((8 - i) / 2); + oss << "|Level " << i << " "; + oss << fmt.val(scan_bbxlo_[i]) << "," + << fmt.val(scan_bbylo_[i]) << "," + << fmt.val(scan_bbzlo_[i]) << " - " + << fmt.val(scan_bbxhi_[i]) << "," + << fmt.val(scan_bbyhi_[i]) << "," + << fmt.val(scan_bbzhi_[i]); + } + return oss.str(); + } + + static inline void scan_push_id(int64_t id, int64_t near, IdVector *result) { + if (id != near) { + result->push_back(id); + } + } + + void scan_planeitem(PlaneItem *pi) { + switch (scan_config_->shape_) { + case PlaneScan::BOX: { + if ((pi->x() >= scan_lo_.x) && (pi->x() <= scan_hi_.x) && + (pi->y() >= scan_lo_.y) && (pi->y() <= scan_hi_.y) && + (pi->z() >= scan_lo_.z) && (pi->z() <= scan_hi_.z)) { + scan_push_id(pi->id(), scan_config_->near_, scan_result_); + } + break; + } + case PlaneScan::SPHERE: { + float dx = (pi->x() - scan_config_->center_.x) * scan_invradius_.x; + float dy = (pi->y() - scan_config_->center_.y) * scan_invradius_.y; + float dz = (pi->z() - scan_config_->center_.z) * scan_invradius_.z; + if (dx*dx + dy*dy + dz*dz <= 1.0) { + scan_push_id(pi->id(), scan_config_->near_, scan_result_); + } + break; + } + case PlaneScan::CYLINDER: { + if ((pi->z() >= scan_lo_.z) && (pi->z() <= scan_hi_.z)) { + float dx = (pi->x() - scan_config_->center_.x) * scan_invradius_.x; + float dy = (pi->y() - scan_config_->center_.y) * scan_invradius_.y; + if (dx*dx + dy*dy <= 1.0) { + scan_push_id(pi->id(), scan_config_->near_, scan_result_); + } + } + break; + } + } + } + + // Recursive part of the planetree scan. + // Note: template expansion terminates because + // TreeLevel<0>::child returns zero again. + template + void scan_node(NodeID node, std::ostream *debug) { + if (LEVEL == 0) { + auto iter = leaf_nodes_.find(node); + assert (iter != leaf_nodes_.end()); + PlaneItem *first = iter->second; + assert(first != nullptr); + if (debug != nullptr) { + print_indented_leaf_node(node, first, debug); + } + PlaneItem *pi = first; + while (true) { + PlaneItem *next = pi->next_; + scan_planeitem(pi); + if (next == first) break; + pi = next; + } + } else { + constexpr int CHILDLEVEL = TreeLevel::child(); + NodeID firstchild = node_nthchild(node, 0); + + int xlo = std::max(0, scan_bbxlo_[CHILDLEVEL] - int(node_get_x(firstchild))); + int ylo = std::max(0, scan_bbylo_[CHILDLEVEL] - int(node_get_y(firstchild))); + int zlo = std::max(0, scan_bbzlo_[CHILDLEVEL] - int(node_get_z(firstchild))); + int xhi = std::min(3, scan_bbxhi_[CHILDLEVEL] - int(node_get_x(firstchild))); + int yhi = std::min(3, scan_bbyhi_[CHILDLEVEL] - int(node_get_y(firstchild))); + int zhi = std::min(3, scan_bbzhi_[CHILDLEVEL] - int(node_get_z(firstchild))); + + auto iter = internal_nodes_.find(node); + assert (iter != internal_nodes_.end()); + ChildBits cb = iter->second; + assert (cb != 0); + if (debug != nullptr) { + print_indented_internal_node(node, cb, debug); + } + + for (int x = xlo; x <= xhi; x++) { + for (int y = ylo; y <= yhi; y++) { + for (int z = zlo; z <= zhi; z++) { + if (cb & child_bit(x, y, z)) { + NodeID child = node_child(node, x, y, z); + scan_node(child, debug); + } + } + } + } + } + } + + // Scan a planetree. + void scan(const PlaneScan &sc, IdVector *result, std::ostream *debug) { + calculate_search_bboxes(sc); + scan_result_ = result; + + // We must only call 'scan_node' on nodes that actually exist. + // So we check if the tree is empty, and if so, we don't scan the + // root node. + if (!internal_nodes_.empty()) { + NodeID root = node_lxyz(8, 0, 0, 0); + scan_node<8>(root, debug); + } + } + + eng::string scan_steps_debug_string(const PlaneScan &sc) { + eng::ostringstream oss; + IdVector result; + scan(sc, &result, &oss); + oss << "|Result: "; + std::sort(result.begin(), result.end()); + util::print_id_vector(result, &oss); + return oss.str(); + } + + void collect_planeitem_ids(PlaneItem *first, IdVector *ids) { + if (first != nullptr) { + PlaneItem *pi = first; + while (true) { + PlaneItem *next = pi->next_; + ids->push_back(pi->id()); + if (next == first) break; + pi = next; + } + } + } + + eng::string tree_debug_string() { + eng::ostringstream oss; + print_tree_r(node_lxyz(8,0,0,0), &oss); + return oss.str(); + } + + eng::string outliers_debug_string() { + eng::ostringstream oss; + oss << "total:" << total_count_ << " justout:" << just_outside_bbox_ << " wayout:" << way_outside_bbox_; + return oss.str(); + } + + // Construct a PlaneTree. + PlaneTree(PlaneMap *pmap, std::string_view plane) { + planemap_ = pmap; + plane_ = plane; + total_count_ = 0; + just_outside_bbox_ = 0; + way_outside_bbox_ = 0; + set_radius_of_empty_tree(pmap->default_radius_); + } + + // Destructor: the PlaneTree doesn't own the PlaneItems, so it doesn't + // delete them, but it needs to untrack all the PlaneItems. + ~PlaneTree() { + untrack_all(); + } +}; + +void PlaneItem::track(PlaneMap *pmap) { + // If we're already in a PlaneMap, and it's not the + // PlaneMap we want to be in, remove from the old PlaneMap. + if ((tree_ != nullptr) && (tree_->planemap_ != pmap)) { + PlaneTree::remove_planeitem(this); + } + + // If we're supposed to be in a PlaneMap, and we're not + // already in the PlaneMap, insert it. + if ((tree_ == nullptr) && (pmap != nullptr)) { + PlaneTree::get(pmap, plane_)->insert_planeitem(this); + } +} + +void PlaneItem::set_pos(const eng::string &plane, float x, float y, float z) { + // If we're not in a PlaneMap, nothing to do but set the variables. + if (tree_ == nullptr) { + plane_ = plane; + x_ = x; + y_ = y; + z_ = z; + return; + } + + // When moving within a plane (not warping), use set_xyz, which is faster. + if (plane_ == plane) { + set_xyz(x, y, z); + return; + } + + // We're warping from one plane to another. That means we're removing + // ourself from one planetree and inserting ourself into a different one. + PlaneTree *newtree = PlaneTree::get(tree_->planemap_, plane); + PlaneTree::remove_planeitem(this); + x_ = x; + y_ = y; + z_ = z; + newtree->insert_planeitem(this); +} + +void PlaneItem::set_xyz(float x, float y, float z) { + // If we're not in a PlaneMap, nothing to do but set the variables. + if (tree_ == nullptr) { + x_ = x; + y_ = y; + z_ = z; + return; + } + + // We could implement this function using 'PlaneTree::remove_planeitem' + // and 'PlaneTree::insert_planeitem', which would be less code, but it + // would also be slower. + NodeInfo old_cell(tree_->scale_, x_, y_, z_); + NodeInfo new_cell(tree_->scale_, x, y, z); + + // Update the variables. + x_ = x; + y_ = y; + z_ = z; + + // Update the outliers counters (unlikely). + if (old_cell.bbcheck != new_cell.bbcheck) { + tree_->decrement_planeitem_counters(old_cell.bbcheck); + tree_->increment_planeitem_counters(new_cell.bbcheck); + } + + // If we have changed cells, update the tree. + // We have to remove the child from the old leaf before inserting + // it into the new leaf, because the 'next' and 'prev' pointers are + // intrusive and we need them to be unused to do the insert. + // However, inserting the child into the childbits first is faster + // and poses no problems. + if (new_cell.node != old_cell.node) { + bool leaf_removed = tree_->remove_planeitem_from_leaf(old_cell.node, this); + bool leaf_created = tree_->insert_planeitem_into_leaf(new_cell.node, this); + if (leaf_created) tree_->insert_child_into_childbits(new_cell.node); + if (leaf_removed) tree_->remove_child_from_childbits(old_cell.node); + } +} + +PlaneItem::PlaneItem() { + id_ = 0; + tree_ = nullptr; + next_ = nullptr; + prev_ = nullptr; + x_ = y_ = z_ = 0.0; +} + +PlaneItem::~PlaneItem() { + untrack(); +} + +PlaneMap::PlaneMap() : default_radius_(32768.0) {} +PlaneMap::~PlaneMap() {} + + +void PlaneMap::scan(const PlaneScan &sc, util::IdVector *into) const { + into->clear(); + if (sc.near_ != 0) { + if (sc.include_near_) { + into->push_back(sc.near_); + } + } + + if (sc.omit_nowhere_ && (sc.plane_ == "nowhere")) { + return; + } + + auto piter = planes_.find(std::string_view(sc.plane_)); + if (piter != planes_.end()) { + const std::unique_ptr &tree = piter->second; + tree->scan(sc, into, nullptr); + } + + if (sc.sorted_) { + std::sort(into->begin(), into->end()); + } +} + +eng::string PlaneMap::tree_debug_string(const eng::string &plane) { + return PlaneTree::get(this, plane)->tree_debug_string(); +} + +eng::string PlaneMap::outliers_debug_string(const eng::string &plane) { + return PlaneTree::get(this, plane)->outliers_debug_string(); +} + +eng::string PlaneMap::search_bboxes_debug_string(const PlaneScan &scan) { + return PlaneTree::get(this, eng::string(scan.plane_))->search_bboxes_debug_string(scan); +} + +eng::string PlaneMap::scan_steps_debug_string(const PlaneScan &scan) { + return PlaneTree::get(this, eng::string(scan.plane_))->scan_steps_debug_string(scan); +} + +void PlaneMap::untrack_all() { + for (const auto &pair : planes_) { + pair.second->untrack_all(); + } +} + +void PlaneScan::configure(LuaKeywordParser &kp) { + lua_State *L = kp.state(); + LuaVar val, vx, vy, vz; + LuaExtStack LS(L, val, vx, vy, vz); + + bool have_plane = false; + bool have_center = false; + bool have_radius = false; + bool have_shape = false; + bool have_near = false; + + if (kp.parse(val, "plane")) { + plane_ = LS.ckstring(val, "plane"); + have_plane = true; + } + + if (kp.parse(val, "center")) { + util::DXYZ xyz = LS.ckxyz(val, "center"); + center_ = xyz; + have_center = true; + } + + if (kp.parse(val, "radius")) { + auto simple = LS.trynumber(val); + if (simple) { + radius_ = *simple; + have_radius = true; + } else { + auto full = LS.tryxyz(val); + if (full) { + radius_ = *full; + have_radius = true; + } + } + if (!have_radius) { + luaL_error(L, "scan configuration: 'radius' must be a vector or number"); + } + } + + if (kp.parse(val, "shape")) { + eng::string shape = LS.ckstring(val, "shape"); + if (shape == "box") { + shape_ = BOX; + } else if (shape == "sphere") { + shape_ = SPHERE; + } else if (shape == "cylinder") { + shape_ = CYLINDER; + } else { + luaL_error(L, "scan configuration: unknown shape %s", shape.c_str()); + } + have_shape = true; + } + + if (kp.parse(val, "near")) { + int64_t id = LS.tanid(val); + if (id == 0) { + luaL_error(L, "scan configuration: 'near' must be a tangible"); + } + if (have_center) { + luaL_error(L, "scan configuration: specified both 'center' and 'near'"); + } + if (have_plane) { + luaL_error(L, "scan configuration: specified both 'plane' and 'near'"); + } + near_ = id; + have_center = true; + have_plane = true; + have_near = true; + } + + if (kp.parse(val, "include")) { + if (!have_near) { + luaL_error(L, "scan configuration: 'include' specified without 'near'"); + } + include_near_ = LS.ckboolean(val, "include"); + } + + if (kp.parse(val, "wholeplane")) { + if (have_plane || have_center || have_radius || have_shape) { + luaL_error(L, "scan configuration: do not specify plane, center, shape, or radius with 'wholeplane'"); + } + plane_ = LS.ckstring(val, "wholeplane"); + set_whole_plane(); + have_plane = true; + have_center = true; + have_radius = true; + have_shape = true; + } + + if (!have_plane) { + luaL_error(L, "scan configuration: did not specify plane"); + } + if (!have_radius) { + luaL_error(L, "scan configuration: did not specify radius"); + } + if (!have_center) { + luaL_error(L, "scan configuration: did not specify center"); + } + if (!have_shape) { + shape_ = SPHERE; // default value. + } +} + +eng::string PlaneScan::debug_string() const { + eng::ostringstream oss; + oss << "plane:" << plane_ << " center:" << center_ << " radius:" << radius_; + if (shape_ == BOX) oss << " shape:box"; + else if (shape_ == SPHERE) oss << " shape:sphere"; + else if (shape_ == CYLINDER) oss << " shape:cylinder"; + else oss << " shape:unknown"; + if (near_ != 0) { + oss << " near:" << near_ << " include:" << include_near_; + } + if (omit_nowhere_) { + oss << " omit_nowhere:true"; + } + if (!sorted_) { + oss << " sorted:false"; + } + return oss.str(); +} + +// The default radius is set such that float coordinates map directly to +// integer coordinates, with an offset of 0x8000. This makes unit testing +// a lot easier. +// +LuaDefine(unittests_planemap, "", "some unit tests") { + PlaneMap pm; + PlaneItem pi123, pi456; + PlaneScan scan; + pi123.set_id(123); + pi456.set_id(456); + + // Test that PlaneItems can be manipulated when they're not + // yet tracking a PlaneMap. + pi123.set_pos("p", 0x38, 0x16, 0x87); + LuaAssert(L, pi123.plane() == "p"); + LuaAssert(L, pi123.x() == 0x38); + LuaAssert(L, pi123.y() == 0x16); + LuaAssert(L, pi123.z() == 0x87); + + // TESTS OF TREE MANIPULATION FOLLOW. + + // Test track. + pi123.set_pos("p", 0x38, 0x16, 0x87); + pi123.track(&pm); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,8,8" + "| L4:80,80,80" + "| L2:803,801,808" + "| L0:8038,8016,8087 123"); + + // Test untrack. + pi123.untrack(); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root"); + + // Track two items at a time, not in the same cell. + pi456.set_pos("p", 0x12, 0x17, 0xAC); + pi123.track(&pm); + pi456.track(&pm); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,8,8" + "| L4:80,80,80" + "| L2:801,801,80a" + "| L0:8012,8017,80ac 456" + "| L2:803,801,808" + "| L0:8038,8016,8087 123"); + + // Move one of the items into the same cell as the other. + pi456.set_xyz(0x38, 0x16, 0x87); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,8,8" + "| L4:80,80,80" + "| L2:803,801,808" + "| L0:8038,8016,8087 123,456"); + + // Move item 456 back out of the cell. + pi456.set_xyz(0x27, 0x11, 0x31); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,8,8" + "| L4:80,80,80" + "| L2:802,801,803" + "| L0:8027,8011,8031 456" + "| L2:803,801,808" + "| L0:8038,8016,8087 123"); + + // Move item 123 to follow 456. + pi123.set_xyz(0x27, 0x11, 0x31); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,8,8" + "| L4:80,80,80" + "| L2:802,801,803" + "| L0:8027,8011,8031 123,456"); + + // TESTS OF OUTLIER CLAMPING FOLLOW. + + // Move item 456 close to, but not quite on the positive edge. + pi123.untrack(); + pi456.set_xyz(0x23, 0x7FFE, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:0 wayout:0"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,f,8" + "| L4:80,ff,80" + "| L2:802,fff,802" + "| L0:8023,fffe,8027 456"); + + // Move item 456 so that it's on the positive edge, but not an outlier. + pi456.set_xyz(0x23, 0x7FFF, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:0 wayout:0"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,f,8" + "| L4:80,ff,80" + "| L2:802,fff,802" + "| L0:8023,ffff,8027 456"); + + // Move item 456 so that it's even closer to the positive edge, but not an outlier. + pi456.set_xyz(0x23, 0x7FFF + 0.99, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:0 wayout:0"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,f,8" + "| L4:80,ff,80" + "| L2:802,fff,802" + "| L0:8023,ffff,8027 456"); + + // Move item 456 so that it's just barely a positive outlier. + pi456.set_xyz(0x23, 0x8000, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:1 wayout:0"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,f,8" + "| L4:80,ff,80" + "| L2:802,fff,802" + "| L0:8023,ffff,8027 456"); + + // Move item 456 so that it's considerably past the positive edge. + pi456.set_xyz(0x23, 0x8048, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:1 wayout:0"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,f,8" + "| L4:80,ff,80" + "| L2:802,fff,802" + "| L0:8023,ffff,8027 456"); + + // Move item 456 so that it's way past the positive edge. + pi456.set_xyz(0x23, 0x83748, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:0 wayout:1"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,f,8" + "| L4:80,ff,80" + "| L2:802,fff,802" + "| L0:8023,ffff,8027 456"); + + // Move item 456 close to, but not quite on the negative edge. + pi456.set_xyz(0x23, -0x7fff, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:0 wayout:0"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,0,8" + "| L4:80,00,80" + "| L2:802,000,802" + "| L0:8023,0001,8027 456"); + + // Move item 456 so that it's on the negative edge, but not an outlier. + pi456.set_xyz(0x23, -0x7fff - 0.5, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:0 wayout:0"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,0,8" + "| L4:80,00,80" + "| L2:802,000,802" + "| L0:8023,0000,8027 456"); + + // Move item 456 so that it's just barely a negative outlier. + pi456.set_xyz(0x23, -0x8000, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:1 wayout:0"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,0,8" + "| L4:80,00,80" + "| L2:802,000,802" + "| L0:8023,0000,8027 456"); + + // Move item 456 so that it's significantly past the negative edge. + pi456.set_xyz(0x23, -0x8048, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:1 wayout:0"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,0,8" + "| L4:80,00,80" + "| L2:802,000,802" + "| L0:8023,0000,8027 456"); + + // Move item 456 so that it's way past the negative edge. + pi456.set_xyz(0x23, -0x83048, 0x27); + LuaAssertStrEq(L, pm.outliers_debug_string("p"), "total:1 justout:0 wayout:1"); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:8,0,8" + "| L4:80,00,80" + "| L2:802,000,802" + "| L0:8023,0000,8027 456"); + + // Test the calculation of search bboxes. + // The two corners are deliberately not in low-high order. + scan.clear(); + scan.set_plane("p"); + scan.set_center_and_radius(util::XYZ(0x23, 0x97, 0x103), 2.0f); + LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan), + "|Level 8 0,0,0 - 0,0,0" + "|Level 6 8,8,8 - 8,8,8" + "|Level 4 80,80,81 - 80,80,81" + "|Level 2 802,809,810 - 802,809,810" + "|Level 0 8021,8095,8101 - 8025,8099,8105"); + + // TESTS OF SCANNING + + // Store a single object in the map to scan. + pm.untrack_all(); + pi123.set_pos("p", 0x12, 0x34, 0x45); + pi123.track(&pm); + + // Set up a scan with a radius of zero, centered + // right on the one object. Check the bboxes to make + // sure they only include the one cell. + scan.clear(); + scan.set_plane("p"); + scan.set_center_and_radius(util::XYZ(0x12, 0x34, 0x45), 0.0); + LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan), + "|Level 8 0,0,0 - 0,0,0" + "|Level 6 8,8,8 - 8,8,8" + "|Level 4 80,80,80 - 80,80,80" + "|Level 2 801,803,804 - 801,803,804" + "|Level 0 8012,8034,8045 - 8012,8034,8045"); + + // Run the scan with radius zero. It should find the one object. + LuaAssertStrEq(L, pm.scan_steps_debug_string(scan), + "|L8:root" + "| L7:2,2,2" + "| L6:8,8,8" + "| L5:20,20,20" + "| L4:80,80,80" + "| L3:200,200,201" + "| L2:801,803,804" + "| L1:2004,200d,2011" + "| L0:8012,8034,8045 123" + "|Result: 123"); + + // Bump the scan over a half-unit. It should have the same + // bboxes as before, since a half-unit isn't enough to shift + // from one cell to the next. + scan.clear(); + scan.set_plane("p"); + scan.set_center_and_radius(util::XYZ(0x12 + 0.5, 0x34, 0x45), 0.0); + LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan), + "|Level 8 0,0,0 - 0,0,0" + "|Level 6 8,8,8 - 8,8,8" + "|Level 4 80,80,80 - 80,80,80" + "|Level 2 801,803,804 - 801,803,804" + "|Level 0 8012,8034,8045 - 8012,8034,8045"); + + // Run the scan with radius zero, but one half-step away + // from the object. It should encounter the cell containing the + // one object, but the object should get removed in the final + // filtering step. + LuaAssertStrEq(L, pm.scan_steps_debug_string(scan), + "|L8:root" + "| L7:2,2,2" + "| L6:8,8,8" + "| L5:20,20,20" + "| L4:80,80,80" + "| L3:200,200,201" + "| L2:801,803,804" + "| L1:2004,200d,2011" + "| L0:8012,8034,8045 123" + "|Result: "); + + // Next, expand the scan radius to huge. Examine the bboxes + // to make sure they cover the entire PlaneTree. + scan.clear(); + scan.set_plane("p"); + scan.set_center_and_radius(util::XYZ(0x12, 0x34, 0x45), 100000.0); + LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan), + "|Level 8 0,0,0 - 0,0,0" + "|Level 6 0,0,0 - f,f,f" + "|Level 4 00,00,00 - ff,ff,ff" + "|Level 2 000,000,000 - fff,fff,fff" + "|Level 0 0000,0000,0000 - ffff,ffff,ffff"); + + // Walk the tree using the expansive search bboxes. It should + // find the one object, and it should still only traverse the same + // cells, because those are the only cells that exist. + LuaAssertStrEq(L, pm.scan_steps_debug_string(scan), + "|L8:root" + "| L7:2,2,2" + "| L6:8,8,8" + "| L5:20,20,20" + "| L4:80,80,80" + "| L3:200,200,201" + "| L2:801,803,804" + "| L1:2004,200d,2011" + "| L0:8012,8034,8045 123" + "|Result: 123"); + + // Add another object to the tree. Then, scan again + // using the expansive search. It should find both objects. + pi456.set_pos("p", 0x14, 0x35, 0x30); + pi456.track(&pm); + LuaAssertStrEq(L, pm.scan_steps_debug_string(scan), + "|L8:root" + "| L7:2,2,2" + "| L6:8,8,8" + "| L5:20,20,20" + "| L4:80,80,80" + "| L3:200,200,200" + "| L2:801,803,803" + "| L1:2005,200d,200c" + "| L0:8014,8035,8030 456" + "| L3:200,200,201" + "| L2:801,803,804" + "| L1:2004,200d,2011" + "| L0:8012,8034,8045 123" + "|Result: 123,456"); + + // We're going to test that sphere of radius 0.0 works. This is an important + // test because the sphere calculation involves calculating the inverse of + // the radius, which has to be special-cased when radius is zero. We need + // special-case code for this. In this test case, we set up a scan with two + // objects in the same cell, just a smidge apart. Use a sphere scan with + // radius zero, centered right on object 123 (but missing object 456). + pm.untrack_all(); + pi123.set_pos("p", 0x12 + 0.1, 0x34, 0x45); + pi456.set_pos("p", 0x12 + 0.2, 0x34, 0x45); + pi123.track(&pm); + pi456.track(&pm); + scan.clear(); + scan.set_plane("p"); + scan.set_shape(PlaneScan::SPHERE); + scan.set_center_and_radius(util::XYZ(0x12 + 0.1, 0x34, 0x45), 0.0); + LuaAssertStrEq(L, pm.scan_steps_debug_string(scan), + "|L8:root" + "| L7:2,2,2" + "| L6:8,8,8" + "| L5:20,20,20" + "| L4:80,80,80" + "| L3:200,200,201" + "| L2:801,803,804" + "| L1:2004,200d,2011" + "| L0:8012,8034,8045 123,456" + "|Result: 123"); + + // We're going to test that 'whole plane' searches work. + // These use an infinite radius. + pm.untrack_all(); + pi123.set_pos("p", 0x12, 0x34, 0x45); + pi123.track(&pm); + scan.clear(); + scan.set_plane("p"); + scan.set_whole_plane(); + LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan), + "|Level 8 0,0,0 - 0,0,0" + "|Level 6 0,0,0 - f,f,f" + "|Level 4 00,00,00 - ff,ff,ff" + "|Level 2 000,000,000 - fff,fff,fff" + "|Level 0 0000,0000,0000 - ffff,ffff,ffff"); + LuaAssertStrEq(L, pm.scan_steps_debug_string(scan), + "|L8:root" + "| L7:2,2,2" + "| L6:8,8,8" + "| L5:20,20,20" + "| L4:80,80,80" + "| L3:200,200,201" + "| L2:801,803,804" + "| L1:2004,200d,2011" + "| L0:8012,8034,8045 123" + "|Result: 123"); + + // Set up a tree with a single object that's outside + // the tree's bounding box (an outlier). Print the tree + // to verify that the object ended up on the edge. + pm.untrack_all(); + pi123.set_pos("p", 0x100000, 0x16, 0x23); + pi123.track(&pm); + LuaAssertStrEq(L, pm.tree_debug_string("p"), + "|L8:root" + "| L6:f,8,8" + "| L4:ff,80,80" + "| L2:fff,801,802" + "| L0:ffff,8016,8023 123"); + + // Now set up a scan around the target outlier. Print out + // the scan bboxes. It should be scanning the edge cell that + // contains the outlier. It also contains a few other cells + // because the radius is nonzero. + scan.clear(); + scan.set_plane("p"); + scan.set_center_and_radius(util::XYZ(0x100000, 0x16, 0x23), 0.2); + LuaAssertStrEq(L, pm.search_bboxes_debug_string(scan), + "|Level 8 0,0,0 - 0,0,0" + "|Level 6 f,8,8 - f,8,8" + "|Level 4 ff,80,80 - ff,80,80" + "|Level 2 fff,801,802 - fff,801,802" + "|Level 0 ffff,8015,8022 - ffff,8016,8023"); + + // Confirm that the scan finds the outlier. + LuaAssertStrEq(L, pm.scan_steps_debug_string(scan), + "|L8:root" + "| L7:3,2,2" + "| L6:f,8,8" + "| L5:3f,20,20" + "| L4:ff,80,80" + "| L3:3ff,200,200" + "| L2:fff,801,802" + "| L1:3fff,2005,2008" + "| L0:ffff,8016,8023 123" + "|Result: 123"); + + return 0; +} diff --git a/luprex/cpp/core/planemap.hpp b/luprex/cpp/core/planemap.hpp new file mode 100644 index 00000000..36fe6526 --- /dev/null +++ b/luprex/cpp/core/planemap.hpp @@ -0,0 +1,260 @@ +////////////////////////////////////////////////////////////// +// +// PLANEMAP +// +// This module defines two classes: PlaneMap, and PlaneItem. A +// PlaneItem is an object that has a plane (a string) and an +// XYZ position. A PlaneMap keeps track of potentially thousands +// of PlaneItem objects, allowing you to search for PlaneItems +// by scanning a geographic region. +// +// CLASS SPRITE DERIVES FROM PLANEITEM +// +// A PlaneMap records the positions of PlaneItems. The intent is +// that class Sprite should derive from PlaneItem. That way, the +// PlaneMap can record the positions of sprites. +// +// When you put derived types like Sprite into a PlaneMap, the +// PlaneMap will work fine. However, when you scan the PlaneMap, +// it will give return PlaneItem pointers, not Sprite pointers. +// +// If you're sure that the PlaneMap contains only sprites, then +// you can use static_cast to convert those PlaneItem pointers to +// Sprite pointers. Sadly, there's no simple way to avoid the casting. +// +// THE SCANNABLE REGION IS FINITE +// +// A plane is a rectangle, with a finite size: if you stray more +// than 80,000,000 meters from the origin, then that PlaneItem +// is beyond the scannable region. +// +// There's nothing stopping you from moving a PlaneItem outside +// the scannable region. It is not an error to do so. +// However, if you do move a PlaneItem outside the scannable +// region, then that PlaneItem will not show up for scan_radius. +// +// PLANES CANNOT BE DESTROYED BUT THEY CAN BE UNINHABITED +// +// You can't "create" or "destroy" planes. Instead, we say +// that a plane is "uninhabited" until you put a PlaneItem +// there. Initially, all possible planes exist, but they're +// all uninhabited until you put a PlaneItem there. Planes +// that are uninhabited take no memory. +// +// THE NOWHERE PLANE +// +// If you use the literal "nowhere" as a plane name, a special case is +// triggered: you're "nowhere." Radius scans can never find items +// whose plane is "nowhere." The same also applies when you use the +// empty string as a plane name. +// +// THE Z COORDINATE IS IGNORED +// +// Class PlaneItem stores X, Y, and Z. The Z coordinate is +// ignored for all plane-scanning operations. In other words, +// planes are 2D. The only reason we store a Z coordinate +// is for the consistency of storing the model's X, Y, and Z +// all in the same place. +// +// MEMORY MANAGEMENT +// +// PlaneMaps do not own PlaneItems. This is a deliberate choice. +// We assume that sprites will be owned by the sprite ID table. +// So therefore, the PlaneMaps shouldn't own the sprites. +// +// So instead, we use this rule: a PlaneMap has a function 'track' +// that causes it to start tracking the location of a PlaneItem. +// However, the PlaneMap still doesn't own the PlaneItem. +// If you destroy a PlaneMap, all the PlaneItems +// will automatically be untracked, but they won't be deleted. +// +////////////////////////////////////////////////////////////// + +#ifndef PLANEMAP_HPP +#define PLANEMAP_HPP + +#include "wrap-vector.hpp" +#include "wrap-map.hpp" +#include "wrap-string.hpp" +#include "wrap-bytell-hash-map.hpp" +#include "util.hpp" +#include "luastack.hpp" + +#include +#include +#include + +class PlaneMap; +class PlaneTree; + + +class PlaneScan : public eng::nevernew { +public: + friend class PlaneMap; + friend class PlaneTree; + enum Shape { BOX, CYLINDER, SPHERE }; +private: + // The plane to scan. + // + // Note: the reason this uses std::string instead of eng::string + // is that we want plane scans to not touch the engine heap. + // + std::string plane_; + + // The bounding box of the scan. + util::XYZ center_, radius_; + + // If you scan a cylinder or SPHERE, it actually + // scans the bounding box first, then clips out + // the parts that aren't correct. + Shape shape_; + + // When true, the items are sorted by ID. + // WARNING: setting this to false can create + // nondeterminism. Scans by lua should always be sorted. + // When sorting and 'include_near' are both specified, + // the near item is returned in its sorted order. + bool sorted_; + + // The near ID is omitted from the results if include_near is false. + // The near ID is included in the results if include_near is true. + // The near ID is ignored if near is zero. + int64_t near_; + bool include_near_; + + // If this is true, items on the nowhere plane are not scanned. + bool omit_nowhere_; + +public: + void clear() { + plane_.clear(); + shape_ = BOX; + sorted_ = true; + near_ = 0; + include_near_ = false; + omit_nowhere_ = false; + radius_ = util::XYZ(); + center_ = util::XYZ(); + } + PlaneScan() { clear(); } + + // Convert a lua table into a scan configuration. + // + // If there is an error in the configuration, + // throws a lua error message. + // + // Caution: if this detects the configuration flag 'near', + // then it stores the near ID. However, it doesn't fetch + // the center and plane. It is the caller's responsibility + // to check if 'near' has been set, and if so, store a center + // and plane. This is admittedly ugly, but it eliminates + // a dependency on the world module. + // + void configure(LuaKeywordParser &kw); + + void set_center_and_radius(const util::XYZ ¢er, float r) { + set_center(center); + set_radius(r); + } + + void set_whole_plane() { + shape_ = BOX; + center_ = util::XYZ(); + radius_.x = std::numeric_limits::infinity(); + radius_.y = radius_.z = radius_.x; + } + + void set_center(const util::XYZ ¢er) { center_ = center; } + void set_radius(const util::XYZ &radius) { radius_ = radius; } + void set_radius(float f) { radius_.x = radius_.y = radius_.z = f; } + void set_plane(std::string_view p) { plane_ = p; } + void set_shape(Shape s) { shape_ = s; } + void set_sorted(bool s) { sorted_ = s; } + void set_near(int64_t id, bool inc) { near_ = id; include_near_ = inc; } + void set_omit_nowhere(bool b) { omit_nowhere_ = b; } + + int64_t near() const { return near_; } + + // Reveal the contents of the PlaneScan as a string. + eng::string debug_string() const; +}; + +class PlaneItem : public eng::nevernew { + friend class PlaneTree; + friend class PlaneMap; +private: + PlaneTree *tree_; + PlaneItem *prev_; + PlaneItem *next_; + int64_t id_; + eng::string plane_; + float x_, y_, z_; + +public: + PlaneItem(); + ~PlaneItem(); + + // You may modify the ID at any time. + void set_id(int64_t id) { id_ = id; } + + int64_t id() const { return id_; } + const eng::string &plane() const { return plane_; } + const float x() const { return x_; } + const float y() const { return y_; } + const float z() const { return z_; } + + void track(PlaneMap *pmap); + void untrack() { track(nullptr); } + + void set_pos(const eng::string &plane, float x, float y, float z); + void set_xyz(float x, float y, float z); +}; + +class PlaneMap : public eng::nevernew { + friend class PlaneItem; + friend class PlaneTree; +private: + float default_radius_; + + // Plane Trees table. + // + eng::map, std::less<>> planes_; + +public: + // No special code is needed for construction or destruction. + PlaneMap(); + ~PlaneMap(); + + // The 'insert' and 'remove' operators are inside class + // PlaneItem. See PlaneItem::track and PlaneItem::untrack. + + // Scan the PlaneMap for items, return their IDs. + // + // Note: the reason this uses IdVector instead of IdVector + // is that we want scans to not touch the engine heap. + // + void scan(const PlaneScan &sc, util::IdVector *into) const; + + // Set the default radius for all planes. + // Maybe we'll make it adaptive some day. + void set_default_radius(float r) { default_radius_ = r; } + + // Return a debug string for the specified plane. + // This is for unit testing. + eng::string tree_debug_string(const eng::string &plane); + eng::string outliers_debug_string(const eng::string &plane); + eng::string search_bboxes_debug_string(const PlaneScan &scan); + eng::string scan_steps_debug_string(const PlaneScan &scan); + + // Untrack all planeitems. This is for unit testing. + void untrack_all(); + +private: + // unit testing stuff. + friend int lfn_unittests_planemap(lua_State *L); +}; + + +#endif // PLANEMAP_HPP + + diff --git a/luprex/cpp/core/pprint.cpp b/luprex/cpp/core/pprint.cpp new file mode 100644 index 00000000..73817f02 --- /dev/null +++ b/luprex/cpp/core/pprint.cpp @@ -0,0 +1,352 @@ + +#include +#include "pprint.hpp" +#include "util.hpp" +#include "table.hpp" + +#include + +class PrintMachine { +public: + LuaVar tabchpos_; + LuaExtStack LS_; + int next_id_; + bool indent_; + std::ostream *output_; + eng::map chpos_to_tabnum_; + + void atomic_print(int xtype, LuaSlot val, bool quote) { + switch (xtype) { + case LUA_TNIL: { + (*output_) << "nil"; + return; + } + case LUA_TSTRING: { + if (quote) { + util::quote_string(LS_.ckstring(val), output_); + } else { + // TODO: this could be more efficient. + (*output_) << LS_.ckstring(val); + } + return; + } + case LUA_TNUMBER: { + double value = LS_.cknumber(val); + if (std::isnan(value)) { + (*output_) << "nan"; + } else { + int64_t ivalue = int64_t(value); + if (double(ivalue) == value) { + (*output_) << ivalue; + } else { + (*output_) << value; + } + } + return; + } + case LUA_TBOOLEAN: { + (*output_) << (LS_.ckboolean(val) ? "true" : "false"); + return; + } + case LUA_TFUNCTION: { + (*output_) << ""; + return; + } + case LUA_TTHREAD: { + (*output_) << ""; + return; + } + case LUA_TLIGHTUSERDATA: { + LuaToken token = LS_.cktoken(val); + (*output_) << "[" << token.str() << "]"; + return; + } + case LUA_TT_GENERAL: { + (*output_) << ""; + return; + } + case LUA_TT_TANGIBLE: { + (*output_) << ""; + return; + } + case LUA_TT_CLASS: { + (*output_) << ""; + return; + } + case LUA_TT_GLOBALENV: { + (*output_) << ""; + return; + } + case LUA_TT_TANGIBLEMETA: { + (*output_) << ""; + return; + } + default: { + (*output_) << ""; + return; + }} + } + + void tabify(int level) { + if (indent_) { + (*output_) << std::endl; + for (int i = 0; i < level; i++) { + (*output_) << " "; + } + } else { + (*output_) << " "; + } + } + + void pprint_r(int level, bool expand, LuaSlot value) { + lua_State *L = LS_.state(); + lua_checkstack(L, 20); + LuaVar loffset, pairs, key, val, lchpos, nextseq; + LuaExtStack LS(L, loffset, pairs, key, val, lchpos, nextseq); + + // Determine the extended type of the object. If the + // expand flag is true, try to coerce it to a general table. + int xtype = LS.xtype(value); + + // Print the atomic portion. + if (xtype != LUA_TT_GENERAL) { + atomic_print(xtype, value, true); + if ((xtype < LUA_TT_GENERAL) || (!expand)) { + return; + } + } + + // Find out whether it has a table number. If necessary, + // assign one. + int tabnum = 0; + LS.rawget(lchpos, tabchpos_, value); + auto chpos = LS.tryint(lchpos); + if (!chpos) { + // First time. Record the character position where the + // table first appears in the output stream. + LS.rawset(tabchpos_, value, int((*output_).tellp())); + } else { + tabnum = chpos_to_tabnum_[*chpos]; + if (tabnum == 0) { + // Second time. The table is already in the output, + // but it hasn't been assigned a number. Assign one. + tabnum = next_id_++; + chpos_to_tabnum_[*chpos] = tabnum; + } + } + + // If the table has a number, that means we're visiting + // it for the second time. Just print an abbreviated version + // and return. + if (tabnum > 0) { + (*output_) << "
"; + if (lua_nkeys(L, value.index())==0) { + (*output_) << "{}"; + } else { + (*output_) << "{...}"; + } + return; + } + + // State variables. + bool needcomma = false; + bool multiline = false; + LS.set(nextseq, 1); + + // Open the brackets. + (*output_) << "{"; + + // Output the table keys. + table_getpairs(LS, value, pairs, true); + for (int i = 2; ; i+=2) { + LS.rawget(key, pairs, i); + if (LS.isnil(key)) break; + LS.rawget(val, pairs, i+1); + if (needcomma) (*output_) << ","; + needcomma = true; + if (LS.rawequal(key, nextseq)) { + (*output_) << " "; + pprint_r(level + 1, false, val); + LS.set(nextseq, LS.ckinteger(nextseq) + 1); + } else { + multiline = true; + tabify(level + 1); + if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) { + (*output_) << LS.ckstring(key); + } else { + (*output_) << "["; + pprint_r(level + 1, false, key); + (*output_) << "]"; + } + if (indent_) { + (*output_) << " = "; + } else { + (*output_) << "="; + } + pprint_r(level + 1, false, val); + } + } + + // Output the metatable. + LS.getmetatable(val, value); + if (LS.istable(val) && (LS.gettabletype(val) != LUA_TT_TANGIBLEMETA)) { + multiline = true; + if (needcomma) (*output_) << ","; + needcomma = true; + tabify(level + 1); + (*output_) << " = "; + pprint_r(level + 1, false, val); + } + + // Close the brackets. + if (multiline) { + tabify(level); + } else if (LS.ckinteger(nextseq) > 1) { + (*output_) << " "; + } + (*output_) << "}"; + } + + // Atomic print interface. + PrintMachine(LuaCoreStack &LS0, LuaSlot root, bool quote, std::ostream *os) : + LS_(LS0.state(), tabchpos_) { + output_ = os; + atomic_print(LS_.xtype(root), root, quote); + } + + // Pretty print interface. + PrintMachine(LuaCoreStack &LS0, LuaSlot root, bool indent, int level, bool expand, std::ostream *os) : + LS_(LS0.state(), tabchpos_) { + next_id_ = 1; + indent_ = indent; + LS_.newtable(tabchpos_); + util::ostringstream preoutput; + output_ = &preoutput; + pprint_r(level, expand, root); + std::string_view pre = preoutput.view(); + + // Output the results. We would just copy the characters + // one by one to the target stream, except that we have to + // insert
in front of all tables that got referenced. + chpos_to_tabnum_.emplace(0x7FFFFFFF, 0); + auto iter = chpos_to_tabnum_.begin(); + for (int i = 0; i < int(pre.size()); i++) { + if (i == iter->first) { + (*os) << "
second << ">"; + iter++; + } + (*os).put(pre[i]); + } + } +}; + +void PrettyPrintOptions::parse(LuaKeywordParser &kp) { + LuaVar option; + LuaExtStack LS(kp.state(), option); + if (kp.parse(option, "indent")) { + indent = LS.ckboolean(option); + } + if (kp.parse(option, "level")) { + level = LS.ckint(option); + } + if (kp.parse(option, "expand")) { + expand = LS.ckboolean(option); + } +} + +void atomic_print(LuaCoreStack &LS, LuaSlot val, bool quote, std::ostream *os) { + PrintMachine pm(LS, val, quote, os); +} + +void pprint(LuaCoreStack &LS, LuaSlot val, const PrettyPrintOptions &opts, std::ostream *os) { + PrintMachine pm(LS, val, opts.indent, opts.level, opts.expand, os); +} + +LuaDefine(string_pprint, "obj1, obj2, ...", + "|Pretty-print the specified objects into a string." + "|" + "|See also: string.pprintx, which has a lot more options." + "|This function uses the default options: pretty print indented," + "|start at indentation level zero, and always expand the" + "|top-level table." + "|") { + LuaRet result; + LuaExtraArgs extra; + LuaDefStack LS(L, result, extra); + + util::ostringstream oss; + for (int i = 0; i < extra.size(); i++) { + pprint(LS, extra[i], PrettyPrintOptions(), &oss); + oss << "\n"; + } + LS.set(result, oss.view()); + return LS.result(); +} + +LuaDefine(string_pprintx, "options", + "|Pretty-print the specified object into a string, with options" + "|" + "|Options is a table with these fields:" + "|" + "| value - the object to pretty-print" + "| indent - if false, suppress newlines and indentation (default: true)" + "| level - base level of indentation (default: zero)" + "| expand - if true, force expansion of top-level table (default: false)" + "|" + "|About the expand flag: normally, when you print a class, it just " + "|prints '', and when you print a tangible, it just" + "|prints ''. But sometimes, you want to see the details." + "|The expand flag forces it to expand the top-level table, even if the" + "|top-level table is a tangible or class." + "|") { + LuaArg loptions; + LuaRet result; + LuaVar value; + LuaDefStack LS(L, loptions, result, value); + PrettyPrintOptions options; + LuaKeywordParser kp(LS, loptions); + options.parse(kp); + if (!kp.parse(value, "value")) { + LS.set(value, LuaNil); + } + kp.final_check_throw(); + util::ostringstream oss; + pprint(LS, value, options, &oss); + LS.set(result, oss.view()); + return LS.result(); +} + +LuaDefine(string_print, "obj", + "|Concise print the specified object into a string" + "|" + "|This prints a concise representation of obj into a string. Tables" + "|are not expanded: for that, use string.pprint or string.pprintx." + "|" + "|The functions string.print and tostring are identical." + "|") { + LuaArg val; + LuaRet result; + LuaDefStack LS(L, val, result); + eng::ostringstream oss; + atomic_print(LS, val, false, &oss); + LS.set(result, oss.str()); + return LS.result(); +} + +LuaDefineAlias(tostring, string_print); + +LuaDefine(string_isidentifier, "str", "return true if the string is a valid lua identifier") { + LuaArg str; + LuaRet result; + LuaDefStack LS(L, str, result); + if (LS.isstring(str)) { + eng::string s = LS.ckstring(str); + LS.set(result, sv::is_lua_id(s)); + } else { + LS.set(result, false); + } + return LS.result(); +} + + + diff --git a/luprex/cpp/core/pprint.hpp b/luprex/cpp/core/pprint.hpp new file mode 100644 index 00000000..7f4a7729 --- /dev/null +++ b/luprex/cpp/core/pprint.hpp @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////////////////// +// +// print, pprint, and tostring +// +// This module implements the heart of the lua 'print', lua 'pprint', and lua +// 'tostring' functions. Note that we have to override the lua builtins 'print' +// and 'tostring' for two reasons: +// +// * We need to suppress the printing of table addresses, for determinism. +// * We need to channel the output to a PrintBuffer in the world model. +// +// Note that the actual lua 'print' and 'pprint' routines aren't defined in this +// module, they're in the World module, because they send their output into the +// PrintBuffer of the world model. But all the tricky code to implement 'print' +// and 'pprint' are in this module. +// +////////////////////////////////////////////////////////////////////////////////// + +#ifndef PPRINT_HPP +#define PPRINT_HPP + +#include "luastack.hpp" +#include + +struct PrettyPrintOptions { + bool indent; + int level; + bool expand; + PrettyPrintOptions() : indent(true), level(0), expand(true) {} + void parse(LuaKeywordParser &kp); +}; + +// Atomic print to a stream. +// +// This prints an atomic value to a stream. If you give it a table, +// it just prints "
". This routine is the heart of the lua +// primitives 'print' and 'tostring'. +// +void atomic_print(LuaCoreStack &LS, LuaSlot val, bool quote, std::ostream *os); + +// Pretty print to a stream. +// +void pprint(LuaCoreStack &LS, LuaSlot val, const PrettyPrintOptions &opts, std::ostream *os); + +#endif // PPRINT_HPP \ No newline at end of file diff --git a/luprex/cpp/core/printbuffer.cpp b/luprex/cpp/core/printbuffer.cpp new file mode 100644 index 00000000..90f05b25 --- /dev/null +++ b/luprex/cpp/core/printbuffer.cpp @@ -0,0 +1,278 @@ +#include "wrap-sstream.hpp" + +#include "printbuffer.hpp" + +#include +#include +#include + +struct PrintBufferCore : public eng::opnew { + // The most recent lines printed. + eng::deque lines_; + + // Line number of the first line in the buffer. From this, all other + // line numbers can be inferred. + int first_line_; + + // Line number of the first unchecked line in the buffer. All line + // numbers including this one and beyond are unchecked. + // "Unchecked" lines are lines in a non-authoritative model + // that haven't been verified through difference transmission. + + int first_unchecked_; + + // Constructor. + PrintBufferCore() : first_line_(0), first_unchecked_(0) {} +}; + +static PrintBufferCore shared_core; + +PrintBuffer::PrintBuffer() { + core_ = &shared_core; +} + +PrintBuffer::~PrintBuffer() { + if (core_ != &shared_core) delete core_; +} + +int PrintBuffer::first_line() const { + return core_->first_line_; +} + +int PrintBuffer::last_line() const { + return core_->first_line_ + core_->lines_.size(); +} + +int PrintBuffer::first_unchecked() const { + return core_->first_unchecked_; +} + +bool PrintBuffer::never_printed() const { + return (core_->first_line_==0) && (core_->first_unchecked_==0) && (core_->lines_.size()==0); +} + +const eng::string &PrintBuffer::nth(int n) const { + return core_->lines_[n - core_->first_line_]; +} + +eng::string PrintBuffer::debug_string() const { + eng::ostringstream oss; + oss << core_->first_line_ << "," << core_->first_unchecked_ << ":"; + for (int i = 0; i < int(core_->lines_.size()); i++) { + oss << core_->lines_[i] << ";"; + } + return oss.str(); +} + +void PrintBuffer::clear() { + if (core_ != &shared_core) { + delete core_; + core_ = &shared_core; + } +} + +static int first_line_len(const char *text, int len) { + for (int i = 0; i < len; i++) { + if (text[i] == '\n') return i; + } + return len; +} + +static void add_line(PrintBufferCore *core, const char *text, int len, bool auth) { + assert(core != &shared_core); + core->lines_.emplace_back(text, len); + if (auth) { + core->first_unchecked_ = core->first_line_ + int(core->lines_.size()); + } +} + +void PrintBuffer::add_string(const eng::string &s, bool auth) { + const char *text = s.c_str(); + int len = s.size(); + if (core_ == &shared_core) core_ = new PrintBufferCore; + while (true) { + int fll = first_line_len(text, len); + if (fll == len) { + if (len > 0) { + add_line(core_, text, len, auth); + } + return; + } else { + add_line(core_, text, fll, auth); + text += (fll + 1); + len -= (fll + 1); + } + } +} + +void PrintBuffer::discard_upto(int n) { + if (core_ == &shared_core) return; + while ((!core_->lines_.empty()) && (core_->first_line_ < n)) { + core_->lines_.pop_front(); + core_->first_line_ += 1; + } + if (core_->first_unchecked_ < core_->first_line_) { + core_->first_unchecked_ = core_->first_line_; + } +} + +void PrintBuffer::serialize(StreamBuffer *sb) const { + if (never_printed()) { + sb->write_bool(true); + } else { + sb->write_bool(false); + sb->write_uint32(core_->first_line_); + sb->write_uint32(core_->first_unchecked_); + sb->write_uint32(core_->lines_.size()); + for (const eng::string &s : core_->lines_) { + sb->write_string(s); + } + } +} + +void PrintBuffer::deserialize(StreamBuffer *sb) { + bool never_printed = sb->read_bool(); + if (never_printed) { + clear(); + } else { + if (core_ == &shared_core) core_ = new PrintBufferCore; + core_->first_line_ = sb->read_uint32(); + core_->first_unchecked_ = sb->read_uint32(); + int nlines = sb->read_uint32(); + core_->lines_.clear(); + for (int i = 0; i < nlines; i++) { + core_->lines_.push_back(sb->read_string()); + } + } +} + +void PrintBuffer::diff(const PrintBuffer &auth, StreamBuffer *sb) const { + // Currently, the implementation is simple. The synchronous model discards + // all prediction lines from its buffer, then the server retransmits all + // those lines. So this barely counts as difference transmission - it's + // just transmission, regardless of what was in the synchronous model. I + // think that's okay for the text console. + // Ask the client to discard anything that precedes auth_first. + sb->write_int32(auth.first_line()); + sb->write_int32(auth.last_line()); + for (int i = core_->first_unchecked_; i < auth.last_line(); i++) { + eng::string line; + if (i >= auth.first_line()) line = auth.nth(i); + sb->write_string(line); + } +} + +void PrintBuffer::patch(StreamBuffer *sb, DebugCollector *dbc) { + DebugBlock dbb(dbc, "PrintBuffer::patch"); + if (core_ == &shared_core) core_ = new PrintBufferCore; + int auth_first = sb->read_int32(); + int auth_last = sb->read_int32(); + core_->lines_.resize(core_->first_unchecked_ - core_->first_line_); + int nlines = auth_last - core_->first_unchecked_; + if (nlines > 0) DebugLine(dbc) << "PrintBuffer received " << nlines << " lines."; + for (int i = core_->first_unchecked_; i < auth_last; i++) { + core_->lines_.emplace_back(sb->read_string()); + } + core_->first_unchecked_ = core_->first_line_ + core_->lines_.size(); + discard_upto(auth_first); + if (core_->first_line_ < auth_first) { + core_->first_line_ = auth_first; + } +} + +bool PrintChanneler::channel(const PrintBuffer *printbuffer, std::ostream &ostream) { + if (printbuffer == nullptr) return false; + if (printbuffer->first_line() > line_) { + line_ = printbuffer->first_line(); + } + while (line_ < printbuffer->first_unchecked()) { + ostream << "|" << printbuffer->nth(line_) << std::endl; + line_ += 1; + } + return line_ > printbuffer->first_line(); +} + +Invocation PrintChanneler::invocation(int64_t actor_id) { + char buf[80]; + sprintf(buf, "%" PRId64, line_); + return Invocation(InvocationKind::FLUSH_PRINTS, actor_id, actor_id, buf); +} + +LuaDefine(unittests_printbuffer, "", "some unit tests") { + PrintBuffer pbm; + PrintBuffer pbs; + StreamBuffer sb; + + // Test authoritative add_string and discard_upto. + LuaAssertStrEq(L, pbm.debug_string(), "0,0:"); + pbm.add_string("foo\nbar\nbaz\n", true); + LuaAssertStrEq(L, pbm.debug_string(), "0,3:foo;bar;baz;"); + pbm.add_string("a\nb\nc", true); + LuaAssertStrEq(L, pbm.debug_string(), "0,6:foo;bar;baz;a;b;c;"); + pbm.discard_upto(2); + LuaAssertStrEq(L, pbm.debug_string(), "2,6:baz;a;b;c;"); + pbm.discard_upto(8); + LuaAssertStrEq(L, pbm.debug_string(), "6,6:"); + + // Test nonauthoritative add_string and discard_upto. + LuaAssertStrEq(L, pbs.debug_string(), "0,0:"); + pbs.add_string("foo\nbar\nbaz\n", false); + LuaAssertStrEq(L, pbs.debug_string(), "0,0:foo;bar;baz;"); + pbs.add_string("a\nb\nc", false); + LuaAssertStrEq(L, pbs.debug_string(), "0,0:foo;bar;baz;a;b;c;"); + pbs.discard_upto(2); + LuaAssertStrEq(L, pbs.debug_string(), "2,2:baz;a;b;c;"); + pbs.discard_upto(8); + LuaAssertStrEq(L, pbs.debug_string(), "6,6:"); + + // Test serialization and deserialization of a nonempty printbuffer. + pbm.clear(); + sb.clear(); + pbm.add_string("boy\nhowdy\nyeah\n", true); + pbm.add_string("baby\nget\ndown\n", false); + LuaAssertStrEq(L, pbm.debug_string(), "0,3:boy;howdy;yeah;baby;get;down;"); + pbm.serialize(&sb); + pbs.deserialize(&sb); + LuaAssertStrEq(L, pbs.debug_string(), "0,3:boy;howdy;yeah;baby;get;down;"); + + // Test serialization and deserialization of an empty printbuffer. + pbm.clear(); + sb.clear(); + LuaAssert(L, pbm.never_printed()); + pbm.serialize(&sb); + pbs.deserialize(&sb); + LuaAssert(L, pbs.never_printed()); + + pbm.clear(); + pbs.clear(); + sb.clear(); + pbm.add_string("foo\nbar\n", true); + pbs.diff(pbm, &sb); + pbs.patch(&sb, nullptr); + LuaAssertStrEq(L, pbs.debug_string(), "0,2:foo;bar;"); + pbm.clear(); + pbm.add_string("foo\nyow\nding\ndong\n", true); + pbs.diff(pbm, &sb); + pbs.patch(&sb, nullptr); + LuaAssertStrEq(L, pbs.debug_string(), "0,4:foo;bar;ding;dong;"); + pbs.discard_upto(2); + LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;"); + pbs.diff(pbm, &sb); + pbs.patch(&sb, nullptr); + LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;"); + pbs.add_string("boy\nhowdy\n", false); + LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;boy;howdy;"); + pbs.diff(pbm, &sb); + pbs.patch(&sb, nullptr); + LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;"); + pbs.add_string("boy\nhowdy\nyeah\nbaby\nget\ndown\n", false); + LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;boy;howdy;yeah;baby;get;down;"); + pbs.discard_upto(5); + LuaAssertStrEq(L, pbs.debug_string(), "5,5:howdy;yeah;baby;get;down;"); + pbs.diff(pbm, &sb); + pbs.patch(&sb, nullptr); + LuaAssertStrEq(L, pbs.debug_string(), "5,5:"); + + + return 0; +} diff --git a/luprex/cpp/core/printbuffer.hpp b/luprex/cpp/core/printbuffer.hpp new file mode 100644 index 00000000..c39316de --- /dev/null +++ b/luprex/cpp/core/printbuffer.hpp @@ -0,0 +1,164 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// PRINTBUFFER +// +// Lua has a 'print' statement - in client-server mode, where does the output of +// the 'print' statement go? We need to be able to handle print-statements on +// the server (and forward them to the client), and we need for predictive +// prints to be handled gracefully. +// +// Class PrintBuffer is a buffer for storing the output of the print statement. +// It is part of the actor tangible. When a lua thread calls 'print', the print +// goes into stringstream lthread_prints. When the thread stops or yields, the +// contents of lthread_prints are converted to lines and transferred to the +// PrintBuffer of the actor. From there, it gets difference transmitted, and the +// client can probe it. +// +// Suppose, for example, that the player logs in and invokes a plan that prints +// six lines from a Robert Frost poem. That plan is executed by both the master +// model and the synchronous model. When the thread finishes, the PrintBuffer +// in the actor in the master model will contain this: +// +// * Line 0: Whose woods these are I think I know. [authoritative] +// * Line 1: His house is in the village though; [authoritative] +// * Line 2: He will not see me stopping here [authoritative] +// * Line 3: To watch his woods fill up with snow. [authoritative] +// * Line 4: My little horse must think it queer [authoritative] +// * Line 5: To stop without a farmhouse near. [authoritative] +// +// Note that the buffer stores line numbers, which start from zero the moment +// the player logs in. In the master model, all lines are always authoritative +// because everything in the master model is authoritative. In the synchronous +// model, the PrintBuffer is likely to contain the same strings, but the lines +// will all be marked as [prediction] instead of [authoritative]. +// +// When the server difference transmits, the difference transmission will fix up +// any mistakes in the PrintBuffer in the synchronous model. In the process, it +// will also fix up any [prediction] changing it to [authoritative]. +// +// Periodically, the oldest lines in the buffer will get discarded. More on +// this later. When lines get discarded, the line numbers don't change. For +// example, if we were to discard three lines from the buffer above, this is +// what would remain: +// +// * Line 3: To watch his woods fill up with snow. [authoritative] +// * Line 4: My little horse must think it queer [authoritative] +// * Line 5: To stop without a farmhouse near. [authoritative] +// +// The client will periodically probe the PrintBuffer. Suppose it sees all six +// lines of the Robert Frost poem. The next time it probes the buffer, it will +// still see those same six lines. The client will continue to see those six +// lines until it takes explicit steps. +// +// Here's how we keep the buffer from growing forever. The client probes the +// PrintBuffer, and sees some authoritative lines. The client downloads and +// stores those lines locally. Once the lines are stored locally in the client, +// the client injects a command into the command stream: "delete from +// PrintBuffer up to line N" into the command stream. This will cause the +// engine to discard the lines that the client no longer needs. +// +// Note that when the client injects a "delete from PrintBuffer" into the +// command stream, that's an invoke-command that has to follow the same process +// as any other client invoke command. It can take time for it to get +// processed. Therefore, the client must be prepared that it might see some +// redundant lines for a little while. +// +// MEMORY EFFICIENCY. +// +// Every tangible will contain a printbuffer. To avoid memory waste, the +// implementation of PrintBuffer stores everything inside a dynamically +// allocated "core" struct. All printbuffers that contain nothing share a +// common empty core. That way, the empty printbuffers that will exist in 99% of +// all tangibles will take up very little space. +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef PRINTBUFFER_HPP +#define PRINTBUFFER_HPP + +#include "wrap-deque.hpp" +#include "wrap-string.hpp" +#include +#include "streambuffer.hpp" +#include "util.hpp" +#include "invocation.hpp" +#include "debugcollector.hpp" + +#include + +struct PrintBufferCore; + +class PrintBuffer : public eng::nevernew { +private: + PrintBufferCore *core_; + +public: + PrintBuffer(); + ~PrintBuffer(); + + // Get the current first line. + int first_line() const; + + // Return the line number beyond the end of the buffer. + int last_line() const; + + // Get the current first unchecked line. + // "Unchecked" lines are lines in a non-authoritative model + // that haven't been verified through difference transmission. + // Guaranteed to be between first_line and last_line inclusive. + int first_unchecked() const; + + // Return true if the printbuffer is in the initial state. + // Note: if you clear the buffer, it's back in the initial state. + bool never_printed() const; + + // Get the specified line number. + const eng::string &nth(int n) const; + + // Print the entire contents of the buffer to a string (for unit testing). + eng::string debug_string() const; + + // Clear the buffer + void clear(); + + // Add a string. If the string doesn't end in a newline, a newline + // is added. The string is broken into lines, and the lines are added + // to the PrintBuffer. + void add_string(const eng::string &s, bool auth); + + // Discard lines up to but not including line N. + void discard_upto(int n); + + // Serialization and deserialization + void serialize(StreamBuffer *sb) const; + void deserialize(StreamBuffer *sb); + + // Difference transmission + void diff(const PrintBuffer &auth, StreamBuffer *sb) const; + void patch(StreamBuffer *sb, DebugCollector *dbc); +}; + + +class PrintChanneler : public eng::nevernew { +private: + int64_t line_; +public: + PrintChanneler() { line_ = 0; } + + // Reset the print channeler. + void reset() { line_ = 0; } + + // Copy any new lines from the printbuffer to the stdostream. + // Update the current line number. Return true if the printbuffer + // contains any lines that have already been channeled. + bool channel(const PrintBuffer *pb, std::ostream &ostream); + + // Generate an invocation that removes unnecessary lines from the + // printbuffer. + Invocation invocation(int64_t actor_id); +}; + + +#endif // PRINTBUFFER_HPP + diff --git a/luprex/cpp/core/sched.cpp b/luprex/cpp/core/sched.cpp new file mode 100644 index 00000000..6075668b --- /dev/null +++ b/luprex/cpp/core/sched.cpp @@ -0,0 +1,107 @@ + +#include "wrap-sstream.hpp" + +#include "sched.hpp" +#include "streambuffer.hpp" +#include "luastack.hpp" + +#include + +bool SchedEntry::operator < (const SchedEntry &other) const { + if (clock_ < other.clock_) return true; + if (clock_ > other.clock_) return false; + if (thread_id_ < other.thread_id_) return true; + if (thread_id_ > other.thread_id_) return false; + if (place_id_ < other.place_id_) return true; + if (place_id_ > other.place_id_) return false; + return false; +} + +eng::string SchedEntry::debug_string() const { + eng::ostringstream oss; + oss << "(" << clock_ << "," << thread_id_ << "," << place_id_ << ")"; + return oss.str(); +} + +void Schedule::add(int64_t clk, int64_t thid, int64_t plid) { + assert(plid != 0); + assert(thid != 0); + schedule_.insert(SchedEntry(clk, thid, plid)); +} + +bool Schedule::ready(int64_t clk) const { + return (!schedule_.empty()) && (schedule_.begin()->clock() <= clk); +} + +SchedEntry Schedule::pop() { + assert(!schedule_.empty()); + SchedEntry result = *schedule_.begin(); + schedule_.erase(schedule_.begin()); + return result; +} + +eng::string Schedule::debug_string() { + eng::ostringstream oss; + for (const SchedEntry &se : schedule_) { + oss << se.debug_string(); + } + return oss.str(); +} + +void Schedule::serialize(StreamBuffer *sb) { + sb->write_uint32(schedule_.size()); + for (const SchedEntry &entry : schedule_) { + sb->write_int64(entry.clock_); + sb->write_int64(entry.thread_id_); + sb->write_int64(entry.place_id_); + } +} + +void Schedule::deserialize(StreamBuffer *sb) { + schedule_.clear(); + size_t nentry = sb->read_uint32(); + for (size_t i = 0; i < nentry; i++) { + int64_t clock = sb->read_int64(); + int64_t thread_id = sb->read_int64(); + int64_t place_id = sb->read_int64(); + add(clock, thread_id, place_id); + } +} + +LuaDefine(unittests_scheduler, "", "some unit tests") { + Schedule s, xs; + StreamBuffer sb; + + // Put some stuff into a scheduler. + s.add(1, 5, 3); + s.add(4, 3, 2); + s.add(1, 3, 2); + s.add(4, 2, 3); + + // Test serialize and deserialize. + LuaAssert(L, s.debug_string() == "(1,3,2)(1,5,3)(4,2,3)(4,3,2)"); + s.serialize(&sb); + xs.deserialize(&sb); + LuaAssert(L, xs.debug_string() == "(1,3,2)(1,5,3)(4,2,3)(4,3,2)"); + + // Verify that pop, ready, and empty do the right thing. + LuaAssert(L, s.ready(0) == false) + LuaAssert(L, s.ready(1) == true) + LuaAssert(L, s.pop().debug_string() == "(1,3,2)"); + LuaAssert(L, s.ready(0) == false) + LuaAssert(L, s.ready(1) == true) + LuaAssert(L, s.pop().debug_string() == "(1,5,3)"); + LuaAssert(L, s.debug_string() == "(4,2,3)(4,3,2)"); + LuaAssert(L, s.pop().debug_string() == "(4,2,3)"); + LuaAssert(L, s.pop().debug_string() == "(4,3,2)"); + LuaAssert(L, s.ready(10000) == false); + LuaAssert(L, s.empty()); + LuaAssert(L, s.debug_string() == ""); + + // Test serialization of an empty streambuffer. + s.serialize(&sb); + xs.deserialize(&sb); + LuaAssert(L, xs.debug_string() == ""); + + return 0; +} diff --git a/luprex/cpp/core/sched.hpp b/luprex/cpp/core/sched.hpp new file mode 100644 index 00000000..9af31c28 --- /dev/null +++ b/luprex/cpp/core/sched.hpp @@ -0,0 +1,48 @@ +#ifndef SCHED_HPP +#define SCHED_HPP + +#include "wrap-set.hpp" + +#include "streambuffer.hpp" + +#include + +class SchedEntry : public eng::nevernew { +private: + friend class Schedule; + int64_t clock_; + int64_t thread_id_; + int64_t place_id_; + +public: + int64_t clock() const { return clock_; } + int64_t thread_id() const { return thread_id_; } + int64_t place_id() const { return place_id_; } + + SchedEntry(int64_t clk, int64_t thid, int64_t plid) { + clock_ = clk; + thread_id_ = thid; + place_id_ = plid; + } + + bool operator < (const SchedEntry &other) const; + + eng::string debug_string() const; +}; + +class Schedule : public eng::nevernew { +private: + eng::set schedule_; +public: + void add(int64_t clk, int64_t thid, int64_t plid); + bool ready(int64_t clk) const; + bool empty() const { return schedule_.empty(); } + SchedEntry pop(); + eng::string debug_string(); + + void serialize(StreamBuffer *sb); + void deserialize(StreamBuffer *sb); +}; + +#endif // SCHED_HPP + diff --git a/luprex/cpp/core/serializelua.cpp b/luprex/cpp/core/serializelua.cpp new file mode 100644 index 00000000..5498b6a2 --- /dev/null +++ b/luprex/cpp/core/serializelua.cpp @@ -0,0 +1,362 @@ +#include "serializelua.hpp" + +enum PackCodes { + LUA_PK_TRUE = LUA_TT_SENTINEL, + LUA_PK_FALSE, + LUA_PK_REFERENCE, + LUA_PK_ENDTABLE +}; + +class DeserializeError { +}; + +class Deserializer { + LuaVar id_to_value_; + LuaExtStack LS_; + eng::string &error_; + StreamBuffer *sb_; + int next_id_; + + void deserialize_table_r(LuaSlot target) { + LuaVar key, val; + LuaExtStack LS(LS_.state(), key, val); + LS.newtable(target); + LS.rawset(id_to_value_, next_id_++, target); + bool hasmeta = sb_->read_bool(); + if (hasmeta) { + uint8_t vtype = sb_->read_uint8(); + deserialize_r(vtype, val); + if (!LS.istable(val)) { + error_ = "serialized data contains invalid metatable"; + throw DeserializeError(); + } + LS.setmetatable(target, val); + } + while (true) { + uint8_t ktype = sb_->read_uint8(); + if (ktype == LUA_PK_ENDTABLE) { + break; + } + deserialize_r(ktype, key); + uint8_t vtype = sb_->read_uint8(); + deserialize_r(vtype, val); + LS.rawset(target, key, val); + } + } + + void deserialize_r(uint8_t kind, LuaSlot target) { + switch (kind) { + case LUA_TNIL: { + LS_.set(target, LuaNil); + return; + } + case LUA_PK_TRUE: { + LS_.set(target, true); + return; + } + case LUA_PK_FALSE: { + LS_.set(target, false); + return; + } + case LUA_TLIGHTUSERDATA: { + LS_.set(target, LuaToken(sb_->read_uint64())); + return; + } + case LUA_TNUMBER: { + LS_.set(target, sb_->read_double()); + return; + } + case LUA_TSTRING: { + LS_.set(target, sb_->read_string()); + LS_.rawset(id_to_value_, next_id_++, target); + return; + } + case LUA_TT_GENERAL: { + deserialize_table_r(target); + return; + } + case LUA_TT_GLOBALENV: { + LS_.getglobaltable(target); + return; + } + case LUA_TT_TANGIBLE: { + int64_t tanid = sb_->read_int64(); + if (!LS_.validpositiveinteger(tanid)) { + throw DeserializeError(); + } + LS_.maketan(target, tanid); + return; + } + case LUA_TT_CLASS: { + eng::string name = sb_->read_string(); + if (!sv::is_lua_classname(name)) { + throw DeserializeError(); + } + LS_.makeclass(target, name); + return; + } + case LUA_PK_REFERENCE: { + int32_t key = sb_->read_int32(); + LS_.rawget(target, id_to_value_, key); + if (LS_.isnil(target)) { + error_ = "invalid backward reference in serialized data"; + throw DeserializeError(); + } + return; + } + default: { + error_ = util::ss("unrecognized type token in serialized data: ", kind); + throw DeserializeError(); + } + } + } + +public: + Deserializer(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb, eng::string &error) : + LS_(LS0.state(), id_to_value_), error_(error), sb_(sb), next_id_(1) { + LS_.newtable(id_to_value_); + int top = lua_gettop(LS_.state()); + try { + uint16_t v = sb_->read_uint16(); + if (v != 0xD096) { + error_ = "This is not a serialized data structure"; + return; + } + uint8_t b = sb_->read_uint8(); + deserialize_r(b, val); + } catch (const StreamException &e) { + error_ = "EOF reached while deserializing data"; + lua_settop(LS_.state(), top); + LS_.set(val, LuaNil); + } catch (DeserializeError e) { + lua_settop(LS_.state(), top); + LS_.set(val, LuaNil); + } + } +}; + + +// If the serialize routine encounters an error, it must set the +// error message string. In that case, it is assumed that the contents +// of the serialization buffer is no longer valid and there is no +// requirement to try to keep it valid. + +class Serializer { + LuaVar lookup_; + LuaVar value_to_id_; + LuaExtStack LS_; + eng::string &error_; + StreamBuffer *sb_; + int next_id_; + + void serialize_table_r(LuaSlot tab) { + LuaVar key, val; + LuaExtStack SLS(LS_.state(), key, val); + sb_->write_uint8(LUA_TT_GENERAL); + SLS.getmetatable(val, tab); + if (SLS.isnil(val)) { + sb_->write_bool(false); + } else { + sb_->write_bool(true); + serialize_r(val); + } + SLS.set(key, LuaNil); + while (SLS.next(tab, key, val)) { + serialize_r(key); + if (!error_.empty()) { + return; + } + serialize_r(val); + if (!error_.empty()) { + return; + } + } + sb_->write_uint8(LUA_PK_ENDTABLE); + } + + void serialize_r(LuaSlot val) { + int tt = LS_.xtype(val); + switch (tt) { + case LUA_TNIL: { + sb_->write_uint8(LUA_TNIL); + return; + } + case LUA_TBOOLEAN: { + if (LS_.ckboolean(val)) { + sb_->write_uint8(LUA_PK_TRUE); + } else { + sb_->write_uint8(LUA_PK_FALSE); + } + return; + } + case LUA_TLIGHTUSERDATA: { + sb_->write_uint8(LUA_TLIGHTUSERDATA); + sb_->write_uint64(LS_.cktoken(val).value); + return; + } + case LUA_TNUMBER: { + sb_->write_uint8(LUA_TNUMBER); + sb_->write_double(LS_.cknumber(val)); + return; + } + case LUA_TSTRING: { + LS_.rawget(lookup_, value_to_id_, val); + if (!LS_.isnil(lookup_)) { + sb_->write_uint8(LUA_PK_REFERENCE); + sb_->write_int32(LS_.ckint(lookup_)); + } else { + LS_.rawset(value_to_id_, val, next_id_++); + sb_->write_uint8(LUA_TSTRING); + sb_->write_string(LS_.ckstring(val)); + } + return; + } + case LUA_TTABLE: { + // LS.xtype should never return LUA_TTABLE. + error_ = "Bad xtype in serialization"; + return; + } + case LUA_TFUNCTION: { + error_ = "Cannot serialize closures"; + return; + } + case LUA_TUSERDATA: { + error_ = "Cannot serialize userdata"; + return; + } + case LUA_TTHREAD: { + error_ = "Cannot serialize coroutines"; + return; + } + case LUA_TT_GENERAL: { + LS_.rawget(lookup_, value_to_id_, val); + if (!LS_.isnil(lookup_)) { + sb_->write_uint8(LUA_PK_REFERENCE); + sb_->write_int32(LS_.ckint(lookup_)); + } else { + LS_.rawset(value_to_id_, val, next_id_++); + serialize_table_r(val); + } + return; + } + case LUA_TT_REGISTRY: { + error_ = "Pointer to registry found in serialization, shouldn't happen"; + return; + } + case LUA_TT_GLOBALENV: { + sb_->write_uint8(LUA_TT_GLOBALENV); + return; + } + case LUA_TT_TANGIBLE: { + sb_->write_uint8(LUA_TT_TANGIBLE); + sb_->write_int64(LS_.tanid(val)); + return; + } + case LUA_TT_TANGIBLEMETA: { + error_ = "Pointer to a tangible metatable found in serialization, shouldn't happen"; + return; + } + case LUA_TT_CLASS: { + sb_->write_uint8(LUA_TT_CLASS); + sb_->write_string(LS_.classname(val)); + return; + } + default: { + error_ = "Unrecognized xtype in serialization"; + return; + } + } + } + +public: + Serializer(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb, eng::string &error) : + LS_(LS0.state(), lookup_, value_to_id_), error_(error), sb_(sb), next_id_(1) { + LS_.newtable(value_to_id_); + sb_->write_uint16(0xD096); + serialize_r(val); + } +}; + +eng::string serialize_lua(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb) { + eng::string error; + Serializer sz(LS0, val, sb, error); + return error; +}; + +eng::string deserialize_lua(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb) { + eng::string error; + Deserializer dsz(LS0, val, sb, error); + if (!error.empty()) { + LS0.set(val, LuaNil); + } + return error; +} + +LuaDefine(table_serialize, "value", + "|Serialize any lua value, returning a string" + "|" + "|Converts lua values into a binary string. The string" + "|can then be deserialized to produce a copy of the value." + "|" + "|Supports these atomic types:" + "|" + "| nil, string, number, boolean, token" + "|" + "|Does not support these types at all:" + "|" + "| function, thread" + "|" + "|Supports simple tables. When it finds a simple table, it recurses" + "|into the table making a deep copy. Metatables are also supported." + "|" + "|Cycles are supported: if you have two tables which point to each other," + "|they can be serialized, and the deserialized copy will contain two" + "|tables that point to each other." + "|" + "|Duplicate tables and duplicate strings are handled efficiently." + "|For example, if the data contains two copies of a 10KB string, the" + "|serialized data will only contain one copy of the string." + "|" + "|Tangibles are tables, but they are handled specially. When the" + "|serialize routine finds a tangible, it just stores the tangible's ID." + "|The deserialized copy will then contain the same tangible by ID." + "|" + "|Classes are also tables, but they too are handled specially. When" + "|the serialize routine finds a class, it stores the class name, and" + "|the deserialized copy will then contain the same class." + "|" + "|The global environment table is also treated specially: if you" + "|serialize it, it will not recurse into it, instead, the deserialized" + "|copy will just contain a pointer to the global environment." + "|") { + LuaArg value; + LuaRet str; + LuaDefStack LS(L, value, str); + StreamBuffer sb; + eng::string error = serialize_lua(LS, value, &sb); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + return LS.result(); + } + LS.set(str, sb.view()); + return LS.result(); +} + +LuaDefine(table_deserialize, "binary", + "|Deserialize a serialized block, returning a value" + "|" + "|See doc(table.serialize) for more information about what" + "|kind of data can be serialized and how it is deserialized." + "|") { + LuaArg str; + LuaRet value; + LuaDefStack LS(L, value, str); + std::string_view s = LS.ckstringview(str); + StreamBuffer sb(s); + eng::string error = deserialize_lua(LS, value, &sb); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + return LS.result(); + } + return LS.result(); +} \ No newline at end of file diff --git a/luprex/cpp/core/serializelua.hpp b/luprex/cpp/core/serializelua.hpp new file mode 100644 index 00000000..c3b8f34f --- /dev/null +++ b/luprex/cpp/core/serializelua.hpp @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////// +// +// This module contains routines to serialize and deserialize +// lua data structures. These routines can handle cyclic data +// structures. +// +// These routines are used for some specific difference +// transmission cases, but they're not the heart of the lua +// difference transmission system. +// +//////////////////////////////////////////////////////////// + + +#ifndef SERIALIZELUA_HPP +#define SERIALIZELUA_HPP + +#include "luastack.hpp" +#include "streambuffer.hpp" + +// serialize_lua +// +// Serialize a value from the LuaSlot and store it in the StreamBuffer. +// On success, returns empty string. +// +// If there is an error, returns an error message. In this case, the +// streambuffer contains garbage. +// +eng::string serialize_lua(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb); + +// deserialize_lua +// +// Deserialize a value from the StreamBuffer and store it in the LuaSlot. +// On success, returns empty string. +// +// If there is an error, returns an error message. In this case, the +// streambuffer is likely only partially consumed. +// +eng::string deserialize_lua(LuaCoreStack &LS0, LuaSlot val, StreamBuffer *sb); + +#endif // SERIALIZELUA_HPP diff --git a/luprex/cpp/core/source.cpp b/luprex/cpp/core/source.cpp new file mode 100644 index 00000000..5de13fe4 --- /dev/null +++ b/luprex/cpp/core/source.cpp @@ -0,0 +1,859 @@ + +#define _USE_MATH_DEFINES +#include + +#include "wrap-string.hpp" +#include "wrap-vector.hpp" +#include "wrap-map.hpp" +#include "wrap-set.hpp" +#include "wrap-sstream.hpp" +#include "util.hpp" +#include "luastack.hpp" +#include "traceback.hpp" +#include "table.hpp" +#include "source.hpp" +#include "luasnap.hpp" + +#include +#include + +LuaDefine(makeclass, "classname", "create a class if it doesn't already exist") { + LuaArg classname; + LuaRet classtab; + LuaDefStack LS(L, classname, classtab); + if (!sv::is_lua_classname(LS.ckstring(classname))) { + luaL_error(L, "invalid class name: %s", LS.ckstring(classname).c_str()); + }; + LS.makeclass(classtab, classname); + return LS.result(); +} + +LuaDefine(getclass, "classname", "get the classtab with the specified name") { + LuaArg classname; + LuaRet classtab; + LuaDefStack LS(L, classname, classtab); + eng::string err = LS.getclass(classtab, classname); + if (err != "") { + luaL_error(L, "%s", err.c_str()); + } + return LS.result(); +} + +LuaDefine(classname, "classtable", "get the class name from a class table") { + LuaArg table; + LuaRet result; + LuaDefStack LS(L, table, result); + eng::string rstr = LS.classname(table); + if (rstr == "") { + LS.set(result, LuaNil); + } else { + LS.set(result, rstr); + } + return LS.result(); +} + +static void get_reg_name(std::string_view name, std::string_view &classname, std::string_view &funcname) { + size_t upos = name.find('_'); + if (upos == std::string_view::npos) { + funcname = name; + classname = ""; + } else { + funcname = name.substr(upos + 1); + classname = name.substr(0, upos); + } +} + +static void get_info_table(LuaCoreStack &LS, LuaSlot db, LuaSlot info, const eng::string &fn) { + LS.rawget(info, db, fn); + if (!LS.istable(info)) { + LS.set(info, LuaNewTable); + LS.rawset(db, fn, info); + } + LS.rawset(info, "name", fn); +} + +static void calculate_loadresult(LuaCoreStack &LS0, LuaSlot info, const eng::string &fn, const eng::string &code) { + LuaVar loadresult; + LuaExtStack LS(LS0.state(), loadresult); + if (code == "") { + LS.rawset(info, "loadresult", "missing or empty source file"); + } else { + eng::string chunk = "=" + fn; + luaL_loadbuffer(LS.state(), code.c_str(), code.size(), chunk.c_str()); + lua_replace(LS.state(), loadresult.index()); + LS.rawset(info, "loadresult", loadresult); + } +} + + +void SourceDB::diff(const SourceDB &auth, StreamBuffer *sb) { + LuaVar sdb, sfn, sinfo, shash, sseq; + LuaVar mdb, mfn, minfo, mhash, mseq, mcode; + LuaExtStack SLS(lua_state_, sdb, sfn, sinfo, shash, sseq); + LuaExtStack MLS(auth.lua_state_, mdb, mfn, minfo, mhash, mseq, mcode); + sb->write_int32(0); + int wc_after = sb->total_writes(); + int nupdates = 0; + // Fetch the two source databases. + SLS.rawget(sdb, LuaRegistry, "sourcedb"); + MLS.rawget(mdb, LuaRegistry, "sourcedb"); + // Loop over the master database. + MLS.set(mfn, LuaNil); + while (MLS.next(mdb, mfn, minfo) != 0) { + eng::string fn = MLS.ckstring(mfn); + assert(MLS.istable(minfo)); + MLS.rawget(mseq, minfo, "sequence"); + MLS.rawget(mhash, minfo, "hash"); + bool diffhash = true; + bool diffseq = true; + SLS.set(sfn, fn); + SLS.rawget(sinfo, sdb, sfn); + if (SLS.istable(sinfo)) { + SLS.rawget(shash, sinfo, "hash"); + SLS.rawget(sseq, sinfo, "sequence"); + diffhash = MLS.ckstring(mhash) != SLS.ckstring(shash); + diffseq = MLS.ckinteger(mseq) != SLS.ckinteger(sseq); + } + if (diffhash || diffseq) { + sb->write_string(MLS.ckstring(mfn)); + sb->write_int32(MLS.ckinteger(mseq)); + if (diffhash) { + MLS.rawget(mcode, minfo, "code"); + sb->write_string(MLS.ckstring(mcode)); + } else { + sb->write_string("\001"); + } + nupdates += 1; + } + } + // Loop over synch database. + SLS.set(sfn, LuaNil); + while (SLS.next(sdb, sfn, sinfo) != 0) { + eng::string fn = SLS.ckstring(sfn); + assert(SLS.istable(sinfo)); + MLS.set(mfn, fn); + MLS.rawget(minfo, mdb, mfn); + if (!MLS.istable(minfo)) { + sb->write_string(SLS.ckstring(sfn)); + sb->write_int32(-1); + sb->write_string(""); + nupdates += 1; + } + } + sb->overwrite_int32(wc_after, nupdates); +} + +bool SourceDB::patch(StreamBuffer *sb, DebugCollector *dbc) { + lua_State *L = lua_state_; + LuaVar db, info; + LuaExtStack LS(L, db, info); + LS.rawget(db, LuaRegistry, "sourcedb"); + int nupdates = sb->read_int32(); + for (int i = 0; i < nupdates; i++) { + eng::string fn = sb->read_string(); + int sequence = sb->read_int32(); + eng::string code = sb->read_string(); + DebugLine(dbc) << "Source file " << fn << " updated"; + if (sequence < 0) { + LS.rawset(db, fn, LuaNil); + } else { + get_info_table(LS, db, info, fn); + LS.rawset(info, "sequence", sequence); + if (code != "\001") { + LS.rawset(info, "code", code); + LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code))); + calculate_loadresult(LS, info, fn, code); + } + } + } + return (nupdates > 0); +} + +void SourceDB::set(const eng::string &fn, const eng::string &code, int sequence) { + lua_State *L = lua_state_; + LuaVar db, info; + LuaExtStack LS(L, db, info); + LS.rawget(db, LuaRegistry, "sourcedb"); + get_info_table(LS, db, info, fn); + LS.rawset(info, "sequence", sequence); + LS.rawset(info, "code", code); + LS.rawset(info, "fingerprint", ""); + LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code))); + calculate_loadresult(LS, info, fn, code); +} + +eng::string SourceDB::get(const eng::string &fn) { + lua_State *L = lua_state_; + LuaVar db, info, code, sequence, loadresult; + LuaExtStack LS(L, db, info, code, sequence, loadresult); + LS.rawget(db, LuaRegistry, "sourcedb"); + LS.rawget(info, db, fn); + if (!LS.istable(info)) { + return ""; + } + LS.rawget(code, info, "code"); + LS.rawget(sequence, info, "sequence"); + LS.rawget(loadresult, info, "loadresult"); + eng::string ccode = LS.ckstring(code); + int seqno = LS.ckint(sequence); + eng::string cloadresult; + if (LS.isfunction(loadresult)) { + cloadresult = ""; + } else { + cloadresult = LS.ckstring(loadresult); + } + return util::ss(seqno, ":", ccode, ":", cloadresult); +} + +void SourceDB::update(const util::LuaSourceVec &source) { + lua_State *L = lua_state_; + LuaVar sourcedb, info; + LuaExtStack LS(L, sourcedb, info); + + // Get and clear the source database. + LS.rawget(sourcedb, LuaRegistry, "sourcedb"); + if (!LS.istable(sourcedb)) { + LS.newtable(sourcedb); + LS.rawset(LuaRegistry, "sourcedb", sourcedb); + } + LS.cleartable(sourcedb, true); + + for (int i = 0; i < int(source.size()); i++) { + const eng::string &file = source[i].first; + const eng::string &code = source[i].second; + util::dprint("Compiling ", file); + LS.newtable(info); + LS.rawset(info, "name", file); + LS.rawset(info, "code", code); + LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code))); + LS.rawset(info, "sequence", i + 1); + calculate_loadresult(LS, info, file, code); + LS.rawset(sourcedb, file, info); + } +} + +// Delete everything from the global environment. +// Clear all the classes in the registry classes table. +// +static void source_clear_globals(lua_State *L) { + LuaVar classname, classtab, key, globtab, classes; + LuaExtStack LS(L, classname, classtab, key, globtab, classes); + + LS.getglobaltable(globtab); + LS.cleartable(globtab, true); + LS.rawset(globtab, "_G", globtab); + + LS.rawget(classes, LuaRegistry, "classes"); + assert(LS.istable(classes)); + LS.set(classname, LuaNil); + while (LS.next(classes, classname, classtab) != 0) { + assert(LS.istable(classtab)); + LS.cleartable(classtab, true); + } +} + + +// Load all the 'LuaDefine' C functions into the lua state. +// +static void source_load_cfunctions(lua_State *L) { + LuaVar classobj; + LuaExtStack LS(L, classobj); + for (auto r = LuaFunctionReg::All; r != nullptr; r=r->next()) { + lua_CFunction func = r->get_func(); + if ((func != nullptr) && (!r->get_sandbox())) { + std::string_view classname; + std::string_view funcname; + get_reg_name(r->get_name(), classname, funcname); + if (classname.empty()) { + LS.getglobaltable(classobj); + LS.rawset(classobj, funcname, func); + } else { + LS.makeclass(classobj, classname); + LS.rawset(classobj, funcname, func); + } + } + } +} + +// Load all the 'LuaConstant' constants into the lua state. +// +static void source_load_cconstants(lua_State *L) { + LuaVar classobj, value; + LuaExtStack LS(L, classobj, value); + for (auto r = LuaConstantReg::All; r != nullptr; r=r->next()) { + if (r->get_tokenvalue().empty()) { + LS.set(value, r->get_numbervalue()); + } else { + LS.set(value, r->get_tokenvalue()); + } + std::string_view classname; + std::string_view funcname; + get_reg_name(r->get_name(), classname, funcname); + if (classname.empty()) { + LS.getglobaltable(classobj); + LS.rawset(classobj, funcname, value); + } else { + LS.makeclass(classobj, classname); + LS.rawset(classobj, funcname, value); + } + } +} + +eng::vector SourceDB::modules() { + eng::vector result; + LuaVar sourcedb, key, info, seq; + LuaExtStack LS(lua_state_, sourcedb, key, info, seq); + + result.push_back("CORE"); + + // Get the source database. + LS.rawget(sourcedb, LuaRegistry, "sourcedb"); + if (!LS.istable(sourcedb)) { + return result; + } + + // Sort the keys by sequence number. + eng::map indices; + LS.set(key, LuaNil); + while (LS.next(sourcedb, key, info) != 0) { + LS.rawget(seq, info, "sequence"); + indices[LS.ckinteger(seq)] = LS.ckstring(key); + } + + for (const auto &pair : indices) { + result.push_back(pair.second); + } + return result; +} + +void SourceDB::rebuild_core() { + source_clear_globals(lua_state_); + source_load_cfunctions(lua_state_); + source_load_cconstants(lua_state_); +} + +eng::string SourceDB::rebuild_module(const eng::string &mod) { + if (mod == "CORE") { + rebuild_core(); + return ""; + } else { + LuaVar sourcedb, info, closure; + LuaExtStack LS(lua_state_, sourcedb, info, closure); + LS.rawget(sourcedb, LuaRegistry, "sourcedb"); + if (!LS.istable(sourcedb)) { + return "SourceDB not initialized"; + } + LS.rawget(info, sourcedb, mod); + if (!LS.istable(info)) { + return util::ss("No such module: ", mod); + } + LS.rawget(closure, info, "loadresult"); + if (!LS.isfunction(closure)) { + return util::ss(mod, ":", LS.ckstring(closure)); + } + lua_pushvalue(lua_state_, closure.index()); + return traceback_pcall(lua_state_, 0, 0); + } +} + +void SourceDB::run_unittests() { + lua_State *L = lua_state_; + LuaVar unittests, name, func, err, globtab; + LuaExtStack LS(L, unittests, name, func, err, globtab); + + LS.getglobaltable(globtab); + LS.rawget(unittests, globtab, "unittests"); + + // Sort the unit test names. + eng::set names; + LS.set(name, LuaNil); + while (LS.next(unittests, name, func) != 0) { + if (LS.isfunction(func) && LS.isstring(name)) { + names.insert(LS.ckstring(name)); + } + } + + // Run the functions in order + bool any = false; + for (const eng::string &name : names) { + util::dprint("Running unittests: ", name); + LS.rawget(func, unittests, name); + + lua_pushvalue(L, func.index()); + eng::string msg = traceback_pcall(L, 0, 0); + if (!msg.empty()) { + LS.set(err, msg); + util::dprint(msg); + any = true; + } + } + + if (any) { + exit(1); + } +} + +void SourceDB::init(lua_State *L) { + lua_state_ = L; + LuaVar globtab, persist, unpersist, classname, classtab, funcname, funcp, rawfunc, nullstring; + LuaExtStack LS(L, globtab, persist, unpersist, classname, classtab, funcname, funcp, rawfunc, nullstring); + LS.getglobaltable(globtab); + LS.rawset(LuaRegistry, "sourcedb", LuaNewTable); + + // Set the metatable for strings. + LS.makeclass(classtab, "string"); + LS.set(nullstring, ""); + LS.setmetatable(nullstring, classtab); + + // Rebuild the global environment. + rebuild_core(); + + // We need to register all C functions with the eris permanents tables. + LS.rawget(persist, LuaRegistry, "persist"); + LS.rawget(unpersist, LuaRegistry, "unpersist"); + LS.set(classname, LuaNil); + while (LS.next(globtab, classname, classtab) != 0) { + if (LS.isstring(classname) && LS.istable(classtab)) { + LS.set(funcname, LuaNil); + while (LS.next(classtab, funcname, funcp) != 0) { + if (LS.isstring(funcname) && LS.iscfunction(funcp)) { + eng::string full = "cfunc:"; + full += LS.ckstring(classname); + full += "."; + full += LS.ckstring(funcname); + lua_pushcfunction(L, lua_tocfunction(L, funcp.index())); + lua_replace(L, rawfunc.index()); + LS.rawset(persist, rawfunc, full); + LS.rawset(unpersist, full, rawfunc); + } + } + } + } +} + +void SourceDB::serialize_source(const util::LuaSourceVec &sv, StreamBuffer *sb) { + sb->write_int32(sv.size()); + for (const auto &pair : sv) { + sb->write_string(pair.first); + sb->write_string(pair.second); + } +} + +void SourceDB::deserialize_source(util::LuaSourceVec *sv, StreamBuffer *sb) { + sv->clear(); + int count = sb->read_int32(); + if ((count < 0) || (count > 10000)) throw StreamCorruption(); + for (int i = 0; i < count; i++) { + eng::string fn = sb->read_string(); + eng::string code = sb->read_string(); + sv->emplace_back(fn, code); + } +} + +// Register lua builtins. +// +// To find the lua builtins, we use this approach: we create a new lua state +// (the 'prototype', we call it), and we call luaL_openlibs on it. The +// prototype should now contain all the builtins. +// +// Then, we iterate over our function registry, looking for LuaDefineBuiltin +// records. These records don't have a function pointer yet. For each such +// record, we look into the prototype, and look up the function in the global +// environment. If it's not found, we print an error. If we find it, we extract +// the C function pointer from it. Then, we remove the function from the +// prototype, to indicate that we already processed that function. +// +// At the end, we scan the prototype to make sure all functions have been +// removed. If not, we know our function registry is missing something and we +// print out an error. +// +void SourceDB::register_lua_builtins() { + lua_State *L = LuaCoreStack::newstate(nullptr); + luaL_openlibs(L); + { + LuaVar globals, lclassname, lfuncname, classtab, func; + LuaExtStack LS(L, globals, lclassname, lfuncname, classtab, func); + LS.getglobaltable(globals); + + // Iterate over the function registry, copying function pointers from + // the prototype lua state into the registry, then remove the closure + // from the prototype. + for (auto reg = LuaFunctionReg::All; reg != nullptr; reg=reg->next()) { + std::string_view funcname; + std::string_view classname; + get_reg_name(reg->get_name(), classname, funcname); + if (classname.empty()) { + LS.getglobaltable(classtab); + } else { + LS.rawget(classtab, globals, classname); + } + lua_CFunction builtin = nullptr; + if (LS.istable(classtab)) { + LS.rawget(func, classtab, funcname); + if (LS.iscfunction(func)) { + builtin = lua_tocfunction(L, func.index()); + LS.rawset(classtab, funcname, LuaNil); + } + } + if (reg->get_func() == nullptr) { + if (builtin == nullptr) { + if (!reg->get_sandbox()) { + util::dprint("No such builtin function: ", classname, " ", funcname); + } + } else { + reg->set_func(builtin); + } + } + } + + // Iterate over the prototype. All cfunctions should have been removed. + LS.set(lclassname, LuaNil); + while (LS.next(globals, lclassname, classtab)) { + if (LS.isstring(lclassname)) { + if (LS.istable(classtab)) { + LS.set(lfuncname, LuaNil); + while (LS.next(classtab, lfuncname, func)) { + if (LS.iscfunction(func)) { + util::dprint("Failed to declare builtin: ", LS.ckstring(lclassname), ".", LS.ckstring(lfuncname)); + } + } + } + } + } + } + lua_close(L); +} + + +eng::string SourceDB::function_docs(const LuaCoreStack &LS0, LuaSlot fn) { + lua_State *L = LS0.state(); + LuaVar sourcedb, fname, finfo, code; + LuaExtStack LS(L, sourcedb, fname, finfo, code); + + if (LS.iscfunction(fn)) { + lua_CFunction cfn = lua_tocfunction(L, fn.index()); + const LuaFunctionReg *reg = LuaFunctionReg::lookup(cfn); + if (reg == nullptr) { + return ""; + } + std::string_view classname; + std::string_view funcname; + get_reg_name(reg->get_name(), classname, funcname); + eng::ostringstream oss; + util::StringVec docs = util::split_docstring(reg->get_docs()); + oss << "function "; + if (!classname.empty()) { + oss << classname << "."; + } + oss << funcname << "(" << reg->get_args() << ")" << std::endl; + oss << "--" << std::endl; + for (const eng::string &line : docs) { + oss << "-- " << line << std::endl; + } + oss << "--" << std::endl; + return oss.str(); + } else if (LS.isfunction(fn)) { + lua_Debug ar; + lua_pushvalue(L, fn.index()); + int status = lua_getinfo(L, ">S", &ar); + if (status == 0) return ""; + + // Get the source database. + LS.rawget(sourcedb, LuaRegistry, "sourcedb"); + if (!LS.istable(sourcedb)) { + return ""; + } + + // Get the finfo table from the source db. + LS.set(fname, eng::string(ar.short_src)); + LS.rawget(finfo, sourcedb, fname); + if (!LS.istable(finfo)) { + return ""; + } + + // Get the code from the finfo table. + LS.rawget(code, finfo, "code"); + if (!LS.isstring(code)) { + return ""; + } + + // Split the code into lines. + util::StringVec lines = util::split_lines(LS.ckstring(code)); + int linehi = ar.linedefined - 1; + if ((linehi < 0) || (linehi >= int(lines.size()))) { + return ""; + } + + // Incorporate the function comment. + int linelo = linehi; + while ((linelo > 0) && (sv::is_lua_comment(lines[linelo-1]))) linelo -= 1; + + // Output the docs. + eng::ostringstream result; + result << lines[linehi] << std::endl; + result << "--" << std::endl; + for (int i = linelo; i < linehi; i++) { + result << lines[i] << std::endl; + } + result << "--" << std::endl; + return result.str(); + } else { + return ""; + } +} + +// These should go away eventually. They're for debugging. +LuaDefine(coroutine_setnextid, "thread,id", "set the next id of a thread (debugging only)") { + LuaArg co, lid; + LuaDefStack LS(L, co, lid); + lua_State *CO = LS.ckthread(co); + lua_Number id = LS.ckinteger(lid); + lua_setnextid(CO, id); + return LS.result(); +} + +LuaDefine(coroutine_getnextid, "thread", "get the next id of a thread (debugging only)") { + LuaArg co; + LuaRet lid; + LuaDefStack LS(L, co, lid); + lua_State *CO = LS.ckthread(co); + LS.set(lid, lua_getnextid(CO)); + return LS.result(); +} + +LuaDefine(unittests_sourcedb, "", "some unit tests") { + LuaSnap msnap; + LuaSnap ssnap; + SourceDB mdb; + SourceDB sdb; + mdb.init(msnap.state()); + sdb.init(ssnap.state()); + StreamBuffer sb; + + // Create a code database using 'set'. + mdb.set("foo", "function foo() print('foo') end", 1); + mdb.set("bar", "function bar() print('bar') end", 2); + mdb.set("zoo", "funcjdshja mxooso yowza!", 3); + + // Verify that get works. + LuaAssertStrEq(L, mdb.get("foo"), "1:function foo() print('foo') end:"); + LuaAssertStrEq(L, mdb.get("bar"), "2:function bar() print('bar') end:"); + LuaAssertStrEq(L, mdb.get("zoo"), "3:funcjdshja mxooso yowza!:zoo:1: syntax error near 'mxooso'"); + LuaAssertStrEq(L, mdb.get("baz"), ""); + + // Difference transmit. + sdb.diff(mdb, &sb); + + // There should still be nothing in the sdb. + LuaAssertStrEq(L, sdb.get("foo"), ""); + LuaAssertStrEq(L, sdb.get("bar"), ""); + LuaAssertStrEq(L, sdb.get("zoo"), ""); + LuaAssertStrEq(L, sdb.get("baz"), ""); + + // Apply the diffs. + sdb.patch(&sb, nullptr); + + // Everything should now be copied to sdb. + LuaAssertStrEq(L, sdb.get("foo"), "1:function foo() print('foo') end:"); + LuaAssertStrEq(L, sdb.get("bar"), "2:function bar() print('bar') end:"); + LuaAssertStrEq(L, sdb.get("zoo"), "3:funcjdshja mxooso yowza!:zoo:1: syntax error near 'mxooso'"); + LuaAssertStrEq(L, sdb.get("baz"), ""); + + // Make a single change to the code. + mdb.set("bar", "function bar1() print('bar1') end", 2); + + // Diff and patch + sdb.diff(mdb, &sb); + sdb.patch(&sb, nullptr); + + // Verify that it's been updated. + LuaAssertStrEq(L, sdb.get("foo"), "1:function foo() print('foo') end:"); + LuaAssertStrEq(L, sdb.get("bar"), "2:function bar1() print('bar1') end:"); + LuaAssertStrEq(L, sdb.get("zoo"), "3:funcjdshja mxooso yowza!:zoo:1: syntax error near 'mxooso'"); + LuaAssertStrEq(L, sdb.get("baz"), ""); + + // Make a single change to just a sequence number. + mdb.set("foo", "function foo() print('foo') end", 6); + + // Diff + sdb.diff(mdb, &sb); + + // The only thing we've updated is a single sequence number. Make + // sure the number of bytes transmitted is reasonable. + // 4 bytes for the count of changes + // 4 bytes for "foo" + // 4 bytes for the sequence number + // 2 bytes for unmodified code + // This verifies that the hash comparisons are working. + LuaAssert(L, sb.fill() == 14); + + // Patch. + sdb.patch(&sb, nullptr); + + // Verify that it's been updated. + LuaAssertStrEq(L, sdb.get("foo"), "6:function foo() print('foo') end:"); + LuaAssertStrEq(L, sdb.get("bar"), "2:function bar1() print('bar1') end:"); + LuaAssertStrEq(L, sdb.get("zoo"), "3:funcjdshja mxooso yowza!:zoo:1: syntax error near 'mxooso'"); + LuaAssertStrEq(L, sdb.get("baz"), ""); + + return 0; +} + +LuaDefineBuiltin(table_concat, "vector1, vector2", "concatenate two vectors"); +LuaDefineBuiltin(table_insert, "vector, pos, value", "insert an element into a vector"); +LuaDefineBuiltin(table_remove, "vector, pos", "remove an element from a vector"); +LuaDefineBuiltin(table_sort, "vector [,comparefn]", "sort a vector"); +LuaDefineBuiltin(table_pack, "v1, v2, v3...", "turn a sequence of arguments into a vector"); +LuaDefineBuiltin(table_unpack, "vector", "turn a vector into a sequence of return values"); +LuaSandboxBuiltin(table_maxn, "", ""); + +LuaDefineBuiltin(string_byte, "str [,index]", "get a single byte from a string"); +LuaDefineBuiltin(string_char, "byte, byte,...", "convert sequence of bytes to a string"); +LuaDefineBuiltin(string_find, "str, pattern [,index]", "return start and end of pattern in a string"); +LuaDefineBuiltin(string_len, "str", "return the length of the string, in bytes"); +LuaDefineBuiltin(string_rep, "str, count", "repeat the string some number of times"); +LuaDefineBuiltin(string_reverse, "str", "reverse the bytes of the string"); +LuaDefineBuiltin(string_lower, "str", "convert string to lowercase"); +LuaDefineBuiltin(string_upper, "str", "convert string to uppercase"); +LuaDefineBuiltin(string_format, "formatstr, v1,v2,v3...", "generate formatted output string"); +LuaDefineBuiltin(string_gmatch, "str, pattern", "iterate over pattern-matched substrings"); +LuaDefineBuiltin(string_gsub, "str, pattern, replace", "global replace pattern in string"); +LuaDefineBuiltin(string_match, "str, pattern", "return start and end of pattern in string"); +LuaDefineBuiltin(string_sub, "str, pos1, pos2", "return substring of str from pos1 to pos2"); +LuaSandboxBuiltin(string_dump, "func", "convert a function to a string"); + +LuaDefineBuiltin(bit32_arshift, "n, shift", "shift 32-bit number to the right, keeping high bit unchanged"); +LuaDefineBuiltin(bit32_band, "n, n, n...", "return the bitwise and of all 32-bit numbers"); +LuaDefineBuiltin(bit32_bnot, "n", "return the bitwise negation of n, ie, (-1 - n) % 2^32"); +LuaDefineBuiltin(bit32_bor, "n, n, n...", "return the bitwise or of all 32-bit arguments"); +LuaDefineBuiltin(bit32_bxor, "n, n, n...", "return the bitwise exclusive or of all 32-bit arguments"); +LuaDefineBuiltin(bit32_btest, "n, n, n...", "compute bitwise and of all 32-bit arguments, return true if nonzero"); +LuaDefineBuiltin(bit32_extract, "n, field, width", "return value from extracted bitfield of 32-bit number"); +LuaDefineBuiltin(bit32_lrotate, "n, shift", "rotate 32-bit number to the left"); +LuaDefineBuiltin(bit32_lshift, "n, shift", "shift 32-bit number to the left, padding with zeros"); +LuaDefineBuiltin(bit32_replace, "n, v, field, width", "change value of extracted bitfield in 32-bit number"); +LuaDefineBuiltin(bit32_rrotate, "n, shift", "rotate 32-bit number to the right"); +LuaDefineBuiltin(bit32_rshift, "n, shift", "shift 32-bit number to the right, padding with zeros"); + +LuaDefineBuiltin(math_abs, "x", "return absolute value of x"); +LuaDefineBuiltin(math_acos, "x", "return arc-cosine of x in radians"); +LuaDefineBuiltin(math_asin, "x", "return arc-sine of x in radians"); +LuaDefineBuiltin(math_atan, "x", "return the arc-tangent of x in radians"); +LuaDefineBuiltin(math_atan2, "y, x", "return arc-tangent of y/x, using signs to compute quadrant"); +LuaDefineBuiltin(math_ceil, "x", "return the smallest integer larger than or equal to x"); +LuaDefineBuiltin(math_cos, "x", "return the cosine of x in radians"); +LuaDefineBuiltin(math_cosh, "x", "return the hyperbolic cosine of x in radians"); +LuaDefineBuiltin(math_deg, "rad", "convert radians to degrees"); +LuaDefineBuiltin(math_exp, "x", "returns the value e^x"); +LuaDefineBuiltin(math_floor, "x", "returns the smallest integer less than or equal to x"); +LuaDefineBuiltin(math_fmod, "x, y", "return the remainder of x/y that rounds the quotient towards zero"); +LuaDefineBuiltin(math_frexp, "x", "given x, returns mantissa and exponent"); +LuaDefineBuiltin(math_ldexp, "x", "return unnormalized mantissa and exponent"); +LuaDefineBuiltin(math_log, "x [, base]", "return the log of x in base, default base is e"); +LuaDefineBuiltin(math_max, "x, x, x...", "return the largest argument"); +LuaDefineBuiltin(math_min, "x, x, x...", "return the smallest argument"); +LuaDefineBuiltin(math_modf, "x", "returns the integral and fractional part of x"); +LuaDefineBuiltin(math_pow, "x, y", "returns x ^ y, equivalent to the operator"); +LuaDefineBuiltin(math_rad, "deg", "convert degrees to radians"); +LuaDefineBuiltin(math_sin, "x", "return the sine of x in radians"); +LuaDefineBuiltin(math_sinh, "x", "return the hyperbolic sine of x in radians"); +LuaDefineBuiltin(math_sqrt, "x", "return the square root of x"); +LuaDefineBuiltin(math_tan, "x", "return the tangent of x in radians"); +LuaDefineBuiltin(math_tanh, "x", "return the hyperbolic tangent of x in radians"); +LuaSandboxBuiltin(math_log10, "", ""); +LuaNumberConstant(math_pi, M_PI, ""); +LuaNumberConstant(math_huge, HUGE_VAL, ""); +LuaNumberConstant(math_nan, NAN, ""); +LuaNumberConstant(math_maxint, LuaCoreStack::MAXINT, ""); +LuaTokenConstant(math_auto, "auto", "A value used to request that a value should be automatically computed"); + +// math.random and math.randomseed are in world-accessor.cpp, because +// generating random numbers must manipulate global state which is +// stored in the world model. + +LuaDefineBuiltin(assert, "flag [,message]", "assert that flag is true, if not, raise error"); +LuaDefineBuiltin(error, "message", "raise an error"); +LuaDefineBuiltin(getmetatable, "table", "get the metatable of a table"); +LuaDefineBuiltin(ipairs, "vector", "meant to be used in loops, iterates over a vector"); +LuaDefineBuiltin(next, "table, k", "returns the key after k and the corresponding value"); +LuaDefineBuiltin(pairs, "table", "meant to be used in loops, iterates over a table"); +LuaDefineBuiltin(rawpairs, "table", "meant to be used in loops, iterates over a table"); +LuaDefineBuiltin(pcall, "function, arg1, arg2, ...", "call the function, catching any errors"); +LuaDefineBuiltin(rawequal, "val1, val2", "return true if the two values are the same object"); +LuaDefineBuiltin(rawlen, "obj", "return the length of a vector or string"); +LuaDefineBuiltin(rawget, "table, key", "get a table entry, ignoring metamethods"); +LuaDefineBuiltin(rawset, "table, key, value", "set a table entry, ignoring metamethods"); +LuaDefineBuiltin(select, "n, arg1, arg2, ...", "return the nth argument"); +LuaDefineBuiltin(setmetatable, "table, meta", "set the metatable of the specified table"); +LuaDefineBuiltin(tonumber, "str", "convert a string to a number"); +LuaDefineBuiltin(type, "obj", "return the type of obj as a string"); +// print is redefined in world.cpp (because it prints into the world model) +// tostring is redefined in pprint.cpp + +LuaSandboxBuiltin(collectgarbage, "", ""); +LuaSandboxBuiltin(dofile, "", ""); +LuaSandboxBuiltin(xpcall, "", ""); +LuaSandboxBuiltin(loadfile, "", ""); +LuaSandboxBuiltin(load, "", ""); +LuaSandboxBuiltin(require, "", ""); +LuaSandboxBuiltin(module, "", ""); +LuaSandboxBuiltin(loadstring, "", ""); +LuaSandboxBuiltin(unpack, "", ""); + + +LuaSandboxBuiltin(debug_debug, "", ""); +LuaSandboxBuiltin(debug_getuservalue, "", ""); +LuaSandboxBuiltin(debug_gethook, "", ""); +LuaSandboxBuiltin(debug_getinfo, "", ""); +LuaSandboxBuiltin(debug_getlocal, "", ""); +LuaSandboxBuiltin(debug_getregistry, "", ""); +LuaSandboxBuiltin(debug_getmetatable, "", ""); +LuaSandboxBuiltin(debug_getupvalue, "", ""); +LuaSandboxBuiltin(debug_upvaluejoin, "", ""); +LuaSandboxBuiltin(debug_upvalueid, "", ""); +LuaSandboxBuiltin(debug_setuservalue, "", ""); +LuaSandboxBuiltin(debug_sethook, "", ""); +LuaSandboxBuiltin(debug_setlocal, "", ""); +LuaSandboxBuiltin(debug_setmetatable, "", ""); +LuaSandboxBuiltin(debug_setupvalue, "", ""); +LuaSandboxBuiltin(debug_traceback, "", ""); + +LuaSandboxBuiltin(eris_persist, "", ""); +LuaSandboxBuiltin(eris_unpersist, "", ""); +LuaSandboxBuiltin(eris_settings, "", ""); + +LuaSandboxBuiltin(package_loadlib, "", ""); +LuaSandboxBuiltin(package_searchpath, "", ""); +LuaSandboxBuiltin(package_seeall, "", ""); + +LuaSandboxBuiltin(coroutine_create, "", ""); +LuaSandboxBuiltin(coroutine_resume, "", ""); +LuaSandboxBuiltin(coroutine_running, "", ""); +LuaSandboxBuiltin(coroutine_status, "", ""); +LuaSandboxBuiltin(coroutine_wrap, "", ""); +LuaSandboxBuiltin(coroutine_yield, "", ""); + +LuaSandboxBuiltin(io_close, "", ""); +LuaSandboxBuiltin(io_flush, "", ""); +LuaSandboxBuiltin(io_input, "", ""); +LuaSandboxBuiltin(io_lines, "", ""); +LuaSandboxBuiltin(io_open, "", ""); +LuaSandboxBuiltin(io_output, "", ""); +LuaSandboxBuiltin(io_popen, "", ""); +LuaSandboxBuiltin(io_read, "", ""); +LuaSandboxBuiltin(io_tmpfile, "", ""); +LuaSandboxBuiltin(io_type, "", ""); +LuaSandboxBuiltin(io_write, "", ""); + +LuaSandboxBuiltin(os_clock, "", ""); +LuaSandboxBuiltin(os_date, "", ""); +LuaSandboxBuiltin(os_difftime, "", ""); +LuaSandboxBuiltin(os_execute, "", ""); +LuaSandboxBuiltin(os_exit, "", ""); +LuaSandboxBuiltin(os_getenv, "", ""); +LuaSandboxBuiltin(os_remove, "", ""); +LuaSandboxBuiltin(os_rename, "", ""); +LuaSandboxBuiltin(os_setlocale, "", ""); +LuaSandboxBuiltin(os_time, "", ""); +LuaSandboxBuiltin(os_tmpname, "", ""); + diff --git a/luprex/cpp/core/source.hpp b/luprex/cpp/core/source.hpp new file mode 100644 index 00000000..63dd1bbf --- /dev/null +++ b/luprex/cpp/core/source.hpp @@ -0,0 +1,209 @@ +//////////////////////////////////////////////////////////// +// +// SOURCEDB +// +// This module manages the loading of lua source files into the Lua environment. +// Since the source files can be reloaded over and over, this module doesn't +// just load the source files. Instead, it does "source rebuilds" in which it +// purges everything from the global environment, then it reinstalls everything +// that should be there. That way, if you delete something from a lua source +// file, it gets removed from the lua global environment. +// +// THE MAKECLASS OPERATOR +// +// This module provides a new lua 'builtin' operator: "makeclass". This creates +// a table and stores it in the global environment. The new table is meant to be +// used as a class. Three fields are initially created: +// +// __index --> points back to the class. Makes it convenient to use the +// class as a metatable. +// +// action --> a subtable of additional methods. +// +// If you invoke 'makeclass' on a class that already exists, the existing table +// is "repaired" - __index field is restored and the action +// subtable, if not present, is recreated. If there are already functions or +// constants inside the class, they are not affected. +// +// +// THE LUA SOURCE DATABASE +// +// Class SourceDB only contains a single pointer to the lua environment. That's +// because all the data for SourceDB is stored in the lua registry. +// Specifically, the registry contains these keys: +// +// 1. The source database proper (registry key "sourcedb") +// 2. The snapshot of builtins (registry key "source_snapshot_builtins") +// +// The source database proper is a table where the keys are filenames, and the +// values are records containing information about that file: +// +// "foo.lua" : { +// "name": "foo.lua", +// "fingerprint": "12893129385854", +// "code": "function xyz ...", +// "loadresult": , +// "sequence": 13 +// } +// +// Let's break that down a little. The "name" field is just the filename. The +// "fingerprint" is an encoding of the file modification date and the file +// length, it is used to detect whether the file has been altered on disk +// without having to reread the file. The "code" is the entire content of the +// source file. The "loadresult" is the closure that results from calling the +// lua 'load' function on the code. If load fails, then the "loadresult" is an +// error message. Finally, "sequence" indicates the order in which the source +// files are meant to be loaded. +// +// The operation SourceDB::update refreshes the source database from disk. It +// doesn't reread files whose fingerprints have not changed. In a synchronous +// model, we don't call SourceDB::update - instead, we update the source +// database by difference transmission. +// +// Note that updating the source database has *no effect* on the lua global +// variables (or lua function definitions). The SourceDB::update operation +// calls lua's "load" on the code, but all that does is return a loaded closure. +// Nothing happens to the lua invironment until you *invoke* the loaded closure. +// That doesn't happen until you do a SourceDB::rebuild operation, described +// below. +// +// +// SOURCE REBUILDS, IN DEPTH +// +// The function SourceDB::rebuild clears and then rebuilds the entire contents +// of the lua global environment. The reason to clear the environment is that +// if we didn't clear it, then removing a function from the lua source would not +// remove it from the lua environment. Rebuilding the lua environment is a +// multi-step process: +// +// * Delete everything from the global environment except class tables. +// +// - Class tables are kept, but the contents are cleared. +// - If a class has an "actions" subtable, that is kept and cleared. +// - Anything else is deleted. +// +// * Lua Builtin functions are reinstalled in the global environment. +// +// - To make this possible, the snapshot of builtins is used. +// +// * C++ functions registered with "LuaDefine" are reinstalled. +// +// - LuaDefine creates a registry, that registry is iterated over. +// +// * Lua code is reinstalled by running the closures in the source DB. +// +// - Simply call the "loadresult" closure to reinstall functions into the +// global environment. +// +// Note that if you've stored any global data in the lua environment, it's gone. +// So therefore, we have to provide a separate "safe" space for global data +// structures. That is provided elsewhere, in the module "globaldb". +// +// +// UNITTESTS +// +// We reserve the lua class name 'unittests' for storing unit tests. Any +// function placed into this class is considered a unit test. We don't separate +// unit tests from the rest of the code - they're compiled right into the main +// binary. +// +// Unit tests can be either lua functions, or Lua-registered C functions. Unit +// tests are executed by calling 'source_run_unittests'. +// +// Each unit test is run in a protected 'pcall' environment. Any errors are +// printed out to console. (At least for now). +// +//////////////////////////////////////////////////////////// + +#ifndef SOURCE_HPP +#define SOURCE_HPP + +#include "wrap-string.hpp" + +#include "util.hpp" +#include "luastack.hpp" +#include "streambuffer.hpp" +#include "debugcollector.hpp" + +class SourceDB : public eng::nevernew { +private: + lua_State *lua_state_; + +public: + void init(lua_State *L); + + // Update + // + // Update the database using the specified lua source code. + // Compiles these files using lua's "load" function. + // + void update(const util::LuaSourceVec &source); + + // modules + // + // Returns a list of all the modules. The first item in the list + // is always the string "CORE" which represents the lua core + // functionality with all the builtins. This is then followed by + // all the lua sourcefiles in the correct order. + // + eng::vector modules(); + + // rebuild_module + // + // To rebuild the lua environment, fetch the module list, then + // call rebuild_module on each module in turn. This will return + // an error message for the module, or empty string if no error. + // + // This is a thin wrapper around traceback_pcall. The return + // value is the return value of traceback_pcall. + // + eng::string rebuild_module(const eng::string &mod); + + // rebuild_core + // + // This is equivalent to rebuild_module("CORE"). Clears the environment + // and installs all the builtins. No error conditions. + // + void rebuild_core(); + + // Difference transmission. + // + // Note: The patch routine applies the differences to the source + // database, and if there are any changes, it does a source rebuild. + // The patch routine returns true if anything was modified. + // + void diff(const SourceDB &auth, StreamBuffer *sb); + bool patch(StreamBuffer *sb, DebugCollector *dbc); + + // run_unittests + // + // Run all the lua unit tests. Print any errors to console. If there + // are any errors, exits the program. + // + void run_unittests(); + + // Get/Set code (for unit testing). + // + // These functions are direct getters/setters for values in the source + // database. They are intended only for unit testing. + // + void set(const eng::string &fn, const eng::string &code, int sequence); + eng::string get(const eng::string &fn); + + // Add builtins to the global function registry. + // + static void register_lua_builtins(); + + // Get function documentation. + // + static eng::string function_docs(const LuaCoreStack &LS, LuaSlot slot); + + // Serialize and unserialize a source vector. + // + static void serialize_source(const util::LuaSourceVec &sv, StreamBuffer *sb); + static void deserialize_source(util::LuaSourceVec *sv, StreamBuffer *sb); +}; + +#endif // SOURCE_HPP + + diff --git a/luprex/cpp/core/spookyv2.cpp b/luprex/cpp/core/spookyv2.cpp new file mode 100644 index 00000000..c6f587c8 --- /dev/null +++ b/luprex/cpp/core/spookyv2.cpp @@ -0,0 +1,360 @@ +// Spooky Hash +// A 128-bit noncryptographic hash, for checksums and table lookup +// By Bob Jenkins. Public domain. +// Oct 31 2010: published framework, disclaimer ShortHash isn't right +// Nov 7 2010: disabled ShortHash +// Oct 31 2011: replace End, ShortMix, ShortEnd, enable ShortHash again +// April 10 2012: buffer overflow on platforms without unaligned reads +// July 12 2012: was passing out variables in final to in/out in short +// July 30 2012: I reintroduced the buffer overflow +// August 5 2012: SpookyV2: d = should be d += in short hash, and remove extra mix from long hash + +#include +#include "spookyv2.hpp" + +#define ALLOW_UNALIGNED_READS 1 + +#define INLINE inline + +// number of uint64_t's in internal state +static const size_t sc_numVars = 12; + +// size of the internal state +static const size_t sc_blockSize = sc_numVars*8; + +// size of buffer of unhashed data, in bytes +static const size_t sc_bufSize = 2*sc_blockSize; + +// +// sc_const: a constant which: +// * is not zero +// * is odd +// * is a not-very-regular mix of 1's and 0's +// * does not need any other special mathematical properties +// +static const uint64_t sc_const = 0xdeadbeefdeadbeefLL; + +// +// left rotate a 64-bit value by k bytes +// +static INLINE uint64_t Rot64(uint64_t x, int k) +{ + return (x << k) | (x >> (64 - k)); +} + +// +// This is used if the input is 96 bytes long or longer. +// +// The internal state is fully overwritten every 96 bytes. +// Every input bit appears to cause at least 128 bits of entropy +// before 96 other bytes are combined, when run forward or backward +// For every input bit, +// Two inputs differing in just that input bit +// Where "differ" means xor or subtraction +// And the base value is random +// When run forward or backwards one Mix +// I tried 3 pairs of each; they all differed by at least 212 bits. +// +static INLINE void Mix( + const uint64_t *data, + uint64_t &s0, uint64_t &s1, uint64_t &s2, uint64_t &s3, + uint64_t &s4, uint64_t &s5, uint64_t &s6, uint64_t &s7, + uint64_t &s8, uint64_t &s9, uint64_t &s10,uint64_t &s11) +{ + s0 += data[0]; s2 ^= s10; s11 ^= s0; s0 = Rot64(s0,11); s11 += s1; + s1 += data[1]; s3 ^= s11; s0 ^= s1; s1 = Rot64(s1,32); s0 += s2; + s2 += data[2]; s4 ^= s0; s1 ^= s2; s2 = Rot64(s2,43); s1 += s3; + s3 += data[3]; s5 ^= s1; s2 ^= s3; s3 = Rot64(s3,31); s2 += s4; + s4 += data[4]; s6 ^= s2; s3 ^= s4; s4 = Rot64(s4,17); s3 += s5; + s5 += data[5]; s7 ^= s3; s4 ^= s5; s5 = Rot64(s5,28); s4 += s6; + s6 += data[6]; s8 ^= s4; s5 ^= s6; s6 = Rot64(s6,39); s5 += s7; + s7 += data[7]; s9 ^= s5; s6 ^= s7; s7 = Rot64(s7,57); s6 += s8; + s8 += data[8]; s10 ^= s6; s7 ^= s8; s8 = Rot64(s8,55); s7 += s9; + s9 += data[9]; s11 ^= s7; s8 ^= s9; s9 = Rot64(s9,54); s8 += s10; + s10 += data[10]; s0 ^= s8; s9 ^= s10; s10 = Rot64(s10,22); s9 += s11; + s11 += data[11]; s1 ^= s9; s10 ^= s11; s11 = Rot64(s11,46); s10 += s0; +} + +// +// Mix all 12 inputs together so that h0, h1 are a hash of them all. +// +// For two inputs differing in just the input bits +// Where "differ" means xor or subtraction +// And the base value is random, or a counting value starting at that bit +// The final result will have each bit of h0, h1 flip +// For every input bit, +// with probability 50 +- .3% +// For every pair of input bits, +// with probability 50 +- 3% +// +// This does not rely on the last Mix() call having already mixed some. +// Two iterations was almost good enough for a 64-bit result, but a +// 128-bit result is reported, so End() does three iterations. +// +static INLINE void EndPartial( + uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3, + uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7, + uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11) +{ + h11+= h1; h2 ^= h11; h1 = Rot64(h1,44); + h0 += h2; h3 ^= h0; h2 = Rot64(h2,15); + h1 += h3; h4 ^= h1; h3 = Rot64(h3,34); + h2 += h4; h5 ^= h2; h4 = Rot64(h4,21); + h3 += h5; h6 ^= h3; h5 = Rot64(h5,38); + h4 += h6; h7 ^= h4; h6 = Rot64(h6,33); + h5 += h7; h8 ^= h5; h7 = Rot64(h7,10); + h6 += h8; h9 ^= h6; h8 = Rot64(h8,13); + h7 += h9; h10^= h7; h9 = Rot64(h9,38); + h8 += h10; h11^= h8; h10= Rot64(h10,53); + h9 += h11; h0 ^= h9; h11= Rot64(h11,42); + h10+= h0; h1 ^= h10; h0 = Rot64(h0,54); +} + +static INLINE void End( + const uint64_t *data, + uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3, + uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7, + uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11) +{ + h0 += data[0]; h1 += data[1]; h2 += data[2]; h3 += data[3]; + h4 += data[4]; h5 += data[5]; h6 += data[6]; h7 += data[7]; + h8 += data[8]; h9 += data[9]; h10 += data[10]; h11 += data[11]; + EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); +} + +// +// The goal is for each bit of the input to expand into 128 bits of +// apparent entropy before it is fully overwritten. +// n trials both set and cleared at least m bits of h0 h1 h2 h3 +// n: 2 m: 29 +// n: 3 m: 46 +// n: 4 m: 57 +// n: 5 m: 107 +// n: 6 m: 146 +// n: 7 m: 152 +// when run forwards or backwards +// for all 1-bit and 2-bit diffs +// with diffs defined by either xor or subtraction +// with a base of all zeros plus a counter, or plus another bit, or random +// +static INLINE void ShortMix(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3) +{ + h2 = Rot64(h2,50); h2 += h3; h0 ^= h2; + h3 = Rot64(h3,52); h3 += h0; h1 ^= h3; + h0 = Rot64(h0,30); h0 += h1; h2 ^= h0; + h1 = Rot64(h1,41); h1 += h2; h3 ^= h1; + h2 = Rot64(h2,54); h2 += h3; h0 ^= h2; + h3 = Rot64(h3,48); h3 += h0; h1 ^= h3; + h0 = Rot64(h0,38); h0 += h1; h2 ^= h0; + h1 = Rot64(h1,37); h1 += h2; h3 ^= h1; + h2 = Rot64(h2,62); h2 += h3; h0 ^= h2; + h3 = Rot64(h3,34); h3 += h0; h1 ^= h3; + h0 = Rot64(h0,5); h0 += h1; h2 ^= h0; + h1 = Rot64(h1,36); h1 += h2; h3 ^= h1; +} + +// +// Mix all 4 inputs together so that h0, h1 are a hash of them all. +// +// For two inputs differing in just the input bits +// Where "differ" means xor or subtraction +// And the base value is random, or a counting value starting at that bit +// The final result will have each bit of h0, h1 flip +// For every input bit, +// with probability 50 +- .3% (it is probably better than that) +// For every pair of input bits, +// with probability 50 +- .75% (the worst case is approximately that) +// +static INLINE void ShortEnd(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3) +{ + h3 ^= h2; h2 = Rot64(h2,15); h3 += h2; + h0 ^= h3; h3 = Rot64(h3,52); h0 += h3; + h1 ^= h0; h0 = Rot64(h0,26); h1 += h0; + h2 ^= h1; h1 = Rot64(h1,51); h2 += h1; + h3 ^= h2; h2 = Rot64(h2,28); h3 += h2; + h0 ^= h3; h3 = Rot64(h3,9); h0 += h3; + h1 ^= h0; h0 = Rot64(h0,47); h1 += h0; + h2 ^= h1; h1 = Rot64(h1,54); h2 += h1; + h3 ^= h2; h2 = Rot64(h2,32); h3 += h2; + h0 ^= h3; h3 = Rot64(h3,25); h0 += h3; + h1 ^= h0; h0 = Rot64(h0,63); h1 += h0; +} + +// +// short hash ... it could be used on any message, +// but it's used by Spooky just for short messages. +// +static void Short( + const void *message, + size_t length, + uint64_t *hash1, + uint64_t *hash2) +{ + uint64_t buf[2*sc_numVars]; + union + { + const uint8_t *p8; + uint32_t *p32; + uint64_t *p64; + size_t i; + } u; + + u.p8 = (const uint8_t *)message; + + if (!ALLOW_UNALIGNED_READS && (u.i & 0x7)) + { + memcpy(buf, message, length); + u.p64 = buf; + } + + size_t remainder = length%32; + uint64_t a=*hash1; + uint64_t b=*hash2; + uint64_t c=sc_const; + uint64_t d=sc_const; + + if (length > 15) + { + const uint64_t *end = u.p64 + (length/32)*4; + + // handle all complete sets of 32 bytes + for (; u.p64 < end; u.p64 += 4) + { + c += u.p64[0]; + d += u.p64[1]; + ShortMix(a,b,c,d); + a += u.p64[2]; + b += u.p64[3]; + } + + //Handle the case of 16+ remaining bytes. + if (remainder >= 16) + { + c += u.p64[0]; + d += u.p64[1]; + ShortMix(a,b,c,d); + u.p64 += 2; + remainder -= 16; + } + } + + // Handle the last 0..15 bytes, and its length + d += ((uint64_t)length) << 56; + switch (remainder) + { + case 15: + d += ((uint64_t)u.p8[14]) << 48; + case 14: + d += ((uint64_t)u.p8[13]) << 40; + case 13: + d += ((uint64_t)u.p8[12]) << 32; + case 12: + d += u.p32[2]; + c += u.p64[0]; + break; + case 11: + d += ((uint64_t)u.p8[10]) << 16; + case 10: + d += ((uint64_t)u.p8[9]) << 8; + case 9: + d += (uint64_t)u.p8[8]; + case 8: + c += u.p64[0]; + break; + case 7: + c += ((uint64_t)u.p8[6]) << 48; + case 6: + c += ((uint64_t)u.p8[5]) << 40; + case 5: + c += ((uint64_t)u.p8[4]) << 32; + case 4: + c += u.p32[0]; + break; + case 3: + c += ((uint64_t)u.p8[2]) << 16; + case 2: + c += ((uint64_t)u.p8[1]) << 8; + case 1: + c += (uint64_t)u.p8[0]; + break; + case 0: + c += sc_const; + d += sc_const; + } + ShortEnd(a,b,c,d); + *hash1 = a; + *hash2 = b; +} + + + + +// do the whole hash in one call +void SpookyHash::ChainHash128( + const void *message, + size_t length, + uint64_t *hash1, + uint64_t *hash2) +{ + if ((*hash1 == 0) && (*hash2 == 0)) { + *hash1 = 0x9438478934792837; + *hash2 = 0x8347848738748378; + } + + if (length < sc_bufSize) + { + Short(message, length, hash1, hash2); + return; + } + + uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11; + uint64_t buf[sc_numVars]; + uint64_t *end; + union + { + const uint8_t *p8; + uint64_t *p64; + size_t i; + } u; + size_t remainder; + + h0=h3=h6=h9 = *hash1; + h1=h4=h7=h10 = *hash2; + h2=h5=h8=h11 = sc_const; + + u.p8 = (const uint8_t *)message; + end = u.p64 + (length/sc_blockSize)*sc_numVars; + + // handle all whole sc_blockSize blocks of bytes + if (ALLOW_UNALIGNED_READS || ((u.i & 0x7) == 0)) + { + while (u.p64 < end) + { + Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + u.p64 += sc_numVars; + } + } + else + { + while (u.p64 < end) + { + memcpy(buf, u.p64, sc_blockSize); + Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + u.p64 += sc_numVars; + } + } + + // handle the last partial block of sc_blockSize bytes + remainder = (length - ((const uint8_t *)end-(const uint8_t *)message)); + memcpy(buf, end, remainder); + memset(((uint8_t *)buf)+remainder, 0, sc_blockSize-remainder); + ((uint8_t *)buf)[sc_blockSize-1] = remainder; + + // do some final mixing + End(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + *hash1 = h0; + *hash2 = h1; +} + diff --git a/luprex/cpp/core/spookyv2.hpp b/luprex/cpp/core/spookyv2.hpp new file mode 100644 index 00000000..2b6d7997 --- /dev/null +++ b/luprex/cpp/core/spookyv2.hpp @@ -0,0 +1,83 @@ +// +// SpookyHash: a 128-bit noncryptographic hash function +// By Bob Jenkins, public domain +// Oct 31 2010: alpha, framework + SpookyHash::Mix appears right +// Oct 31 2011: alpha again, Mix only good to 2^^69 but rest appears right +// Dec 31 2011: beta, improved Mix, tested it for 2-bit deltas +// Feb 2 2012: production, same bits as beta +// Feb 5 2012: adjusted definitions of uint* to be more portable +// Mar 30 2012: 3 bytes/cycle, not 4. Alpha was 4 but wasn't thorough enough. +// August 5 2012: SpookyV2 (different results) +// +// Up to 3 bytes/cycle for long messages. Reasonably fast for short messages. +// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit. +// +// This was developed for and tested on 64-bit x86-compatible processors. +// It assumes the processor is little-endian. There is a macro +// controlling whether unaligned reads are allowed (by default they are). +// This should be an equally good hash on big-endian machines, but it will +// compute different results on them than on little-endian machines. +// +// Google's CityHash has similar specs to SpookyHash, and CityHash is faster +// on new Intel boxes. MD4 and MD5 also have similar specs, but they are orders +// of magnitude slower. CRCs are two or more times slower, but unlike +// SpookyHash, they have nice math for combining the CRCs of pieces to form +// the CRCs of wholes. There are also cryptographic hashes, but those are even +// slower than MD5. +// + +#ifndef SPOOKYV2_HPP +#define SPOOKYV2_HPP + +#include +#include +#include +#include + +class SpookyHash +{ +public: + // A hash is two uint64's. + // + using HashValue = std::pair; + + // + // SpookyHash: hash a single message in one call, produce 128-bit output + // + static void ChainHash128( + const void *message, // message to hash + size_t length, // length of message in bytes + uint64_t *hash1, // input seed0, output hash0 + uint64_t *hash2); // input seed1, output hash1 + + + static inline HashValue QkHash128(const void *message, size_t len) { + uint64_t hash1 = 0; + uint64_t hash2 = 0; + ChainHash128(message, len, &hash1, &hash2); + return std::make_pair(hash1, hash2); + } + + static inline HashValue QkHash128(std::string_view v) { + uint64_t hash1 = 0; + uint64_t hash2 = 0; + ChainHash128(v.data(), v.size(), &hash1, &hash2); + return std::make_pair(hash1, hash2); + } + + static inline uint64_t QkHash64(const void *message, size_t len) { + uint64_t hash1 = 0; + uint64_t hash2 = 0; + ChainHash128(message, len, &hash1, &hash2); + return hash1; + } + + static inline uint64_t QkHash64(std::string_view v) { + uint64_t hash1 = 0; + uint64_t hash2 = 0; + ChainHash128(v.data(), v.size(), &hash1, &hash2); + return hash1; + } +}; + +#endif // SPOOKYV2_HPP diff --git a/luprex/cpp/core/streambuffer.cpp b/luprex/cpp/core/streambuffer.cpp new file mode 100644 index 00000000..b3dda74d --- /dev/null +++ b/luprex/cpp/core/streambuffer.cpp @@ -0,0 +1,149 @@ +#include "wrap-string.hpp" + +#include "eng-malloc.hpp" +#include "streambuffer.hpp" +#include "spookyv2.hpp" + +#include +#include + + +int lua_writer_into_streambuffer(lua_State *L, const void* bytes, size_t sz, void* sbv) { + StreamBuffer *sb = (StreamBuffer *)sbv; + memcpy(sb->make_space(sz), bytes, sz); + sb->wrote_space(sz); + return 0; +} + +static bool streq(const char *str, const char *data) { + int len = strlen(str); + return memcmp(str, data, len) == 0; +} + +LuaDefine(unittests_streambuffer, "", "some unit tests") { + // An 11-byte fixed-size stream buffer. + StreamBuffer sb11(11, true); + + // Check the initial state. + LuaAssert(L, sb11.layout_is(0, 0, 11)); + + // Write a few bytes. + sb11.write_bytes("abcdef"); + LuaAssert(L, sb11.layout_is(0, 6, 5)); + + // Try reading some bytes. + LuaAssert(L, streq("abcd", sb11.read_bytes(4))); + LuaAssert(L, sb11.layout_is(4, 2, 5)); + + // Put back two bytes. + sb11.unread_to(2); + LuaAssert(L, sb11.layout_is(2, 4, 5)); + + // Read some more bytes. + LuaAssert(L, streq("cdef", sb11.read_bytes(4))); + LuaAssert(L, sb11.layout_is(6, 0, 5)); + + // Reading bytes now should raise an EOF and should not alter layout + try { + sb11.read_bytes(1); + LuaAssert(L, false && "This should have thrown an exception"); + } catch (const StreamEofOnRead &) {} + LuaAssert(L, sb11.layout_is(6, 0, 5)); + + // Write some more bytes into the stream, forcing a shift-left + sb11.write_bytes("ghijkl"); + LuaAssert(L, sb11.layout_is(0, 6, 5)); + + // Test buffer wrapping a little more. + LuaAssert(L, streq("ghi", sb11.read_bytes(3))); + LuaAssert(L, sb11.layout_is(3, 3, 5)); + sb11.write_bytes("mnopqr"); + LuaAssert(L, sb11.layout_is(0, 9, 2)); + LuaAssert(L, streq("jklmnopqr", sb11.read_bytes(9))); + LuaAssert(L, sb11.layout_is(9, 0, 2)); + + // Test 1-byte integer ops. + sb11.clear(); + for (int i = 0; i < 10; i++) { + sb11.write_int8(i); + sb11.write_int8(i+100); + LuaAssert(L, sb11.read_int8() == i); + LuaAssert(L, sb11.read_int8() == i+100); + } + + // Test 2-byte integer ops. + for (int i = 0; i < 10; i++) { + sb11.write_int16(i); + sb11.write_int16(i+10000); + LuaAssert(L, sb11.read_int16() == i); + LuaAssert(L, sb11.read_int16() == i+10000); + } + + // Test 4-byte integer ops. + for (int i = 0; i < 10; i++) { + sb11.write_int32(i); + sb11.write_int32(i+1000000); + LuaAssert(L, sb11.read_int32() == i); + LuaAssert(L, sb11.read_int32() == i+1000000); + } + + // Test 8-byte integer ops. + for (int i = 0; i < 10; i++) { + sb11.write_int64(i + 1000000); + LuaAssert(L, sb11.read_int64() == i + 1000000); + } + + // Check the write count and read count accumulator ability. + sb11.clear(); + LuaAssert(L, sb11.total_writes() == 0); + LuaAssert(L, sb11.total_reads() == 0); + for (int i = 0; i < 10; i++) { + LuaAssert(L, sb11.total_writes() == i * 8); + sb11.write_int32(i); + sb11.write_int32(i+1000000); + LuaAssert(L, sb11.total_reads() == i * 8); + LuaAssert(L, sb11.read_int32() == i); + LuaAssert(L, sb11.read_int32() == i+1000000); + } + + // Try clearing the buffer. + sb11.clear(); + LuaAssert(L, sb11.layout_is(0, 0, 11)); + + // Write a number then overwrite. + for (int i = 0; i < 2; i++) { + sb11.write_int16(12); + sb11.write_int16(34); + int64_t wc = sb11.total_writes(); + sb11.write_int16(56); + sb11.write_int16(78); + sb11.overwrite_int16(wc, 90); + LuaAssert(L, sb11.read_int16() == 12); + LuaAssert(L, sb11.read_int16() == 90); + LuaAssert(L, sb11.read_int16() == 56); + LuaAssert(L, sb11.read_int16() == 78); + } + + // Try compact string encoding. + sb11.clear(); + sb11.write_string("abc"); + sb11.write_string(""); + sb11.write_string("de"); + LuaAssert(L, sb11.layout_is(0, 8, 3)); + LuaAssert(L, sb11.read_string() == "abc"); + LuaAssert(L, sb11.read_string() == ""); + LuaAssert(L, sb11.read_string() == "de"); + + // Make sure that contents_equal is not obviously broken. + StreamBuffer eqsb1, eqsb2; + eqsb1.write_int32(12); + eqsb1.write_int32(34); + eqsb2.write_int32(34); + LuaAssert(L, !eqsb1.contents_equal(&eqsb2)); + eqsb1.read_int32(); + LuaAssert(L, eqsb1.contents_equal(&eqsb2)); + eqsb1.write_int32(34); + LuaAssert(L, !eqsb1.contents_equal(&eqsb2)); + + return 0; +} diff --git a/luprex/cpp/core/streambuffer.hpp b/luprex/cpp/core/streambuffer.hpp new file mode 100644 index 00000000..8c6ab1e2 --- /dev/null +++ b/luprex/cpp/core/streambuffer.hpp @@ -0,0 +1,311 @@ +////////////////////////////////////////////////////////////// +// +// STREAMBUFFER +// +// Serves as a buffer for buffered I/O operations. Has rather sophisticated +// methods to help serialize and deserialize data. +// +// The semantics of this class contain a lot of subtlety! Please read the +// documentation carefully. +// +// TELLING LINUX TO READ A FILE DESCRIPTOR INTO A STREAMBUFFER +// +// It is possible to read from a linux file descriptor, directly into a stream +// buffer. You should do this, it's very efficient. Here is how you do it: +// +// // With linux read, you have to pick an arbitrary buffer size. +// const int bufsize = 16384; +// +// // Allocate transient space in the streambuffer. +// char *space = streambuffer.make_space(bufsize); +// +// // Call the linux 'read' function. +// ssize_t bytes_read = read(fd, space, bufsize); +// +// // Append the bytes read to the streambuffer. +// streambuffer.wrote_space(bytes_read); +// +// The make_space operation allocates an array of bytes where the data can be +// written, and returns a pointer to that array of bytes. The read operation +// fills some or all of the allocated bytes. Finally, the wrote_space operation +// notifies the StreamBuffer that some of the bytes have been filled with data. +// These bytes are appended to the StreamBuffer. +// +// The pointer returned by 'make_space' is only valid until you mutate the +// StreamBuffer. Therefore, you should call 'make_space', then immediately fill +// the bytes. It is imperative that 'wrote_space' be the first mutator after +// 'make_space.' You should think of 'make_space' followed by 'wrote_space' as +// a single two-phase operation. +// +// THE OVERWRITE_INT METHODS: +// +// These overwrite methods are meant to help deal with this situation: you want +// to write a length followed by some data, but you don't know the length until +// after you've written the data. The workaround: write a dummy length, then +// write the data, and then overwrite the previously-written length with the +// correct length. This is the construction that accomplishes this: +// +// // Write the dummy length, this will get overwritten. +// streambuffer.write_int32(0); +// +// // Write the data, and calculate its length in bytes. +// int64_t write_count_1 = streambuffer.total_writes(); +// write_data(stream); +// int64_t write_count_2 = streambuffer.total_writes(); +// int64_t data_len = write_count_2 - write_count_1; +// +// // Overwrite the previously-written dummy length. +// streambuffer.overwrite_int32(write_count_1, data_len); +// +// Almost all of this is self-explanatory, but the last line is interesting. In +// order to know what part of the buffer to overwrite, overwrite_int uses +// write_count_1 as a pointer into the buffer - it points immedately to the +// right of the integer to overwrite. +// +// OVERWRITE_INT LIMITS +// +// If you use write_int to write an integer into the buffer, you are allowed to +// overwrite that integer UNTIL you do a read from the buffer. Once you do a +// read, it is no longer legal to overwrite ints that you wrote BEFORE the read. +// +// WRITE_STRING STORES THE STRING LENGTH, WRITE_BYTES DOES NOT +// +// write_string writes a string into the buffer and prepends a length. The +// encoding of the length field is designed to be efficient for short strings +// but still capable of encoding long lengths. +// +// write_bytes doesn't store the data length in the buffer. It's just a raw +// write of bytes. +// +// STREAM EXCEPTIONS +// +// If you do a read_int64, but the buffer doesn't contain the necessary 8 bytes, +// it throws a StreamEof exception. In general, during reading, the following +// common situations generate StreamEof or StreamCorruption exceptions: +// +// * not enough bytes to satisfy a 'read' call: StreamEof +// * call read_eof, but the buffer is not empty: StreamCorruption +// * call read_string, but the string is unreasonably long: StreamCorruption +// +// Exceptions are only generated when reading from a stream that contains bad +// data. Any other error generates a full-blown abort. For example, if you try +// to write to a stream that's not open for writing, that's an abort, not an +// exception. Write operations never generate exceptions. +// +// Sometimes, it is convenient to throw StreamCorruption yourself, if you detect +// that the data you've read from a stream is invalid. This can make error +// handling a little cleaner. +// +// READ BYTES POINTER VALIDITY +// +// When you call read_bytes, it returns a pointer to a block of bytes. This +// pointer only remains valid until you do a 'write' into the stream. +// +// UNREADING BYTES +// +// It's possible to 'unread' bytes that you've already read from a stream. This +// makes it possible to read those same bytes again. +// +// A common situation where this might be useful is: you're decoding a message, +// but you discover halfway through the process of decoding the message that you +// haven't received the whole message yet. In that case, it may be desirable to +// unread the partial message, so that you can wait for the rest of the message +// to be received. +// +// Here is the construction that accomplishes this: +// +// // Get the stream's read count before parsing the message. +// size_t read_count_before = streambuffer.total_reads(); +// +// // Parse the message, but if there's an EOF, deal with it: +// try { +// // Parse the message. +// int32_t value1 = streambuffer.read_int32(); +// eng::string value2 = streambuffer.read_string(maxlen); +// int64_t value3 = streambuffer.read_int64(); +// +// // Great! I got the whole message. +// execute_message(value1, value2, value3); +// } catch (StreamEof) { +// // I ran out of bytes. Unread the message. +// streambuffer.unread(read_count_before); +// } +// +// UNREAD LIMITS +// +// If you read bytes from a stream, that data can be 'unread' until you do a +// write. After a write, it is no longer possible to 'unread' data that you +// read before the write. +// +// STREAMBUFFERS THAT DON'T OWN THEIR OWN MEMORY +// +// If you create a streambuffer using this constructor: +// +// StreamBuffer(const char *data, uint64_t len); +// +// This StreamBuffer reads from an external (unowned) block of bytes, which is +// not copied! The StreamBuffer saves the pointer that you passed in. This +// pointer must remain valid until you're done with the StreamBuffer. +// +// A StreamBuffer that reads from an external block of bytes is read-only. +// Attempts to write to this buffer will be caught and will cause an abort. The +// total_writes for such a buffer returns the 'len' value that you initialized +// the buffer with. +// +// NESTED DECODING +// +// Here is an interesting construct: +// +// // Read a message from the stream. +// size_t len = streambuffer.read_int32() +// const char *bytes = streambuffer.read_bytes(len); +// +// // Construct another stream object to decode the message. +// StreamBuffer substream(bytes, len); +// decode(substream); +// +// This is perfectly valid and a potentially convenient way to parse the +// contents of a message. Note that the substream contains a pointer to +// the parent stream's buffer, and therefore, data corruption will occur +// if you mutate the parent stream while reading the substream. +// +// USING A STREAMBUFFER TO READ AN ENTIRE FILE +// +// If you wish to read an entire file and store the file contents in a +// StreamBuffer, you should probe the size of the file, then allocate a +// StreamBuffer of the correct size using this constructor: +// +// StreamBuffer(int64_t size); +// +// Then, you can use 'alloc_space' and 'wrote_space' to read the file into the +// buffer in a single read call. +// +// USING A STREAMBUFFER AS A LUA_WRITER OR LUA_READER +// +// You can use a streambuffer as a lua_Writer, as follows: +// +// lua_dump(L, lua_writer_into_streambuffer, &sb); +// +// Anything written to the lua_writer gets appended to the streambuffer, the +// same as if it had been written using write_bytes. +// +// You can't use streambuffer as a lua_Reader directly, but you can get a +// string_view out of it and then use that to construct a lua_Reader, as +// follows: +// +// LuaStringViewReader svr(mystreambuffer.view()); +// lua_load (L, svr.lua_reader(), svr.lua_reader_userdata()); +// +////////////////////////////////////////////////////////////// + + +#ifndef STREAMBUFFER_HPP +#define STREAMBUFFER_HPP + +#include "wrap-string.hpp" +#include "wrap-sstream.hpp" + +#include +#include +#include + +#include "base-buffer.hpp" +#include "luastack.hpp" +#include "util.hpp" + +class StreamException : public eng::nevernew +{ +public: + virtual char const *what() const { return "General stream exception"; } +}; + +class StreamEofOnRead : public StreamException +{ +public: + virtual char const *what() const { return "Stream ran out of data"; } +}; + +class StreamStringTooLong: public StreamException +{ +public: + virtual char const *what() const { return "Stream contained a string that was too long"; } +}; + +class StreamIntegerTruncated: public StreamException +{ +public: + virtual char const *what() const { return "You truncated an integer when writing to a stream"; } +}; + +class StreamCorruption: public StreamException +{ +public: + virtual char const *what() const { return "Stream Corruption"; } +}; + +using SimpleDynamicValue = SimpleDynamic; + +class StreamBufferCore { +protected: + void *basebuffer_malloc(size_t size) { return eng::malloc(size); } + void basebuffer_free(void *p) { eng::free(p); } + void clear_error_flags() { } + void raise_eof_on_read() { throw StreamEofOnRead(); } + void raise_string_too_long() { throw StreamStringTooLong(); } + void raise_integer_truncated() { throw StreamIntegerTruncated(); } +}; + +class StreamBuffer : public eng::nevernew, public BaseBuffer { +public: + using BaseBuffer::BaseBuffer; + + void write_xyz(const util::XYZ &xyz) { + write_float(xyz.x); + write_float(xyz.y); + write_float(xyz.z); + } + + void write_dxyz(const util::DXYZ &xyz) { + write_double(xyz.x); + write_double(xyz.y); + write_double(xyz.z); + } + + void write_hashvalue(const util::HashValue &h) { + write_uint64(h.first); + write_uint64(h.second); + } + + util::XYZ read_xyz() { + float x = read_float(); + float y = read_float(); + float z = read_float(); + return util::XYZ(x, y, z); + } + + util::DXYZ read_dxyz() { + double x = read_double(); + double y = read_double(); + double z = read_double(); + return util::DXYZ(x, y, z); + } + + util::HashValue read_hashvalue() { + uint64_t f = read_uint64(); + uint64_t s = read_uint64(); + return util::HashValue(f, s); + } + + bool contents_equal(const StreamBuffer *sb) { + return view() == sb->view(); + } + + void copy_into(StreamBuffer *sb) { + sb->write_bytes(view()); + } +}; + +// Use a streambuffer as a lua_writer. +int lua_writer_into_streambuffer(lua_State *L, const void* bytes, size_t sz, void* sb); + +#endif // STREAMBUFFER_HPP diff --git a/luprex/cpp/core/table.cpp b/luprex/cpp/core/table.cpp new file mode 100644 index 00000000..e162c1f6 --- /dev/null +++ b/luprex/cpp/core/table.cpp @@ -0,0 +1,813 @@ + +#include "wrap-string.hpp" + +#include "table.hpp" +#include "source.hpp" + +// A quick check to see if a table appears to be a vector. +// Does not thoroughly verify the vector. Returns the size +// of the vector. +static int check_vector_quick(LuaCoreStack &LS, LuaSlot vector, LuaSlot tmp) { + LS.cktable(vector, "vector"); + int nkeys = LS.nkeys(vector); + if (nkeys > 0) { + LS.rawget(tmp, vector, nkeys); + if (LS.isnil(tmp)) { + luaL_error(LS.state(), "Not a valid vector"); + } + LS.rawget(tmp, vector, nkeys + 1); + if (!LS.isnil(tmp)) { + luaL_error(LS.state(), "Not a valid vector"); + } + } + return nkeys; +} + +bool table_equal(LuaCoreStack &LS, LuaSlot t1, LuaSlot t2) { + lua_State *L = LS.state(); + int top = lua_gettop(L); + LS.cktable(t1, "table1"); + LS.cktable(t2, "table2"); + int nkeys1 = lua_nkeys(L, t1.index()); + int nkeys2 = lua_nkeys(L, t2.index()); + if (nkeys1 != nkeys2) return false; + int total = 0; + lua_pushnil(L); + while (lua_next(L, t1.index()) != 0) { + lua_pushvalue(L, -2); // k v1 k + lua_rawget(L, t2.index()); // k v1 v2 + if (!lua_rawequal(L, -1, -2)) { + lua_settop(L, top); + return false; + } + lua_pop(L, 2); + total += 1; + } + assert(total == nkeys1); + lua_settop(L, top); + return true; +} + +LuaDefine(table_equal, "table1,table2", + "|Return true if two tables contain the same keys and values." + "|" + "|This works on arbitrary tables. Metatables are ignored." + "|") { + LuaArg t1, t2; + LuaRet eql; + LuaDefStack LS(L, t1, t2, eql); + LS.set(eql, table_equal(LS, t1, t2)); + return LS.result(); +} + +LuaDefine(table_isvector, "table", + "|Return true if the table is a valid vector." + "|" + "|A vector is a table that has keys starting with 1, and no gaps." + "|The empty table also counts as a valid vector. This function" + "|scans the entire vector to verify its validity, so it takes O(N)" + "|time. See also table.isvectorq" + "|" + "|The functions vector.isvector and table.isvector are identical." + "|") { + LuaArg table; + LuaRet result; + LuaVar tmp; + LuaDefStack LS(L, table, result, tmp); + if (!LS.istable(table)) { + LS.set(result, false); + return LS.result(); + } + int nkeys = lua_nkeys(L, table.index()); + for (int i = 1; i <= nkeys; i++) { + LS.rawget(tmp, table, i); + if (LS.isnil(tmp)) { + LS.set(result, false); + return LS.result(); + } + } + LS.set(result, true); + return LS.result(); +} + +LuaDefine(table_isvectorq, "table", + "|Return true if the table is probably a valid vector." + "|" + "|A vector is a table that has keys starting with 1, and no gaps." + "|The empty table also counts as a valid vector. This function" + "|does a constant-time heuristic check: it gets the number of keys" + "|in the table, NKEYS. Then it verifies that table[NKEYS] is not" + "|nil and that table[NKEYS+1] is nil. If these things are both" + "|true, the table is very likely a valid vector." + "|" + "|The functions vector.isvectorq and table.isvectorq are identical." + "|") { + LuaArg table; + LuaRet result; + LuaVar tmp; + LuaDefStack LS(L, table, result, tmp); + if (!LS.istable(table)) { + LS.set(result, false); + return LS.result(); + } + int nkeys = LS.nkeys(table); + if (nkeys > 0) { + LS.rawget(tmp, table, nkeys); + if (LS.isnil(tmp)) { + LS.set(result, false); + return LS.result(); + } + LS.rawget(tmp, table, nkeys + 1); + if (!LS.isnil(tmp)) { + LS.set(result, false); + return LS.result(); + } + } + LS.set(result, true); + return LS.result(); +} + +LuaDefineAlias(vector_isvector, table_isvector); + +LuaDefine(vector_removeall, "vector,value", + "|Remove all occurrences of value from vector." + "|" + "|For example, if you remove the number 3 from the vector" + "|{1,2,3,4,5,4,3,2,1} you get {1,2,4,5,4,2,1}." + "|" + "|Returns true if it removed something, false otherwise." + "|") { + LuaArg vector, value; + LuaRet result; + LuaVar tmp; + LuaDefStack LS(L, vector, value, result, tmp); + int nkeys = check_vector_quick(LS, vector, tmp); + int dest = 1; + for (int i = 1; i <= nkeys; i++) { + LS.rawget(tmp, vector, i); + if (LS.isnil(tmp)) { + luaL_error(L, "not a valid vector"); + return LS.result(); + } + if (!LS.rawequal(tmp, value)) { + if (dest < i) { + LS.rawset(vector, dest, tmp); + } + dest += 1; + } + } + LS.set(result, (dest < nkeys)); + while (dest <= nkeys) { + LS.rawset(vector, dest, LuaNil); + dest += 1; + } + return LS.result(); +} + +LuaDefine(vector_push, "vector,value", + "|Push a value onto the end of a vector." + "|" + "|Argument must be a valid vector. Appends the value to" + "|the end of the vector." + "|") { + LuaArg vector, value; + LuaVar tmp; + LuaDefStack LS(L, vector, value, tmp); + int nkeys = check_vector_quick(LS, vector, tmp); + LS.rawset(vector, nkeys + 1, value); + return LS.result(); +} + +LuaDefine(vector_pop, "vector,value", + "|Pop a value from the end of a vector." + "|" + "|Argument must be a valid vector. Returns the last value" + "|from the vector. If the vector is empty, returns nil." + "|") { + LuaArg vector; + LuaRet value; + LuaVar tmp; + LuaDefStack LS(L, vector, value, tmp); + int nkeys = check_vector_quick(LS, vector, tmp); + if (nkeys == 0) { + LS.set(value, LuaNil); + } else { + LS.rawget(value, vector, nkeys); + LS.rawset(vector, nkeys, LuaNil); + } + return LS.result(); +} + +LuaDefine(vector_find, "vector,value", + "|Find the first occurence of value in vector." + "|" + "|Argument must be a valid vector. Returns the index of the" + "|first occurrence of value in vector. If the value is not" + "|found, returns nil." + "|" + "|Searching for 'nil' in a vector is explicitly disallowed, since" + "|a valid vector cannot contain nil." + "|") { + LuaArg vector, value; + LuaRet index; + LuaVar tmp; + LuaDefStack LS(L, vector, value, index, tmp); + int nkeys = check_vector_quick(LS, vector, tmp); + if (LS.isnil(value)) { + luaL_error(L, "cannot search for nil in a vector"); + return 0; + } + for (int i = 1; i <= nkeys; i++) { + LS.rawget(tmp, vector, i); + if (LS.rawequal(tmp, value)) { + LS.set(index, i); + return LS.result(); + } + } + LS.set(index, LuaNil); + return LS.result(); +} + +LuaDefine(table_empty, "table", + "|Return true if the table is empty." + "|" + "|Empty means the table has no key-value pairs stored." + "|Metatables and metavalues are ignored." + "|") { + luaL_checktype(L, -1, LUA_TTABLE); + int total = lua_nkeys(L, -1); + lua_pushboolean(L, (total == 0)?1:0); + return 1; +} + +LuaDefine(table_count, "table", + "|Return the number of key-value pairs in the table." + "|" + "|Returns the number of key-value pairs that have been" + "|explicitly stored. Metatables and metavalues don't count." + "|") { + luaL_checktype(L, -1, LUA_TTABLE); + int total = lua_nkeys(L, -1); + lua_pushinteger(L, total); + return 1; +} + +LuaDefine(table_clear, "table,metaflag", + "|Clear the table key-value pairs, and optionally the metatable" + "|" + "|This removes all key-value pairs from the table. If the metaflag" + "|is set, it also removes the metatable. If you try to clear the" + "|metatable when the metatable is locked, this will throw an error." + "|") { + LuaArg tab, clearmeta; + LuaVar metatable, metafield; + LuaDefStack LS(L, tab, clearmeta, metatable, metafield); + LS.cktable(tab, "table"); + if (LS.ckboolean(clearmeta)) { + LS.getmetatable(metatable, tab); + if (LS.istable(metatable)) { + LS.rawget(metafield, metatable, "__metatable"); + if (!LS.isnil(metafield)) { + luaL_error(L, "Cannot clear metatable."); + return LS.result(); + } + } + LS.cleartable(tab, true); + } else { + LS.cleartable(tab, false); + } + return LS.result(); +} + +LuaDefine(table_getflagbits, "table", "get the table's flag bits (debugging only)") { + LuaArg tab; + LuaRet bits; + LuaDefStack LS(L, tab, bits); + uint16_t ubits = lua_getflagbits(L, tab.index()); + LS.set(bits, ubits); + return LS.result(); +} + +LuaDefine(table_setflagbits, "table,bits", "set the table's flag bits (debugging only)") { + LuaArg tab, bits; + LuaDefStack LS(L, tab, bits); + uint16_t ubits = LS.ckinteger(bits); + lua_setflagbits(L, tab.index(), ubits); + return LS.result(); +} + +///////////////////////////////////////////////////////////// +// +// Deque operators. +// +///////////////////////////////////////////////////////////// + +#define DEQUE_LEFT 1 +#define DEQUE_FILL 2 +#define DEQUE_MAX 3 +#define DEQUE_BASE 4 + +void deque_checktype(lua_State *L, int slot, int type) { + if (lua_type(L, slot) != type) { + luaL_error(L, "object is not a valid deque"); + } +} + +void deque_get_info(lua_State *L, int deque, int *left, int *fill, int *max) { + luaL_checktype(L, deque, LUA_TTABLE); + if (left) { + lua_rawgeti(L, deque, DEQUE_LEFT); + deque_checktype(L, -1, LUA_TNUMBER); + *left = lua_tointeger(L, -1); + lua_pop(L, 1); + } + if (fill) { + lua_rawgeti(L, deque, DEQUE_FILL); + deque_checktype(L, -1, LUA_TNUMBER); + *fill = lua_tointeger(L, -1); + lua_pop(L, 1); + } + if (max) { + lua_rawgeti(L, deque, DEQUE_MAX); + deque_checktype(L, -1, LUA_TNUMBER); + *max = lua_tointeger(L, -1); + lua_pop(L, 1); + } +} + +void deque_put_info(lua_State *L, int deque, int *left, int *fill, int *max) { + if (left) { + lua_pushinteger(L, *left); + lua_rawseti(L, deque, DEQUE_LEFT); + } + if (fill) { + lua_pushinteger(L, *fill); + lua_rawseti(L, deque, DEQUE_FILL); + } + if (max) { + lua_pushinteger(L, *max); + lua_rawseti(L, deque, DEQUE_MAX); + } +} + +int deque_make_room(lua_State *L, int deque, int left, int fill, int max) { + if (fill == max) { + for (int i = 0; i < left; i++) { + lua_rawgeti(L, deque, DEQUE_BASE + i); + lua_rawseti(L, deque, DEQUE_BASE + i + max); + lua_pushinteger(L, 0); + lua_rawseti(L, deque, DEQUE_BASE + i); + } + for (int i = left; i < max; i++) { + lua_pushinteger(L, 0); + lua_rawseti(L, deque, DEQUE_BASE + i + max); + } + max *= 2; + lua_pushinteger(L, max); + lua_rawseti(L, deque, DEQUE_MAX); + } + return max; +} + +LuaDefine(deque_create, "", "create a deque") { + LuaRet rdeque; + LuaVar classobj; + LuaDefStack LS(L, rdeque, classobj); + const int imax = 4; + eng::string err = LS.getclass(classobj, "deque"); + if (err != "") { + luaL_error(L, "Class deque has been corrupted"); + } + LS.createtable(rdeque, DEQUE_BASE + imax - 1, 0); + LS.rawset(rdeque, DEQUE_LEFT, 0); + LS.rawset(rdeque, DEQUE_FILL, 0); + LS.rawset(rdeque, DEQUE_MAX, imax); + for (int i = 0; i < imax; i++) { + LS.rawset(rdeque, DEQUE_BASE + i, 0); + } + LS.setmetatable(rdeque, classobj); + return LS.result(); +} + +LuaDefine(deque_pushl, "deque,value", "push onto the left end of a deque") { + LuaArg deque, elt; + LuaDefStack LS(L, deque, elt); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + max = deque_make_room(L, deque.index(), left, fill, max); + int target = (left - 1) & (max-1); + LS.rawset(deque, DEQUE_BASE + target, elt); + fill += 1; + left = (left - 1) & (max - 1); + deque_put_info(L, deque.index(), &left, &fill, NULL); + return LS.result(); +} + +LuaDefine(deque_pushr, "deque,value", "push onto the right end of a deque") { + LuaArg deque, elt; + LuaDefStack LS(L, deque, elt); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + max = deque_make_room(L, deque.index(), left, fill, max); + int target = (left + fill) & (max-1); + LS.rawset(deque, DEQUE_BASE + target, elt); + fill += 1; + deque_put_info(L, deque.index(), NULL, &fill, NULL); + return LS.result(); +} + +LuaDefine(deque_popl, "deque", "pop the left end of a deque") { + LuaArg deque; + LuaRet result; + LuaDefStack LS(L, deque, result); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + if (fill == 0) { + LS.set(result, LuaNil); + return LS.result(); + } + LS.rawget(result, deque, DEQUE_BASE + left); + LS.rawset(deque, DEQUE_BASE + left, 0); + left = (left + 1) & (max - 1); + fill -= 1; + deque_put_info(L, deque.index(), &left, &fill, NULL); + return LS.result(); +} + +LuaDefine(deque_popr, "deque", "pop the right end of a deque") { + LuaArg deque; + LuaRet result; + LuaDefStack LS(L, deque, result); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + if (fill == 0) { + LS.set(result, LuaNil); + return LS.result(); + } + int target = (left + fill - 1) & (max - 1); + LS.rawget(result, deque, DEQUE_BASE + target); + LS.rawset(deque, DEQUE_BASE + target, 0); + fill -= 1; + deque_put_info(L, deque.index(), NULL, &fill, NULL); + return LS.result(); +} + +LuaDefine(deque_nthl, "deque,n", "return the nth item from the left end of a deque") { + LuaArg deque, nn; + LuaRet result; + LuaDefStack LS(L, deque, nn, result); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + int n = LS.ckint(nn); + if ((n < 1) || (n > fill)) { + LS.set(result, LuaNil); + return LS.result(); + } + int target = (left + n - 1) & (max - 1); + LS.rawget(result, deque, DEQUE_BASE + target); + return LS.result(); +} + +LuaDefine(deque_nthr, "deque,n", "return the nth item from the right end of a deque") { + LuaArg deque, nn; + LuaRet result; + LuaDefStack LS(L, deque, nn, result); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + int n = LS.ckint(nn); + if ((n < 1) || (n > fill)) { + LS.set(result, LuaNil); + return LS.result(); + } + int target = (left + fill - n) & (max - 1); + LS.rawget(result, deque, DEQUE_BASE + target); + return LS.result(); +} + +LuaDefine(deque_setl, "deque,n,value", "set the nth item from the left end of a deque") { + LuaArg deque, nn, val; + LuaDefStack LS(L, deque, nn, val); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + int n = LS.ckint(nn); + if ((n < 1) || (n > fill)) { + luaL_error(L, "invalid index"); + return LS.result(); + } + int target = (left + n - 1) & (max - 1); + LS.rawset(deque, DEQUE_BASE + target, val); + return LS.result(); +} + +LuaDefine(deque_setr, "deque,n,value", "set the nth item from the right end of a deque") { + LuaArg deque, nn, val; + LuaDefStack LS(L, deque, nn, val); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + int n = LS.ckint(nn); + if ((n < 1) || (n > fill)) { + luaL_error(L, "invalid index"); + return LS.result(); + } + int target = (left + fill - n) & (max - 1); + LS.rawset(deque, DEQUE_BASE + target, val); + return LS.result(); +} + +LuaDefine(deque_findl, "deque,value", "find the first occurence of value in deque, starting from left") { + LuaArg deque, val; + LuaRet pos; + LuaVar check; + LuaDefStack LS(L, deque, val, pos, check); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + for (int i = 0; i < fill; i++) { + int index = (left + i) & (max - 1); + LS.rawget(check, deque, DEQUE_BASE + index); + if (LS.rawequal(check, val)) { + LS.set(pos, i + 1); + return LS.result(); + } + } + LS.set(pos, LuaNil); + return LS.result(); +} + +LuaDefine(deque_findr, "deque,value", "find the first occurrence of value in deque, starting from right") { + LuaArg deque, val; + LuaRet pos; + LuaVar check; + LuaDefStack LS(L, deque, val, pos, check); + int left, fill, max; + deque_get_info(L, deque.index(), &left, &fill, &max); + int base = left + fill - 1; + for (int i = 0; i < fill; i++) { + int index = (base - i) & (max - 1); + LS.rawget(check, deque, DEQUE_BASE + index); + if (LS.rawequal(check, val)) { + LS.set(pos, i + 1); + return LS.result(); + } + } + LS.set(pos, LuaNil); + return LS.result(); +} + +LuaDefine(deque_size, "deque", "return the number of items in the deque") { + LuaArg deque; + LuaRet size; + LuaDefStack LS(L, deque, size); + LS.cktable(deque, "deque"); + LS.rawget(size, deque, DEQUE_FILL); + if (!LS.tryinteger(size)) { + luaL_error(L, "deque has been corrupted"); + } + return LS.result(); +} + + +///////////////////////////////////////////////////////////// +// +// table_getpairs +// +// Given a table, return all the pairs in the table as a sequence. +// The resulting sequence contains a 1 in the first position, +// followed by the pairs, like so: +// +// sortedpairs({a=1,b=2,c=3}) => {1,"a",1,"b",2,"c",3} +// +// Note that this function is not directly exposed to lua. +// It's exposed only through the 'sortedpairs' iterator. +// +///////////////////////////////////////////////////////////// + +static void pushkey (lua_State *L, int tab, int i) { + lua_rawgeti(L, tab, i*2); +} + +static void push2keys (lua_State *L, int tab, int i, int j) { + lua_rawgeti(L, tab, i*2); + lua_rawgeti(L, tab, j*2); +} + +static void pop2keys (lua_State *L, int tab, int i, int j) { + lua_rawseti(L, tab, i*2); + lua_rawseti(L, tab, j*2); +} + +static void swap2values (lua_State *L, int tab, int i, int j) { + lua_rawgeti(L, tab, i*2+1); + lua_rawgeti(L, tab, j*2+1); + lua_rawseti(L, tab, i*2+1); + lua_rawseti(L, tab, j*2+1); +} + +static void auxsort (lua_State *L, int tab, int l, int u) { + while (l < u) { /* for tail recursion */ + int i, j; + /* sort elements a[l], a[(l+u)/2] and a[u] */ + push2keys(L, tab, l, u); + if (lua_genlt(L, -1, -2)) { + pop2keys(L, tab, l, u); + swap2values(L, tab, l, u); + } else { + lua_pop(L, 2); + } + if (u - l == 1) break; /* only 2 elements */ + i = (l + u) / 2; + push2keys(L, tab, i, l); + if (lua_genlt(L, -2, -1)) { + pop2keys(L, tab, i, l); + swap2values(L, tab, i, l); + } else { + lua_pop(L, 1); /* remove a[l] */ + pushkey(L, tab, u); + if (lua_genlt(L, -1, -2)) { + pop2keys(L, tab, i, u); + swap2values(L, tab, i, u); + } else { + lua_pop(L, 2); + } + } + if (u - l == 2) break; /* only 3 elements */ + /* put the pivot value on top of the stack and keep it there */ + pushkey(L, tab, i); + /* move the pivot from i to u-1 */ + lua_pushvalue(L, -1); + pushkey(L, tab, u-1); + pop2keys(L, tab, i, u-1); + swap2values(L, tab, i, u-1); + /* a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2 */ + i = l; + j = u - 1; + for (;;) { /* invariant: a[l..i] <= P <= a[j..u] */ + /* repeat ++i until a[i] >= P */ + while (pushkey(L, tab, ++i), lua_genlt(L, -1, -2)) { + if (i >= u) luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[i] */ + } + /* repeat --j until a[j] <= P */ + while (pushkey(L, tab, --j), lua_genlt(L, -3, -1)) { + if (j <= l) luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[j] */ + } + if (j < i) { + lua_pop(L, 3); /* pop pivot, a[i], a[j] */ + break; + } + pop2keys(L, tab, i, j); + swap2values(L, tab, i, j); + } + push2keys(L, tab, u-1, i); + pop2keys(L, tab, u-1, i); + swap2values(L, tab, u-1, i); + /* a[l..i-1] <= a[i] == P <= a[i+1..u] */ + /* adjust so that smaller half is in [j..i] and larger one in [l..u] */ + if (i - l < u - i) { + j = l; + i = i - 1; + l = i + 2; + } else { + j = i + 1; + i = u; + u = j - 2; + } + auxsort(L, tab, j, i); /* call recursively the smaller one */ + } /* repeat the routine for the larger one */ +} + +bool table_getpairs(LuaCoreStack &LS0, LuaSlot tab, LuaSlot pairs, bool sort) { + lua_State *L = LS0.state(); + LuaVar key, value; + LuaExtStack LS(L, key, value); + bool sorted = true; + // Create the table, store the initial 1. + int total = lua_nkeys(L, tab.index()); + LS.createtable(pairs, total * 2 + 1, 0); + LS.rawset(pairs, 1, 1); + // Transfer the pairs into the sequence. + lua_pushnil(L); + int offset = 2; + while (lua_next(L, tab.index()) != 0) { + int ktype = lua_type(L, -2); + if (ktype != LUA_TNUMBER && ktype != LUA_TSTRING && ktype != LUA_TBOOLEAN) { + sorted = false; + } + lua_pushvalue(L, -2); // K V K + lua_rawseti(L, pairs.index(), offset++); + lua_rawseti(L, pairs.index(), offset++); + } + if (sort) { + auxsort(L, pairs.index(), 1, total); + } + return sorted; +} + +///////////////////////////////////////////////////////////// +// +// Given a sortedpairs vector, return the (key, value) pairs +// one by one. The first element of the vector is used as a +// counter to keep track of our position. +// +///////////////////////////////////////////////////////////// + +LuaDefine(table_nextsortedpair, "sortedpairs,dummy", "next function used by sortedpairs") { + if (lua_gettop(L) < 2) { + luaL_error(L, "Not enough arguments to nextpair"); + } + luaL_checktype(L, 1, LUA_TTABLE); + lua_rawgeti(L, 1, 1); + lua_Integer i = luaL_checkinteger(L, -1); + lua_pop(L, 1); + lua_pushinteger(L, i+1); + lua_rawseti(L, 1, 1); + lua_rawgeti(L, 1, i*2); + if (lua_isnil(L, -1)) { + return 1; + } else { + lua_rawgeti(L, 1, i*2+1); + return 2; + } +} + +LuaDefine(table_sortedpairs, "table", + "|Iterate over table, sorting all keys" + "|" + "|Some keys can't be sorted. For example, you can use a closure" + "|as a table key. If you try to iterate over a table containing" + "|a non-sortable key, the error 'Cannot sort the table keys' will" + "|be generated." + "|" + "|See doc(genlt) for information about the sort order." + "|" + "|") { + LuaArg tab; + LuaRet closure, rtab, key; + LuaDefStack LS(L, tab, closure, rtab, key); + bool sorted = table_getpairs(LS, tab, rtab, true); + if (!sorted) { + luaL_error(L, "Cannot sort the table keys"); + } + LS.set(closure, lfn_table_nextsortedpair); + LS.set(key, LuaNil); + return LS.result(); +} + +LuaDefine(table_semisortedpairs, "table", + "|Iterate over table, sorting all the keys that can be sorted." + "|" + "|Some keys can't be sorted. For example, you can use a closure" + "|as a table key. If you try to iterate over a table containing" + "|a non-sortable key, the non-sortable elements will appear at" + "|the end of the iteration, after all the sortable elements. The" + "|non-sortable elements will be in an arbitrary order." + "|" + "|See doc(genlt) for information about the sort order." + "|") { + LuaArg tab; + LuaRet closure, rtab, key; + LuaDefStack LS(L, tab, closure, rtab, key); + table_getpairs(LS, tab, rtab, true); + LS.set(closure, lfn_table_nextsortedpair); + LS.set(key, LuaNil); + return LS.result(); +} + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + +#define LUA_NUMTAGS 9 + +LuaDefine(genlt, "obj1,obj2", + "|Generalized less-than function" + "|" + "|This comparison function can compare any two objects. The" + "|return value is as follows:" + "|" + "|* Numbers are compared in the obvious numeric manner." + "|* Strings are compared alphabetically." + "|* Booleans are compared with false being less than true." + "|* Tables are all considered equal to other tables." + "|* Functions are all considered equal to other functions." + "|* Coroutines are all considered equal to other coroutines." + "|" + "|* Numbers are less than strings." + "|* Strings are less than booleans." + "|* Booleans are less than functions." + "|* Functions are less than coroutines." + "|* Coroutines are less than tables." + "|") { + LuaArg o1,o2; + LuaRet lt; + LuaDefStack LS(L, o1, o2, lt); + int ltf = lua_genlt(L, o1.index(), o2.index()); + LS.set(lt, ltf ? true:false); + return LS.result(); +} diff --git a/luprex/cpp/core/table.hpp b/luprex/cpp/core/table.hpp new file mode 100644 index 00000000..3198dbd3 --- /dev/null +++ b/luprex/cpp/core/table.hpp @@ -0,0 +1,29 @@ +//////////////////////////////////////////////////////////// +// +// This module contains a library of lua functions +// for manipulating tables. It also provides a library +// of useful classes based on tables. +// +//////////////////////////////////////////////////////////// + + +#ifndef TABLE_HPP +#define TABLE_HPP + +#include "luastack.hpp" + +// table_equal +// +// True if two tables contain the same key/value pairs. +// +bool table_equal(LuaCoreStack &LS0, LuaSlot tab1, LuaSlot tab2); + +// table_getpairs +// +// Get a table containing the key-value pairs in tab. Optionally sort +// the pairs. Return true if all keys were sortable. +// +bool table_getpairs(LuaCoreStack &LS0, LuaSlot tab, LuaSlot pairs, bool sort); + + +#endif // TABLE_HPP diff --git a/luprex/cpp/core/traceback.cpp b/luprex/cpp/core/traceback.cpp new file mode 100644 index 00000000..4842c04b --- /dev/null +++ b/luprex/cpp/core/traceback.cpp @@ -0,0 +1,97 @@ +#include "traceback.hpp" + +#include +#include + +#define TRACEBACK_LEVELS1 12 +#define TRACEBACK_LEVELS2 10 + +// Call this with the error message on top of the stack. +// The error message is replaced with a traceback. +// +int traceback_coroutine(lua_State *L) { + lua_checkstack(L, 20); + int top = lua_gettop(L); + + // Convert message to a string + if (!lua_tostring(L, top)) { + luaL_callmeta(L, top, "__tostring"); + // If callmeta didn't produce exactly one string, clear the stack + // and push "unknown error" + if ((lua_gettop(L) == top + 1) && (lua_tostring(L, -1))) { + lua_remove(L, top); + } else { + lua_settop(L, top - 1); + lua_pushstring(L, "unknown error"); + } + } + + // Append the traceback. + lua_Debug ar; + int firstpart = 1; + bool any = false; + for (int level = 0; lua_getstack(L, level, &ar); level++) { + if (level > TRACEBACK_LEVELS1 && firstpart) { + /* no more than `LEVELS2' more levels? */ + if (!lua_getstack(L, level + TRACEBACK_LEVELS2, &ar)) + level--; /* keep going */ + else { + lua_pushliteral(L, "\n\t..."); /* too many levels */ + while (lua_getstack(L, level + TRACEBACK_LEVELS2, &ar)) /* find last levels */ + level++; + } + firstpart = 0; + continue; + } + lua_getinfo(L, "Snl", &ar); + if ((!any) && (*ar.what == 'C') && (ar.name != 0)) { + if (strcmp(ar.name, "__newindex") == 0) continue; + } + if ((ar.currentline > 0) || (*ar.namewhat != 0) || (*ar.what != 'C')) { + any = true; + lua_pushliteral(L, "\n\t"); + lua_pushfstring(L, "%s:", ar.short_src); + if (ar.currentline > 0) + lua_pushfstring(L, "%d:", ar.currentline); + if (*ar.namewhat != '\0') /* is there a name? */ + lua_pushfstring(L, " in function " LUA_QS, ar.name); + else { + if (*ar.what == 'm') /* main? */ + lua_pushfstring(L, " in main chunk"); + else if (*ar.what == 'C' || *ar.what == 't') + lua_pushliteral(L, " ?"); /* C function or tail call */ + else + lua_pushfstring(L, " in function <%s:%d>", + ar.short_src, ar.linedefined); + } + if (1 + lua_gettop(L) - top > 5) { + lua_concat(L, 1 + lua_gettop(L) - top); + } + } + } + lua_pushstring(L, "\n"); + if (1 + lua_gettop(L) - top > 1) { + lua_concat(L, 1 + lua_gettop(L) - top); + } + return 1; +} + + +eng::string traceback_pcall(lua_State *L, int narg, int nret) { + int status; + int base = lua_gettop(L) - narg; /* function index */ + lua_pushcfunction(L, traceback_coroutine); /* push traceback function */ + lua_insert(L, base); /* put it under chunk and args */ + status = lua_pcall(L, narg, nret, base); + lua_remove(L, base); /* remove traceback function */ + if (status != LUA_OK) { + const char *msg = lua_tostring(L, -1); + if ((msg == NULL) || (msg[0] == 0)) { + msg = "unknown error"; + } + eng::string result = msg; + assert(result != "attempt to yield from outside a coroutine"); + return result; + } + return ""; +} diff --git a/luprex/cpp/core/traceback.hpp b/luprex/cpp/core/traceback.hpp new file mode 100644 index 00000000..978b9a5b --- /dev/null +++ b/luprex/cpp/core/traceback.hpp @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////// +// +// TRACEBACK ROUTINES +// +// The following routines are meant to help produce good-quality +// tracebacks from errors in lua code. +// +///////////////////////////////////////////////////////////////// + + +#ifndef TRACEBACK_HPP +#define TRACEBACK_HPP + +#include "luastack.hpp" + +// traceback_coroutine +// +// Given a coroutine which contains an error message, replace +// the error message with a full traceback. Always returns 1. +// +int traceback_coroutine(lua_State *L); + +// traceback_pcall +// +// Similar to lua_pcall, except that it automatically supplies +// traceback_coroutine as a message handler. It also automatically +// returns any error message. Returns empty string if there's +// no error. Also checks for a yield inside a pcall, which is +// not allowed, and does an assert-fail in that case. +// +eng::string traceback_pcall(lua_State *L, int narg, int nret); + +#endif // TRACEBACK_HPP diff --git a/luprex/cpp/core/util.cpp b/luprex/cpp/core/util.cpp new file mode 100644 index 00000000..f489a6a7 --- /dev/null +++ b/luprex/cpp/core/util.cpp @@ -0,0 +1,985 @@ +#include "wrap-string.hpp" +#include "wrap-vector.hpp" +#include "util.hpp" +#include "fast-float.hpp" +#include "luastack.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace sv { + +bool case_insensitive_eq(string_view s1, string_view s2) { + if (s1.size() != s2.size()) return false; + for (int i = 0; i < int(s1.size()); i++) { + char c1 = s1[i]; + char c2 = s2[i]; + if (ascii_isupper(c1)) c1 += 'a'-'A'; + if (ascii_isupper(c2)) c2 += 'a'-'A'; + if (c1 != c2) return false; + } + return true; +} + +bool valid_int64(string_view value) { + int64_t result; + const char *last = value.data() + value.size(); + auto r = std::from_chars(value.data(), last, result, 10); + if (r.ec != std::errc()) return false; + if (r.ptr != last) return false; + return true; +} + +bool valid_hex64(string_view value) { + int64_t result; + const char *last = value.data() + value.size(); + auto r = std::from_chars(value.data(), last, result, 16); + if (r.ec != std::errc()) return false; + if (r.ptr != last) return false; + return true; +} + +bool valid_double(string_view value) { + double result; + const char *last = value.data() + value.size(); + auto r = fast_float::from_chars(value.data(), last, result); + if (r.ec != std::errc()) return false; + if (r.ptr != last) return false; + return true; +} + +bool valid_hostname(string_view value) { + bool start_word = true; + bool last_dash = false; + for (char c : value) { + if (c == '.') { + if (start_word) return false; + if (last_dash) return false; + start_word = true; + last_dash = false; + } else if (c == '-') { + if (start_word) return false; + start_word = false; + last_dash = true; + } else if (ascii_isalnum(c)) { + start_word = false; + last_dash = false; + } else { + return false; + } + } + if (start_word || last_dash) return false; + return true; +} + +int64_t to_int64(string_view value, int64_t errval) { + int64_t result; + const char *p = value.data(); + const char *last = p + value.size(); + if ((p < last) && (*p == '+')) p++; + auto r = std::from_chars(p, last, result, 10); + if (r.ec != std::errc()) return errval; + if (r.ptr != last) return errval; + return result; +} + +uint64_t to_hex64(string_view value, uint64_t errval) { + uint64_t result; + if (sv::zfront(value) == '-') return errval; + const char *last = value.data() + value.size(); + auto r = std::from_chars(value.data(), last, result, 16); + if (r.ec != std::errc()) return errval; + if (r.ptr != last) return errval; + return result; +} + +double to_double(string_view value, double errval) { + double result; + const char *last = value.data() + value.size(); + auto r = fast_float::from_chars(value.data(), last, result); + if (r.ec != std::errc()) return errval; + if (r.ptr != last) return errval; + return result; +} + +string_view ltrim(string_view v) { + while ((!v.empty()) && (ascii_isspace(v.front()))) { + v.remove_prefix(1); + } + return v; +} + +string_view rtrim(string_view v) { + while ((!v.empty()) && (ascii_isspace(v.back()))) { + v.remove_suffix(1); + } + return v; +} + +string_view trim(string_view v) { + while ((!v.empty()) && (ascii_isspace(v.front()))) { + v.remove_prefix(1); + } + while ((!v.empty()) && (ascii_isspace(v.back()))) { + v.remove_suffix(1); + } + return v; +} + +string_view ltrim(string_view v, char c) { + while ((!v.empty()) && (v.front() == c)) { + v.remove_prefix(1); + } + return v; +} + +string_view rtrim(string_view v, char c) { + while ((!v.empty()) && (v.back() == c)) { + v.remove_suffix(1); + } + return v; +} + +string_view trim(string_view v, char c) { + while ((!v.empty()) && (v.front() == c)) { + v.remove_prefix(1); + } + while ((!v.empty()) && (v.back() == c)) { + v.remove_suffix(1); + } + return v; +} + +bool has_prefix(string_view s, string_view prefix) { + return 0 == s.compare(0, prefix.size(), prefix); +} + +bool has_suffix(string_view s, string_view suffix) { + if (s.length() >= suffix.length()) { + return (0 == s.compare (s.length() - suffix.length(), suffix.length(), suffix)); + } else { + return false; + } +} + +int common_prefix_length(string_view a, string_view b) { + int minlen = std::min(a.size(), b.size()); + for (int i = 0; i < minlen; i++) { + if (a[i] != b[i]) return i; + } + return minlen; +} + +bool is_lua_id(string_view str) { + if (str.size() == 0) return false; + char c=str[0]; + if ((!ascii_isalpha(c)) && (c!='_')) return false; + for (int i = 1; i < int(str.size()); i++) { + char c = str[i]; + if ((!ascii_isalnum(c)) && (c!='_')) return false; + } + return true; +} + +bool is_lua_classname(string_view s) { + if (s == "_G") return false; + return is_lua_id(s); +} + +bool is_lua_comment(string_view s) { + int start = 0; + while ((start < int(s.size())) && ((s[start]==' ') || (s[start]=='\t'))) start++; + return s.substr(start, 2) == "--"; +} + +string_view read_to_sep(string_view &source, char sep) { + size_t pos = source.find(sep); + string_view result; + if (pos == string_view::npos) { + result = source; + source = string_view(); + } else { + result = source.substr(0, pos); + source = source.substr(pos + 1); + } + return result; +} + +string_view read_to_line(string_view &source) { + size_t pos = source.find('\n'); + string_view result; + if (pos == string_view::npos) { + result = source; + source = string_view(); + } else { + result = source.substr(0, pos); + source = source.substr(pos + 1); + } + if ((!result.empty()) && (result.back() == '\r')) { + result.remove_suffix(1); + } + return result; +} + +bool read_prefix(string_view &source, string_view prefix) { + if (0 == source.compare(0, prefix.size(), prefix)) { + source.remove_prefix(prefix.size()); + return true; + } else { + return false; + } +} + +string_view read_to_space(string_view &source) { + size_t pos1 = 0; + while ((pos1 < source.size()) && (!ascii_isspace(source[pos1]))) { + pos1 += 1; + } + string_view result = source.substr(0, pos1); + if (pos1 == source.size()) { + source = string_view(); + return result; + } + size_t pos2 = pos1 + 1; + while ((pos2 < source.size()) && (ascii_isspace(source[pos2]))) { + pos2 += 1; + } + source = source.substr(pos2); + return result; +} + +string_view read_nbytes(string_view &source, int nbytes) { + if (nbytes < 0) nbytes = 0; + if (nbytes > int(source.size())) nbytes = source.size(); + string_view result = source.substr(0, nbytes); + source = source.substr(nbytes); + return result; +} + +string_view read_ascii_identifier(string_view &source) { + size_t len = 0; + if ((len < source.size()) && (sv::ascii_isalpha(source[len]))) { + len += 1; + while ((len < source.size()) && (sv::ascii_isalnum(source[len]))) { + len += 1; + } + } + string_view result = source.substr(0, len); + source.remove_prefix(len); + return result; +} + +std::string_view read_number(string_view &source, bool plus, bool minus, bool dec, bool exp) { + const char *p = source.data(); + const char *l = p + source.size(); + if (p == l) return source.substr(0, 0); + char sign = *p; + if (sign == '+') { + if (!plus) return source.substr(0, 0); + p++; + } + if (sign == '-') { + if (!minus) return source.substr(0, 0); + p++; + } + if (p == l) return source.substr(0, 0); + bool have_digits = false; + while ((p < l) && (ascii_isdigit(*p))) { + have_digits = true; + p++; + } + if ((p < l) && dec && (*p == '.')) { + p++; + while ((p < l) && (ascii_isdigit(*p))) { + have_digits = true; + p++; + } + } + if (!have_digits) return source.substr(0, 0); + if ((p < l) && exp && ((*p == 'e')||(*p == 'E'))) { + p++; + if ((p < l) && ((*p == '+') || (*p == '-'))) { + p++; + } + bool have_exp = false; + while ((p < l) && (ascii_isdigit(*p))) { + have_exp = true; + p++; + } + if (!have_exp) return source.substr(0, 0); + } + string_view result = source.substr(0, p - source.data()); + source.remove_prefix(result.size()); + return result; +} + +int32_t read_ascii_char(string_view &source) { + if (source.empty()) return -1; + int32_t result = source.front(); + source.remove_prefix(1); + return result; +} + +int32_t read_codepoint_utf8(std::string_view &source) { + size_t size = source.size(); + if (size == 0) return -1; + + const unsigned char *bytes = (const unsigned char *)source.data(); + int codepoint; + size_t seqlen; + if ((bytes[0] & 0x80) == 0x00) { + // U+0000 to U+007F + codepoint = (bytes[0] & 0x7F); + seqlen = 1; + } else if ((bytes[0] & 0xE0) == 0xC0) { + // U+0080 to U+07FF + codepoint = (bytes[0] & 0x1F); + seqlen = 2; + } else if ((bytes[0] & 0xF0) == 0xE0) { + // U+0800 to U+FFFF + codepoint = (bytes[0] & 0x0F); + seqlen = 3; + } else if ((bytes[0] & 0xF8) == 0xF0) { + // U+10000 to U+10FFFF + codepoint = (bytes[0] & 0x07); + seqlen = 4; + } else { + // Bad character. return invalid CP. + return -2; + } + + if (seqlen > size) { + return -1; + } + + for (size_t i = 1; i < seqlen; ++i) { + if ((bytes[i] & 0xC0) != 0x80) { + // Bad character. return invalid CP. + return -2; + } + codepoint = (codepoint << 6) | (bytes[i] & 0x3F); + } + + if ((codepoint > 0x10FFFF) || + ((codepoint >= 0xD800) && (codepoint <= 0xDFFF)) || + ((codepoint <= 0x007F) && (seqlen != 1)) || + ((codepoint >= 0x0080) && (codepoint <= 0x07FF) && (seqlen != 2)) || + ((codepoint >= 0x0800) && (codepoint <= 0xFFFF) && (seqlen != 3)) || + ((codepoint >= 0x10000) && (codepoint <= 0x1FFFFF) && (seqlen != 4))) { + // Bad character. return invalid CP. + return -2; + } + + source.remove_prefix(seqlen); + return codepoint; +} + +bool valid_utf8(string_view s) { + while (!s.empty()) { + int32_t codepoint = read_codepoint_utf8(s); + if (codepoint < 0) return false; + } + return true; +} + +bool valid_number(string_view s, bool plus, bool minus, bool dec, bool exp) { + read_number(s, plus, minus, dec, exp); + return s.empty(); +} + +} // namespace sv + + +namespace util { + +eng::string ascii_tolower(std::string_view s) { + eng::string mod(s); + for (int i = 0; i < int(mod.size()); i++) { + if (sv::ascii_isupper(mod[i])) { + mod[i] += 'a' - 'A'; + } + } + return mod; +} + +eng::string ascii_toupper(std::string_view s) { + eng::string mod(s); + for (int i = 0; i < int(mod.size()); i++) { + if (sv::ascii_islower(mod[i])) { + mod[i] += 'A' - 'a'; + } + } + return mod; +} + +void quote_string(const eng::string &s, std::ostream *os) { + bool anysq = false; + bool anydq = false; + for (char c : s) { + if (c == '\'') anysq = true; + if (c == '"') anydq = true; + } + bool usesinglequote = (!anysq)||(anydq); + (*os) << (usesinglequote ? '\'' : '"'); + std::string_view str(s); + while (!str.empty()) { + unsigned char c0 = (unsigned char)(str[0]); + int cp = sv::read_codepoint_utf8(str); + if (cp < 0) { + (*os) << "\\" << dec.width(3).fill('0').val(c0); + str.remove_prefix(1); + } else if (cp < 32) { + c0 = ((unsigned char)cp); + switch (c0) { + case '\n': (*os) << "\\n"; break; + case '\t': (*os) << "\\t"; break; + case '\r': (*os) << "\\r"; break; + case '\b': (*os) << "\\b"; break; + default: + (*os) << "\\" << dec.width(3).fill('0').val(c0); + break; + } + } else if (cp == '"') { + (*os) << (usesinglequote ? "\"" : "\\\""); + } else if (cp == '\'') { + (*os) << (usesinglequote ? "\\'" : "'"); + } else if (cp == '\\') { + (*os) << "\\\\"; + } else { + write_codepoint_utf8(cp, os); + } + } + (*os) << (usesinglequote ? '\'' : '"'); +} + +void base64_encode(std::string_view str, std::ostream *oss) { + const char *encode_tab = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const char *s = str.data(); + size_t size = str.size(); + for (size_t i = 0; i < size; i += 3) { + uint32_t block = ((unsigned char)(s[i])) << 16; + if (i + 1 < size) block |= ((unsigned char)(s[i + 1])) << 8; + if (i + 2 < size) block |= ((unsigned char)(s[i + 2])); + (*oss) << encode_tab[(block>>18)&0x3F]; + (*oss) << encode_tab[(block>>12)&0x3F]; + (*oss) << ((i + 1 < size) ? encode_tab[(block>>6)&0x3F] : '='); + (*oss) << ((i + 2 < size) ? encode_tab[(block>>0)&0x3F] : '='); + } +} + +bool base64_decode(std::string_view str, std::ostream *oss) { + uint32_t chunk = 0; + int fill = 0; + int skip = 0; + bool clean = true; + for (int i = 0; i < int(str.size()); i++) { + char c = str[i]; + uint32_t value; + + if ((c >= 'A') && (c <= 'Z')) value = c - 'A'; + else if ((c >= 'a') && (c <= 'z')) value = c - 'a' + 26; + else if ((c >= '0') && (c <= '9')) value = c - '0' + 52; + else if (c == '+') value = 62; + else if (c == '/') value = 63; + else if (c == '=') { value = 0; skip ++; } + else { clean=false; continue; } + + chunk = (chunk << 6) | value; + fill ++; + if (fill == 4) { + oss->put((chunk>>16) & 0xFF); + if (skip < 2) oss->put((chunk>>8) & 0xFF); + if (skip < 1) oss->put(chunk & 0xFF); + chunk = 0; fill = 0; skip = 0; + } + } + if (fill != 0) clean = false; + return clean; +} + +IdVector id_vector_create(int64_t id1, int64_t id2, int64_t id3, int64_t id4) { + IdVector result; + if (id1 >= 0) result.push_back(id1); + if (id2 >= 0) result.push_back(id2); + if (id3 >= 0) result.push_back(id3); + if (id4 >= 0) result.push_back(id4); + return result; +} + +void print_id_vector(const IdVector &idv, std::ostream *os) { + bool first = true; + for (int64_t id : idv) { + if (!first) (*os) << ","; + (*os) << id; + first = false; + } +} + +void print_id_vector(const std::vector &idv, std::ostream *os) { + bool first = true; + for (int64_t id : idv) { + if (!first) (*os) << ","; + (*os) << id; + first = false; + } +} + +eng::string id_vector_debug_string(const IdVector &idv) { + eng::ostringstream oss; + print_id_vector(idv, &oss); + return oss.str(); +} + +IdVector sort_union_id_vectors(const IdVector &v1, const IdVector &v2) { + IdVector result(v1.size() + v2.size()); + int next = 0; + for (int64_t id : v1) result[next++] = id; + for (int64_t id : v2) result[next++] = id; + std::sort(result.begin(), result.end()); + int64_t prev = -1; + int64_t count = 0; + for (int64_t id : result) { + if (id != prev) { + prev = id; + result[count++] = id; + } + } + result.resize(count); + return result; +} + +HashValue hash_string(std::string_view s) { + uint64_t hash1 = 0; + uint64_t hash2 = 0; + SpookyHash::ChainHash128(s.data(), s.size(), &hash1, &hash2); + return util::HashValue(hash1, hash2); +} + +HashValue hash_string(HashValue prev, std::string_view s) { + uint64_t hash1 = prev.first; + uint64_t hash2 = prev.second; + SpookyHash::ChainHash128(s.data(), s.size(), &hash1, &hash2); + return util::HashValue(hash1, hash2); +} + +HashValue hash_id_vector(const IdVector &idv) { + uint64_t hash1 = 0; + uint64_t hash2 = 0; + SpookyHash::ChainHash128(&idv[0], idv.size() * sizeof(int64_t), &hash1, &hash2); + return util::HashValue(hash1, hash2); +} + +eng::string hash_to_hex(const HashValue &hv) { + eng::ostringstream oss; + oss << hex64.val(hv.first) << hex64.val(hv.second); + return oss.str(); +} +static inline uint64_t Rot64(uint64_t x, int k) +{ + return (x << k) | (x >> (64 - k)); +} + +uint64_t hash_ints(uint64_t a, uint64_t b, uint64_t c, uint64_t d) { + uint64_t h0 = c ^ 0xc548cebf3714dbb9; + uint64_t h1 = d ^ 0xd23a7edd44383f8d; + uint64_t h2 = a ^ 0x7356f92e4b154df7; + uint64_t h3 = b ^ 0x55ce09295766838d; + + h3 ^= h2; h2 = Rot64(h2,15); h3 += h2; + h0 ^= h3; h3 = Rot64(h3,52); h0 += h3; + h1 ^= h0; h0 = Rot64(h0,26); h1 += h0; + h2 ^= h1; h1 = Rot64(h1,51); h2 += h1; + h3 ^= h2; h2 = Rot64(h2,28); h3 += h2; + h0 ^= h3; h3 = Rot64(h3,9); h0 += h3; + h1 ^= h0; h0 = Rot64(h0,47); h1 += h0; + h2 ^= h1; h1 = Rot64(h1,54); h2 += h1; + h3 ^= h2; h2 = Rot64(h2,32); h3 += h2; + h0 ^= h3; h3 = Rot64(h3,25); h0 += h3; + h1 ^= h0; h0 = Rot64(h0,63); h1 += h0; + + return h1; +} + +double hash_to_double(uint64_t hash) { + return (hash >> (64-53)) * 0x1p-53; +} + +StringVec split(const eng::string &s, char sep) { + StringVec result; + int start = 0; + for (int i = 0; i < int(s.size()); i++) { + if (s[i] == sep) { + result.push_back(s.substr(start, i-start)); + start = i+1; + } + } + if (start < int(s.size())) { + result.push_back(s.substr(start)); + } + return result; +} + +static eng::string substr_nocr(const eng::string &s, int start, int len) { + if ((len > 0) && (s[start + len - 1] == '\r')) { + len -= 1; + } + return s.substr(start, len); +} + +StringVec split_lines(const eng::string &s) { + StringVec result; + int start = 0; + for (int i = 0; i < int(s.size()); i++) { + if (s[i]=='\n') { + result.push_back(substr_nocr(s, start, i-start)); + start = i + 1; + } + } + if (start < int(s.size())) { + result.push_back(substr_nocr(s, start, s.size()-start)); + } + return result; +} + +StringVec split_docstring(const eng::string &s) { + StringVec result; + int start = 0; + for (int i = 0; i < int(s.size()); i++) { + if (s[i]=='|') { + int len = i-start; + if ((len > 0)||(start > 0)) { + result.push_back(s.substr(start, i-start)); + } + start = i + 1; + } + } + if (start < int(s.size())) { + result.push_back(s.substr(start, s.size()-start)); + } + return result; +} + +eng::string join(const StringVec &strs, const eng::string &sep) { + if (strs.empty()) return ""; + eng::ostringstream oss; + oss << strs[0]; + for (int i = 1; i < int(strs.size()); i++) { + oss << sep << strs[i]; + } + return oss.str(); +} + +eng::string repeat_string(const eng::string &a, int n) { + int len = a.size(); + eng::string result(len * n, ' '); + for (int i = 0; i < n; i++) { + for (int j = 0; j < len; j++) { + result[i*len + j] = a[j]; + } + } + return result; +} + +eng::string tolower(eng::string input) { + for (int i = 0; i < int(input.size()); i++) { + input[i] = std::tolower(input[i]); + } + return input; +} + +eng::string toupper(eng::string input) { + for (int i = 0; i < int(input.size()); i++) { + input[i] = std::toupper(input[i]); + } + return input; +} + +static int buffer_codepoint_utf8(char32_t scp, char *buffer) { + uint32_t cp = (uint32_t)scp; + unsigned char *c = (unsigned char *)buffer; + if (cp < 0) { + return 0; + } + else if (cp <= 0x7F) { + c[0] = cp; + return 1; + } + else if (cp <= 0x7FF) { + c[0] = (cp>>6)+192; + c[1] = (cp&63)+128; + return 2; + } + else if (cp <= 0xFFFF) { + if ((cp >= 0xD800) && (cp <= 0xDFFF)) { + return 0; + } + c[0] = (cp>>12)+224; + c[1] = ((cp>>6)&63)+128; + c[2] = (cp&63)+128; + return 3; + } + else if (cp <= 0x10FFFF) { + c[0] = (cp>>18)+240; + c[1] = ((cp>>12)&63)+128; + c[2] = ((cp>>6)&63)+128; + c[3] = (cp&63)+128; + return 4; + } else { + return 0; + } +} + +eng::string get_codepoint_utf8(uint32_t cp) { + char buffer[4]; + int len = buffer_codepoint_utf8(cp, buffer); + return eng::string(buffer, len); +} + +bool write_codepoint_utf8(int32_t cp, std::ostream *s) { + char buffer[4]; + int len = buffer_codepoint_utf8(cp, buffer); + (*s) << std::string_view(buffer, len); + return (len > 0); +} + +double distance_squared(double x1, double y1, double x2, double y2) { + double dx = x1 - x2; + double dy = y1 - y2; + return dx*dx + dy*dy; +} + +LuaSourcePtr make_lua_source(const eng::string &code) { + LuaSourcePtr result(new LuaSourceVec); + eng::string fn = "file.lua"; + result->push_back(std::make_pair(fn, code)); + return result; +} + + + +void (*dprint_hook)(const char *oneline, size_t size); + +void hook_dprint(void (*func)(const char *oneline, size_t size)) { + dprint_hook = func; +} + +void dprintview(std::string_view view) { + // Drop the final newline, if any. We're going + // to automatically end with a newline and we don't + // want to double up. + if ((view.size() > 0) && (view.back() == '\n')) { + view.remove_suffix(1); + } + + // Chop it up into lines and call the hook one line + // at a time. + const char *buffer = view.data(); + const char *base = buffer; + for (int i = 0; i < int(view.size()); i++) { + if ((buffer[i] == '\n') || (buffer[i] == 0)) { + size_t sz = buffer + i - base; + if (dprint_hook == nullptr) { + fwrite(base, 1, sz, stderr); + fwrite("\n", 1, 1, stderr); + } else { + dprint_hook(base, sz); + } + base = buffer + i + 1; + } + } + + // Output the final line. + size_t sz = buffer + view.size() - base; + if (sz > 0) { + if (dprint_hook == nullptr) { + fwrite(base, 1, sz, stderr); + fwrite("\n", 1, 1, stderr); + } else { + dprint_hook(base, sz); + } + } + + // If we're sending to stderr, flush. If not, then + // the hook routine is responsible for flushing its own + // output. + if (dprint_hook == nullptr) { + fflush(stderr); + } +} + +void dprintf(const char *format, ...) { + char buffer[256]; + va_list args; + va_start (args, format); + int n = vsnprintf(buffer, 256, format, args); + if (n <= 255) { + dprintview(std::string_view(buffer, n)); + } else { + char *lbuffer = (char *)malloc(n + 1); + vsnprintf(lbuffer, n+1, format, args); + dprintview(std::string_view(lbuffer, n)); + free(lbuffer); + } +} + +} // namespace util + + +static std::string_view read_number_x(const char *p, bool plus, bool minus, bool dec, bool exp) { + std::string_view source = p; + return sv::read_number(source, plus, minus, dec, exp); +} + +LuaDefine(unittests_util, "", "some unit tests") { + // Test valid_hostname + LuaAssert(L, sv::valid_hostname("foo123")); + LuaAssert(L, !sv::valid_hostname("foo%123")); + LuaAssert(L, sv::valid_hostname("foo-bar")); + LuaAssert(L, !sv::valid_hostname("-foo")); + LuaAssert(L, !sv::valid_hostname("foo-")); + LuaAssert(L, sv::valid_hostname("foo.bar.baz")); + LuaAssert(L, sv::valid_hostname("foo-bar.baz")); + LuaAssert(L, !sv::valid_hostname("foo-bar-.baz")); + LuaAssert(L, !sv::valid_hostname("foo.-bar-baz")); + + // test str_to_int64, str_to_double + LuaAssert(L, sv::to_int64("123") == 123); + LuaAssert(L, sv::to_int64("123.4") == INT64_MAX); + LuaAssert(L, sv::to_int64("12ab") == INT64_MAX); + LuaAssert(L, sv::to_int64("") == INT64_MAX); + LuaAssert(L, sv::to_double("123.5") == 123.5); + LuaAssert(L, std::isnan(sv::to_double("12ab"))); + LuaAssert(L, std::isnan(sv::to_double(""))); + + // Test trim, ltrim, rtrim + LuaAssert(L, sv::ltrim(" foo ") == "foo "); + LuaAssert(L, sv::rtrim(" foo ") == " foo"); + LuaAssert(L, sv::trim(" foo ") == "foo"); + LuaAssert(L, sv::trim("foo") == "foo"); + LuaAssert(L, sv::trim("") == ""); + LuaAssert(L, sv::ltrim("**foo**", '*') == "foo**"); + LuaAssert(L, sv::rtrim("**foo**", '*') == "**foo"); + LuaAssert(L, sv::trim("**foo**", '*') == "foo"); + LuaAssert(L, sv::trim("foo", '*') == "foo"); + LuaAssert(L, sv::trim("", '*') == ""); + + // Test read_to_line + std::string_view v = "foo\nbar\r\nbaz"; + + std::string_view v1 = sv::read_to_line(v); + LuaAssertStrEq(L, v1, "foo"); + LuaAssertStrEq(L, v, "bar\r\nbaz"); + + std::string_view v2 = sv::read_to_line(v); + LuaAssertStrEq(L, v2, "bar"); + LuaAssertStrEq(L, v, "baz"); + + std::string_view v3 = sv::read_to_line(v); + LuaAssertStrEq(L, v3, "baz"); + LuaAssert(L, sv::isnull(v)); + + // Test read_to_space + v = "foo bar baz"; + + std::string_view s1 = sv::read_to_space(v); + LuaAssertStrEq(L, s1, "foo"); + LuaAssertStrEq(L, v, "bar baz"); + + std::string_view s2 = sv::read_to_space(v); + LuaAssertStrEq(L, s2, "bar"); + LuaAssertStrEq(L, v, "baz"); + + std::string_view s3 = sv::read_to_space(v); + LuaAssertStrEq(L, s3, "baz"); + LuaAssert(L, sv::isnull(v)); + + // Test the unioning of ID vectors. + util::IdVector idv1,idv2; + idv1.push_back(1); + idv1.push_back(6); + idv1.push_back(4); + idv2.push_back(5); + idv2.push_back(1); + idv2.push_back(6); + util::IdVector joined = util::sort_union_id_vectors(idv1, idv2); + LuaAssert(L, joined.size() == 4); + LuaAssert(L, joined[0] == 1); + LuaAssert(L, joined[1] == 4); + LuaAssert(L, joined[2] == 5); + LuaAssert(L, joined[3] == 6); + + // Test the string split routine. + util::StringVec sv1 = util::split("foo,bar,baz", ','); + LuaAssert(L, sv1.size() == 3); + LuaAssert(L, sv1[0] == "foo"); + LuaAssert(L, sv1[1] == "bar"); + LuaAssert(L, sv1[2] == "baz"); + util::StringVec sv2 = util::split(",foo,,bar", ','); + LuaAssert(L, sv2.size() == 4); + LuaAssert(L, sv2[0]==""); + LuaAssert(L, sv2[1]=="foo"); + LuaAssert(L, sv2[2]==""); + LuaAssert(L, sv2[3]=="bar"); + + // Test the split_lines routine. + util::StringVec sv3 = util::split_lines("foo\n\nbar\r\nbaz\r\n\r\n"); + LuaAssert(L, sv3.size() == 5); + LuaAssert(L, sv3[0] == "foo"); + LuaAssert(L, sv3[1] == ""); + LuaAssert(L, sv3[2] == "bar"); + LuaAssert(L, sv3[3] == "baz"); + LuaAssert(L, sv3[4] == ""); + + // Test the repeat string routine. + LuaAssertStrEq(L, util::repeat_string("abc", 3), "abcabcabc"); + + // test toupper and tolower + LuaAssert(L, util::toupper("fooBar") == "FOOBAR"); + LuaAssert(L, util::tolower("fooBar") == "foobar"); + + // Test distance_squared + LuaAssert(L, util::distance_squared(1, 1, 5, 4) == 25.0); + LuaAssert(L, util::distance_squared(5, 4, 1, 1) == 25.0); + + // Test XYZ. + util::XYZ xyza(3,4,5), xyzb(3,4,5), xyzc(3,4,6); + LuaAssert(L, xyza.x == 3); + LuaAssert(L, xyza.y == 4); + LuaAssert(L, xyza.z == 5); + LuaAssert(L, xyza == xyzb); + LuaAssert(L, xyza != xyzc); + LuaAssert(L, xyza.debug_string() == "(3,4,5)"); + + // Test hash_to_string + LuaAssertStrEq(L, util::hash_to_hex(util::HashValue(0x1234,0x789a)), + "0000000000001234000000000000789a"); + + // Test hash_to_double + LuaAssert(L, util::hash_to_double(0x1000000000000000) == 1.0/16.0); + LuaAssert(L, util::hash_to_double(0x7000000000000000) == 7.0/16.0); + LuaAssert(L, util::hash_to_double(0xF000000000000000) == 15.0/16.0); + + // Test read_number allowing everything. + LuaAssert(L, read_number_x("123x", true, true, true, true) == "123"); + LuaAssert(L, read_number_x("123.3x", true, true, true, true) == "123.3"); + LuaAssert(L, read_number_x("123.x", true, true, true, true) == "123."); + LuaAssert(L, read_number_x("123..x", true, true, true, true) == "123."); + LuaAssert(L, read_number_x("-123x", true, true, true, true) == "-123"); + LuaAssert(L, read_number_x("+123x", true, true, true, true) == "+123"); + LuaAssert(L, read_number_x("+-123x", true, true, true, true) == ""); + LuaAssert(L, read_number_x("-123.02e05x", true, true, true, true) == "-123.02e05"); + LuaAssert(L, read_number_x("-123e-5x", true, true, true, true) == "-123e-5"); + LuaAssert(L, read_number_x("-123e+5x", true, true, true, true) == "-123e+5"); + LuaAssert(L, read_number_x("-123e+x", true, true, true, true) == ""); + + return 0; +} + diff --git a/luprex/cpp/core/util.hpp b/luprex/cpp/core/util.hpp new file mode 100644 index 00000000..66ce05e2 --- /dev/null +++ b/luprex/cpp/core/util.hpp @@ -0,0 +1,504 @@ +/////////////////////////////////////////////////////////////////////// +// +// NAMESPACE SV +// +// * Operate on string_view or just characters. +// * Do not allocate memory. +// * Do not copy strings. +// +// NAMESPACE UTIL +// +// * General purpose utility functions. +// * Sort of a catch-all. +// +/////////////////////////////////////////////////////////////////////// + +#ifndef UTIL_HPP +#define UTIL_HPP + +#include "wrap-string.hpp" +#include "wrap-set.hpp" +#include "wrap-map.hpp" +#include "wrap-vector.hpp" +#include "wrap-sstream.hpp" +#include +#include +#include +#include +#include +#include +#include +// #include +#include +#include "spookyv2.hpp" + + +enum WorldType { + WORLD_TYPE_MASTER = 1, + WORLD_TYPE_PREDICTIVE = 2, +}; + + +namespace sv { + +// Bring this into our namespace. +using string_view = std::string_view; + +// Test character class, ignoring current locale and unicode issues. +inline bool ascii_isupper(char c) { return (c >= 'A') && (c <= 'Z'); } +inline bool ascii_islower(char c) { return (c >= 'a') && (c <= 'z'); } +inline bool ascii_isdigit(char c) { return (c >= '0') && (c <= '9'); } +inline bool ascii_isalpha(char c) { return ascii_isupper(c) || ascii_islower(c); } +inline bool ascii_isalnum(char c) { return ascii_isalpha(c) || ascii_isdigit(c); } +inline bool ascii_isspace(char c) { return (c==' ')||(c=='\t')||(c=='\r')||(c=='\n')||(c=='\f')||(c=='\v'); } + +// Check for the null string_view +// +// Note that the null string view is an empty string, +// but not every empty string is the null string view. +// +inline bool isnull(string_view v) { return v.data() == nullptr; } + +// Return true if the two strings are equal, ignoring case. +// +bool case_insensitive_eq(std::string_view s1, std::string_view s2); + +// Check if numbers can be parsed as int64/double +bool valid_double(string_view v); +bool valid_int64(string_view v); +bool valid_hex64(string_view v); + +// Check if a hostname is a valid DNS (ascii) hostname. +bool valid_hostname(string_view v); + +// Convert strings to numbers. Returns errval on failure. +// +// The integer parser accepts a sequence of digits, +// with or without a + or - sign. The hex parser +// does not allow a + or - sign. For both the int64 +// and hex64 parser, it is a failure if the number +// does not fit in 64 bits. The double parser does +// not accept the strings 'nan' or 'inf'. +// +double to_double(string_view v, double errval = std::numeric_limits::quiet_NaN()); +int64_t to_int64(string_view v, int64_t errval = std::numeric_limits::max()); +uint64_t to_hex64(string_view v, uint64_t errval = std::numeric_limits::max()); + +// Trim whitspace from a string_view. +string_view ltrim(string_view v); +string_view rtrim(string_view v); +string_view trim(string_view v); + +// Trim specific character (all occurrences) from a string_view. +string_view ltrim(string_view v, char c); +string_view rtrim(string_view v, char c); +string_view trim(string_view v, char c); + +// Return true if the string has the specified prefix or suffix. +bool has_prefix(string_view s, string_view prefix); +bool has_suffix(string_view s, string_view suffix); + +// Return the length of the common prefix of A and B. +int common_prefix_length(string_view a, string_view b); + +// Return true if the string is a lua identifier. +bool is_lua_id(string_view s); + +// Return true if the string is a valid lua classname. +bool is_lua_classname(string_view s); + +// Return true if the line of code is a lua comment. +bool is_lua_comment(string_view s); + +// Return the first character, but if the view is empty, +// return zero. +inline char zfront(string_view &s) { + return s.empty() ? char(0) : s.front(); +} + +// Read from a string_view until separator is reached. +// +// If the separator appears in the source, returns everything +// before the separator, and updates the source to everything +// after the separator. +// +// If the separator doesn't appear in the source, returns +// the entire source, and replaces source with the null string_view. +// +string_view read_to_sep(string_view &source, char sep); + +// Read from a string_view until newline is reached. +// +// If there's a line-break in the source (newline or CRLF), +// returns the text before the line-break, and updates the +// source to the text after the line-break. +// +// If there's no line-break in the source, returns the entire source, +// and updates source to the null string_view. +// +string_view read_to_line(string_view &source); + +// Read a prefix string from a string_view. +// +// Returns false if the string view doesn't start with +// the specified prefix. +// +bool read_prefix(string_view &source, string_view prefix); + +// Read from a string_view until whitespace is reached. +// +// If there's any whitespace in the source, returns the text +// before the whitespace, and update the source to the text +// after the whitespace. +// +// If there's no whitespace in the source, returns the entire +// source, and updates the source to the null string_view. +// +string_view read_to_space(string_view &source); + +// Read up to nbytes from a string_view. +// +string_view read_nbytes(string_view &source, int nbytes); + +// Read an ascii identifier from a string_view +// +// If there's no valid identifier, returns empty string. +// +string_view read_ascii_identifier(string_view &source); + +// Read a number from a string view +// +// This is basically a regex pattern matching routine +// hardwired with the regex for numbers. You must +// specify which of the following parts of the regex +// are allowed or not: +// +// * plus sign +// * minus sign +// * decimal point +// * scientific notation exponents +// +// Returns the number as a string_view. There is +// no guarantee that the number is small enough to +// fit into any particular number of bits. This +// always uses base 10. +// +std::string_view read_number(string_view &source, bool plus, bool minus, bool dec, bool exp); + +// Read an ascii character from a string. +// +// Returns -1 if the string is empty. +// +int32_t read_ascii_char(string_view &source); + +// Read a UTF8 codepoint from a string_view. +// +// If the string_view is empty, returns -1 and doesn't update +// the string_view. +// +// If the string_view contains an unfinished but possibly valid +// codepoint, returns -1 and doesn't update the string_view. +// +// If the next thing in the string_view is an invalid codepoint, +// returns -2 and doesn't update the string_view. +// +int32_t read_codepoint_utf8(string_view &source); + +// Return true if the string is valid utf-8. +bool valid_utf8(string_view s); + +// Return true if the number conforms to the spec. +// See read_number for more information. +// +bool valid_number(string_view v, bool plus, bool minus, bool dec, bool exp); + +} // namespace sv + +namespace util { + +enum MessageType { + MSG_NULL, + MSG_DIFF, + MSG_ACK, + MSG_INVOKE, +}; + +// Note: IdVector is weird in that it deliberately uses std::vector +// instead of eng::vector. This is because we want plane scans +// to not touch the engine heap. +// +using IdVector = std::vector; + +using StringVec = eng::vector; +using StringPair = std::pair; +using StringSet = eng::set; +using LuaSourceVec = eng::vector; +using LuaSourcePtr = std::unique_ptr; +using HashValue = std::pair; +using SharedStdString = std::shared_ptr; +using SharedStdStringVec = std::vector; + +// Ascii uppercase and lowercase. +eng::string ascii_tolower(std::string_view c); +eng::string ascii_toupper(std::string_view c); + +// Output a string to a stream using Lua string escaping and quoting. +void quote_string(const eng::string &str, std::ostream *os); + +// base64 encode. +void base64_encode(std::string_view v, std::ostream *oss); + +// base64 decode. +// +// Returns true if the base64 was 'clean' base64, as +// opposed to base64 with extraneous characters. +// +bool base64_decode(std::string_view v, std::ostream *oss); + +// ID vector quick create. +IdVector id_vector_create(int64_t id1=-1, int64_t id2=-1, int64_t id3=-1, int64_t id4=-1); + +// Print an ID vector to a stream. +void print_id_vector(const IdVector &idv, std::ostream *os); +void print_id_vector(const std::vector &idv, std::ostream *os); + +// ID vector debug string. +eng::string id_vector_debug_string(const IdVector &idv); + +// Unions and sorts two ID vectors. +IdVector sort_union_id_vectors(const IdVector &v1, const IdVector &v2); + +// Get a 128-bit hashvalue for a string. +HashValue hash_string(std::string_view str); + +// Get a 128-bit hashvalue for a string, with a previous value. +HashValue hash_string(HashValue prev, std::string_view str); + +// Get a 128-bit hashvalue for an ID vector. +HashValue hash_id_vector(const IdVector &idv); + +// Convert a 128-bit hash to a hexadecimal string. +eng::string hash_to_hex(const HashValue &hash); + +// Hash four integers together to 64 bits. +// This is a good hash, but not cryptographically good. +uint64_t hash_ints(uint64_t n1, uint64_t n2, uint64_t n3, uint64_t n4); + +// Hash a single 64-bit integer. +// This is a good hash, but not cryptographically good. +// Published by David Stafford in his article 'Better Bit Mixing'. +inline uint64_t hash_int(uint64_t x) { + x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); + x = x ^ (x >> 31); + return x; +} + +// Convert a 64-bit hash value into a floating point number between 0 and 1. +double hash_to_double(uint64_t hash); + +// Split a string into multiple strings +StringVec split(const eng::string &s, char sep); + +// Split a string into multiple strings using \r or \n +StringVec split_lines(const eng::string &s); + +// Split a string into multiple lines using |, remove any leading blank line. +StringVec split_docstring(const eng::string &s); + +// Join multiple strings into one string +eng::string join(const StringVec &strs, eng::string sep); + +// Return N repetitions of string A +eng::string repeat_string(const eng::string &a, int n); + +// String to lowercase/uppercase. Ascii only, no unicode. +eng::string tolower(eng::string input); +eng::string toupper(eng::string input); + +// Convert a codepoint number into a utf8 string. +// If the codepoint is invalid, returns empty string. +eng::string get_codepoint_utf8(int32_t cp); + +// Write a codepoint in utf8 to a stream. +// If the codepoint is invalid, writes nothing and returns false. +bool write_codepoint_utf8(int32_t cp, std::ostream *out); + +// Calculate distance between two points +double distance_squared(double x1, double y1, double x2, double y2); + +// Make a LuaSourceVec with one element, for unit testing. +LuaSourcePtr make_lua_source(const eng::string &code); + +// Return true if the worldtype is authoritative. +inline bool is_authoritative(WorldType t) { return (t == WORLD_TYPE_MASTER); } + +// Remove items from a vector that are nullptr. +template +void remove_nullptrs(T &vec) { + auto iter = std::partition(vec.begin(), vec.end(), [] (const auto &x) { return x != nullptr; }); + vec.erase(iter, vec.end()); +} + +// Remove items from a vector that are marked for deletion. +template +void remove_marked_items(T &vec) { + auto iter = std::partition(vec.begin(), vec.end(), [] (const auto &x) { return !x.marked_for_deletion(); }); + vec.erase(iter, vec.end()); +} + +// An XYZ coordinate, general purpose. +template +struct NumXYZ { + using Number = NUMBER; + Number x, y, z; + NumXYZ() { x=0; y=0; z=0; } + NumXYZ(Number ix, Number iy, Number iz) { x=ix; y=iy; z=iz; } + void operator =(const NumXYZ &other) { x = other.x; y = other.y; z = other.z; } + void operator =(const NumXYZ &other) { x = other.x; y = other.y; z = other.z; } + void operator =(Number n) { x = n; y = n; z = n; } + bool operator ==(const NumXYZ &o) const { return x==o.x && y == o.y && z==o.z; } + bool operator !=(const NumXYZ &o) const { return x!=o.x || y != o.y || z!=o.z; } + NumXYZ operator -(const NumXYZ &o) const { return NumXYZ(x-o.x, y-o.y, z-o.z); } + NumXYZ operator +(const NumXYZ &o) const { return NumXYZ(x+o.x, y+o.y, z+o.z); } + NumXYZ operator *(float scale) const { return NumXYZ(x*scale, y*scale, z*scale); } + template + const NumXYZ convert() const { NumXYZ r; r.x=ONUMBER(x); r.y=ONUMBER(y); r.z=ONUMBER(z); return r; } + + eng::string debug_string() const { + eng::ostringstream oss; + oss << "(" << x << "," << y << "," << z << ")"; + return oss.str(); + } +}; + +using XYZ=NumXYZ; +using DXYZ=NumXYZ; + +// util::ostringstream +// +// This is a variant of ostringstream in which it is possible +// to get the contents without copying. To get the contents +// without copying, use oss.view(). +// +class ostringstream : public eng::ostringstream { + class rstringbuf : public std::basic_stringbuf { + public: + char *eback() const { return std::streambuf::eback(); } + char *pptr() const { return std::streambuf::pptr(); } + }; + rstringbuf rstringbuf_; +public: + ostringstream() { + std::basic_ostream::rdbuf(&rstringbuf_); + } + char *data() const { + return rstringbuf_.eback(); + } + size_t size() const { + return rstringbuf_.pptr() - rstringbuf_.eback(); + } + std::string_view view() const { + return std::string_view(data(), size()); + } + eng::string str() const { + return rstringbuf_.str(); + } +}; + +// send_to_stream: send all arguments to the specified stream. +inline void send_to_stream(std::ostream &os) {} +template +inline void send_to_stream(std::ostream &os, const ARG &arg, const REST & ... rest) { + os << arg; + send_to_stream(os, rest...); +} + +// ss: convert all arguments to a string by sending them to a stringstream. +template +inline eng::string ss(const ARGS & ... args) { + eng::ostringstream oss; + send_to_stream(oss, args...); + return oss.str(); +} + +// dprintf / dprint +// +// Send a debugging message to somewhere that it can be seen. This routine +// initially just sends output to stderr. But it can be hooked to send output +// somewhere else, like to a debug output window. +// +// The hook function must be a function that accepts a single line of text. The +// hook function will always be passed one line, consisting of printable +// characters only. There will be no control characters. The newline is +// implied. +// +void dprintview(std::string_view view); +void dprintf(const char *format, ...); +void hook_dprint(void (*func)(const char *oneline, size_t size)); + +template +inline void dprint(const ARGS & ... args) { + util::ostringstream oss; + send_to_stream(oss, args...); + dprintview(oss.view()); +} + +// A better API than std::setfill, std::hex, std::setw, std::setprecision +// +// Usage examples: +// std::cout << util::hex.width(5).fill('0').val(123) +// std::cout << util::dec.fill('$').precision(val(123) +// +// The reason that other API is bad is that it can leave std::cout +// in an unpredictable state. This API always leaves the stream clean. +// +template +class FormattedNumber { +public: + VALUE value_; + bool hex_; + int width_; + char fill_; + int precision_; + + constexpr FormattedNumber(VALUE v, bool h, int w, char f, int p) + : value_(v), hex_(h), width_(w), fill_(f), precision_(p) {} + + constexpr FormattedNumber width(int w) const { return FormattedNumber(value_, hex_, w, fill_, precision_); } + constexpr FormattedNumber fill(char f) const { return FormattedNumber(value_, hex_, width_, f, precision_); } + constexpr FormattedNumber precision(int p) const { return FormattedNumber(value_, hex_, width_, fill_, p); } + + template + constexpr FormattedNumber val(NVALUE v) const { return FormattedNumber(v, hex_, width_, fill_, precision_); } +}; + +constexpr auto hex = FormattedNumber(0, true, 0, '0', 6); +constexpr auto hex8 = FormattedNumber(0, true, 2, '0', 6); +constexpr auto hex16 = FormattedNumber(0, true, 4, '0', 6); +constexpr auto hex32 = FormattedNumber(0, true, 8, '0', 6); +constexpr auto hex64 = FormattedNumber(0, true, 16, '0', 6); +constexpr auto dec = FormattedNumber(0, false, 0, ' ', 6); + +} // namespace util + +template +inline std::ostream &operator<<(std::ostream &oss, util::FormattedNumber n) { + if (n.hex_) oss << std::hex; + else oss << std::dec; + oss << std::setprecision(n.precision_) << std::setfill(n.fill_) << std::setw(n.width_) << n.value_; + oss << std::dec << std::setfill(' ') << std::setprecision(6); + return oss; +} + +inline std::ostream &operator<<(std::ostream &oss, const util::XYZ &xyz) { + oss << xyz.x << "," << xyz.y << "," << xyz.z; + return oss; +} + +inline std::ostream &operator<<(std::ostream &oss, const util::DXYZ &xyz) { + oss << xyz.x << "," << xyz.y << "," << xyz.z; + return oss; +} + +#endif // UTIL_HPP diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp new file mode 100644 index 00000000..cc7cf082 --- /dev/null +++ b/luprex/cpp/core/world-accessor.cpp @@ -0,0 +1,1073 @@ + +#include "world.hpp" +#include "pprint.hpp" +#include + +static void tangible_getall(LuaCoreStack &LS0, LuaSlot list, const util::IdVector &idv) { + LuaVar tangibles, tan; + LuaExtStack LS(LS0.state(), tangibles, tan); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + assert(LS.istable(tangibles)); + LS.set(list, LuaNewTable); + int index = 1; + for (int64_t id : idv) { + LS.rawget(tan, tangibles, id); + assert(LS.istable(tan)); + LS.rawset(list, index++, tan); + } +} + +LuaDefine(tangible_xyz, "tan", + "|Get the current coordinates of the tangible and the plane." + "|Returns four values: x, y, z, plane") { + LuaArg tanobj; + LuaRet x, y, z, plane; + LuaDefStack LS(L, tanobj, x, y, z, plane); + World *w = World::fetch_global_pointer(L); + Tangible *tan = w->tangible_get(LS, tanobj, false); + AnimCoreState pos = tan->anim_queue_.get_final_core_state(); + LS.set(x, pos.xyz.x); + LS.set(y, pos.xyz.y); + LS.set(z, pos.xyz.z); + LS.set(plane, pos.plane); + return LS.result(); +} + +LuaDefine(tangible_animdebug, "tan", + "|Return a debug string showing the entire animation queue" + "|") { + LuaArg tanobj; + LuaRet result; + LuaDefStack LS(L, tanobj, result); + World *w = World::fetch_global_pointer(L); + Tangible *tan = w->tangible_get(LS, tanobj, false); + LS.set(result, tan->anim_queue_.steps_debug_string()); + return LS.result(); +} + +LuaDefine(tangible_animfinal, "tan", + "|Return the final step in the animation queue." + "|" + "|The animation queue stores animation steps. This function returns" + "|the final animation step. An animation step consists of key-value" + "|pairs. Some of those key-value pairs describe the last thing that" + "|happened to the tangible. Others describe the final resting place" + "|of the tangible." + "|" + "|For example, if the tangible were a pirate chest, the key-value" + "|pairs might be:" + "|" + "| action='open' # last thing the chest did" + "| xyz={1,2,3} # xyz coordinate" + "| plane='earth' # plane where the chest is located" + "| facing=0 # rotation of the chest" + "| open=true # chest can be open or closed" + "| fullness=0.8 # how big the heap of coins is" + "|" + "|See doc(tangible.animinit) for more information about animation queues." + "|") { + LuaArg tanobj; + LuaRet result; + LuaDefStack LS(L, tanobj, result); + World *w = World::fetch_global_pointer(L); + Tangible *tan = w->tangible_get(LS, tanobj, false); + AnimState state = tan->anim_queue_.get_final_everything(); + state.to_lua(LS, result, true); + return LS.result(); +} + +LuaDefine(tangible_animinit, "tan,config", + "|Reinitialize the animation queue and specify persistent state." + "|" + "|Every tangible has an animation queue. The queue consists of a" + "|sequence of animation steps. Each step consists of a list of" + "|key-value pairs. For example, if you want a human person to jump" + "|three feet in the air, you might find this animation step in the" + "|animation queue:" + "|" + "| action='jump' - the name of the animation to perform" + "| height=3.0 - the height to which you want him to jump" + "| xyz={1,2,3} - person's xyz coordinate during the jump" + "| plane=earth - plane where the jump takes place" + "|" + "|Some of those key-value pairs are 'persistent'. For example, xyz is" + "|persistent. That means that the player must always have an xyz" + "|coordinate. Every single animation step in the queue must" + "|contain a value for xyz. Likewise, 'plane' is a persistent variable." + "|The player must always be on some plane or another." + "|" + "|When you add an animation step to the animation queue, you do not have" + "|to always specify xyz and plane. For example, you can legally say:" + "|" + "| tangible.animate(a, nil, {action='jump', height=3.0}))" + "|" + "|This adds a step to the animation queue. That step contains" + "|xyz and plane, even though we didn't specify xyz and plane in" + "|the 'animate' command above. The values for xyz and plane will be" + "|copied over from the previous animation step. In this way, those values" + "|get persisted: they stay the same unless you change them in" + "|the 'animate' command." + "|" + "|There are five hardwired persistent variables: plane,xyz,facing,bp,model." + "|These five variables are persistent no matter what. This function," + "|tangible.animinit, optionally allows you to create more persistent" + "|variables. For example, let's say you have a pirate chest. You might" + "|want to add two persistent variables in addition to the usual set:" + "|" + "| open=true - whether the chest is open or closed" + "| heapsize=0.8 - the size of the heap of coins" + "|" + "|Making a variable persistent means that it will always have a value" + "|no matter what you do." + "|" + "|This function, tangible.animinit, is used to reconfigure the set of" + "|persistent state variables that are retained by the tangible's" + "|animation queue. You must provide a table containing all the" + "|persistent values you want, and their initial values." + "|") { + LuaArg tanobj, config; + LuaDefStack LS(L, tanobj, config); + World *w = World::fetch_global_pointer(L); + Tangible *tan = w->tangible_get(LS, tanobj, false); + AnimState state; + eng::string error = state.from_lua(LS, config, true, false); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + } + AnimState defsource = tan->anim_queue_.get_final_persistent(); + error = state.add_defaults(&defsource); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + } + tan->anim_queue_.clear(state); + tan->update_plane_item(); + return LS.result(); +} + +LuaDefine(tangible_animate, "tan,options,config", + "|Add an animation step to the tangible." + "|" + "|The animation queue stores animation steps. This function, " + "|tangible.animate, adds one new animation step to the queue." + "|" + "|It might be useful to read doc(tangible.animinit) before reading" + "|more." + "|" + "|An animation step is just a list of key-value pairs. Therefore," + "|the config table must be key-value pairs. Keys must be simple" + "|identifiers. Values can be numbers, strings, booleans, or" + "|coordinates." + "|" + "|Some of the key-value pairs may match the name of a persistent" + "|variable. If so, that key-value pair permanently changes the" + "|value of that persistent variable. The new value will" + "|be retained for all future animation steps." + "|" + "|Some of the key-value pairs may not match the name of any persistent" + "|variable. If so, that key-value pair is part of the" + "|animation step, but nothing is propagated forward to future animation" + "|steps." + "|" + "|The options can be nil, or options can be a table containing" + "|the following flags:" + "|" + "| replace: if true, then the last step in the queue is removed," + "| and the new animation replaces it. Persistent state is carried" + "| over from the step that was replaced." + "|" + "|") { + LuaArg tanobj, options, steptab; + LuaVar option; + LuaDefStack LS(L, option, tanobj, options, steptab); + bool replace = false; + if (!LS.isnil(options)) { + LuaKeywordParser kp(LS, options); + if (kp.parse(option, "replace")) { + replace = LS.ckboolean(option); + } + kp.final_check_throw(); + } + World *w = World::fetch_global_pointer(L); + Tangible *tan = w->tangible_get(LS, tanobj, false); + AnimState previous = tan->anim_queue_.get_final_persistent(); + AnimState update; + eng::string error = update.from_lua(LS, steptab, false, true); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + } + AnimState merge; + error = merge.merge(previous, update); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + } + if (replace) { + tan->anim_queue_.replace(merge); + } else { + tan->anim_queue_.add(merge); + } + tan->update_plane_item(); + return LS.result(); +} + + +LuaDefine(tangible_setclass, "tan,class", + "|Set the class of the tangible." + "|" + "|The class can be a 'class table' (ie, a table of methods), " + "|or it can be a string that names a class. The tangible is " + "|given an __index metamethod that points at the class table." + "|") { + LuaArg tanobj, classname; + LuaVar classtab, mt; + LuaDefStack LS(L, tanobj, classname, classtab, mt); + World *w = World::fetch_global_pointer(L); + w->tangible_get(LS, tanobj, false); + eng::string err = LS.getclass(classtab, classname); + if (err != "") { + luaL_error(L, "%s", err.c_str()); + } + LS.getmetatable(mt, tanobj); + LS.rawset(mt, "__index", classtab); + return LS.result(); +} + +LuaDefine(tangible_getclass, "tan", + "|Get the class of the tangible, if any." + "|" + "|The return value is a string, the class name, not" + "|the class table." + "|") { + LuaArg tanobj; + LuaVar classtab; + LuaRet classname; + LuaDefStack LS(L, tanobj, classtab, classname); + World *w = World::fetch_global_pointer(L); + w->tangible_get(LS, tanobj, false); + LS.tangetclass(classtab, tanobj); + eng::string name = LS.classname(classtab); + if (name == "") { + LS.set(classname, LuaNil); + } else { + LS.set(classname, name); + } + return LS.result(); +} + +LuaDefine(tangible_delete, "tan", + "|Delete the specified tangible." + "|" + "|This cannot be used to delete player tangibles," + "|To delete a player, use tangible.redirect") { + LuaArg tanobj; + LuaDefStack LS(L, tanobj); + World *w = World::fetch_global_pointer(L); + Tangible *tan = w->tangible_get(LS, tanobj, true); + if (tan == nullptr) { + return LS.result(); + } + if (tan->is_an_actor()) { + luaL_error(L, "Cannot delete a player using tangible.delete, use tangible.redirect instead."); + return 0; + } + w->tangible_delete(tan->id()); + return LS.result(); +} + +LuaDefine(tangible_build, "config", + "|Build a new tangible object." + "|" + "|The config table must contain: class,animstate." + "|" ){ + LuaArg config; + LuaVar classname, classtab, mt, animstate; + LuaRet database; + LuaDefStack LS(L, config, classname, classtab, database, mt, animstate); + LuaKeywordParser kp(LS, config); + + // Get the keyword arguments. + if (!kp.parse(classname, "class")) { + luaL_error(L, "You must specify a class for the tangible"); + } + if (!kp.parse(animstate, "animstate")) { + luaL_error(L, "You must specify an animstate table"); + } + kp.final_check_throw(); + + // Find the class. + eng::string err = LS.getclass(classtab, classname); + if (err != "") { + luaL_error(L, "%s", err.c_str()); + } + + // Calculate the initial animation state. + AnimState state; + err = state.from_lua(LS, animstate, true, false); + if (err != "") { + luaL_error(L, "%s", err.c_str()); + } + if (!state.contains("xyz") || !state.contains("plane")) { + luaL_error(L, "You must specify both xyz and plane in animstate"); + } + err = state.add_defaults(nullptr); + if (err != "") { + luaL_error(L, "%s", err.c_str()); + } + + World *w = World::fetch_global_pointer(L); + int64_t new_id = w->alloc_id_predictable(); + Tangible *tan = w->tangible_make(LS, database, new_id); + + // Update the class of the new tangible. + LS.getmetatable(mt, database); + LS.rawset(mt, "__index", classtab); + + tan->anim_queue_.clear(state); + tan->update_plane_item(); + + return LS.result(); +} + +LuaDefine(tangible_get, "id", + "|Get the tangible with the specified id." + "|This is for debugging only and will be removed in" + "|the released version.") { + LuaArg id; + LuaVar tangibles; + LuaRet database; + LuaDefStack LS(L, id, tangibles, database); + int64_t nid = LS.ckinteger(id); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(database, tangibles, id); + if (!LS.istable(database)) { + luaL_error(L, "Not a tangible ID: %d", nid); + } + return LS.result(); +} + +LuaDefine(tangible_redirect, "tan1,tan2,bulldozetan1", + "|Redirect is not working yet") { + LuaArg actor1, actor2, bldz; + LuaDefStack LS(L, actor1, actor2, bldz); + World *w = World::fetch_global_pointer(L); + bool bulldoze = LS.ckboolean(bldz); + Tangible *tan1 = w->tangible_get(LS, actor1, false); + if (!tan1->is_an_actor()) { + luaL_error(L, "redirect source is not an actor"); + } + if (LS.isnil(actor2)) { + w->redirects_[tan1->id()] = 0; + } else { + Tangible *tan2 = w->tangible_get(LS, actor2, false); + tan2->configure_id_pool_for_actor(); + w->redirects_[tan1->id()] = tan2->id(); + } + if (bulldoze) { + w->tangible_delete(tan1->id()); + } + return LS.result(); +} + +LuaDefine(tangible_id, "tan", + "|Return the tangible's id number." + "|This is for debugging only and will be removed" + "|in the released version.") { + LuaArg tanobj; + LuaRet id; + LuaDefStack LS(L, tanobj, id); + int64_t tid = LS.tanid(tanobj); + if (tid == 0) { + luaL_error(L, "Not a tangible"); + } + LS.set(id, tid); + return LS.result(); +} + +LuaDefine(tangible_actor, "", + "|Return the current actor.") { + LuaRet actor; + LuaVar tangibles; + LuaDefStack LS(L, tangibles, actor); + World *w = World::fetch_global_pointer(L); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(actor, tangibles, w->lthread_actor_id_); + return LS.result(); +} + +LuaDefine(tangible_place, "", + "|Return the current place.") { + LuaRet place; + LuaVar tangibles; + LuaDefStack LS(L, tangibles, place); + World *w = World::fetch_global_pointer(L); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(place, tangibles, w->lthread_place_id_); + return LS.result(); +} + +LuaDefine(tangible_find, "config", + "|Find tangibles by their location." + "|" + "|There are multiple ways to specify the plane, the bounding" + "|box, and the shape of the search. The most basic is to" + "|include these parameters in the table:" + "|" + "| plane : the plane to search (a string)" + "| center : xyz of the center of the search (a vector)" + "| radius : the radius of the search (a vector or number)" + "| shape : 'box', 'sphere', or 'cylinder'" + "|" + "|Shape has a default: 'sphere'. The other parameters do" + "|not have default values." + "|" + "|If you specify the radius as a vector, ie, different radii in" + "|each dimension, then the 'sphere' shape will actually be a" + "|spheroid, and the 'cylinder' shape will actually be a cylindroid." + "|" + "|Instead of specifying the center and plane, you can specify" + "|a tangible to search near:" + "|" + "| near : a tangible in whose vicinity the search occurs" + "|" + "|If you specify 'near', then by default, the near tangible is" + "|filtered out of the search. If you want to include it in the" + "|results, set the 'include' flag to true." + "|" + "| include : include the 'near' object in the results" + "|" + "|If you want to search an entire plane, you can use the" + "|wholeplane option, which replaces all the other options:" + "|" + "| wholeplane: the name of the plane to scan in its entirety" + "|" + "|It is valid to use 0.0 as a radius. For example, you could" + "|use shape='cylinder' and radiusz=0.0 to scan a flat" + "|circular area." + "|" + "|It is also valid to use math.huge (infinity) as a radius. For" + "|example, you could use shape='cylinder' and radius.z=math.huge" + "|to scan an infinitely tall cylinder." + "|" + "|If you are making a 2D game, it works fine to set all object Z" + "|coordinates to zero, and then do searches with centerz=0.0" + "|and radiusz=0.0." + "|") { + LuaArg config; + LuaRet result; + LuaDefStack LS(L, config, result); + LuaKeywordParser kw(LS, config); + PlaneScan scan; + scan.configure(kw); + kw.final_check_throw(); + + World *w = World::fetch_global_pointer(L); + util::IdVector idv; + w->get_near(scan, &idv); + tangible_getall(LS, result, idv); + return LS.result(); +} + +LuaDefine(tangible_start, "tangible,function,arg1,arg2...", + "|Start a thread." + "|" + "|Every thread is owned by a tangible. The first argument" + "|to 'tangible.start' indicates the tangible that owns" + "|the new thread. Instead of passing a single tangible," + "|you can pass a list of tangibles, in which case a thread" + "|is started on each tangible." + "|" + "|The function can be a lua closure, or it can be a string." + "|If it's a string, then the tangible's class will be" + "|used to look up the relevant closure." + "|" + "|The arguments arg1,arg2... will be passed to the" + "|function." + "|" + "|Actor and place aren't passed to the function unless" + "|you manually include them in the list arg1, arg2, etc." + "|The new thread can, however, use the builtin " + "|functions 'tangible.actor' and 'tangible.place' to obtain " + "|actor and place. Actor will be the same actor who " + "|called 'tangible.start'. Place will be the tangible that" + "|owns the thread, ie, the tangible passed to 'tangible.start'." + "|" + "|The new thread doesn't start running instantly:" + "|it waits until the current thread is finished. The" + "|current thread must either block (eg, 'wait') or terminate" + "|before the new thread can actually begin execution." + "|" + "|If you start a thread, then start another, then both of" + "|the newly-started threads wait until the current thread" + "|is finished. At that point, it is undefined which of the" + "|two new threads runs first." + "|" + "|Threads are owned by tangibles. If a tangible is" + "|deleted, then none of its threads will ever be resumed," + "|for any reason." + "|" + "|However, if a thread deletes its own place (ie, the tangible" + "|that owns the thread), then the thread will be allowed" + "|to continue running until it blocks. But from that point" + "|forward, the thread will never be resumed for any reason.") { + + int top = lua_gettop(L); + if (top < 2) { + luaL_error(L, "Not enough arguments to tangible.start"); + return 0; + } + int varlen = top - 2; + + World *w = World::fetch_global_pointer(L); + w->guard_blockable(L, "tangible.start"); + + LuaVar mt, classtab, plthreads, thread, thinfo, func, tanlist; + LuaDefStack LS(L, mt, classtab, plthreads, thread, thinfo, func, tanlist); + LuaSpecial place(1); + LuaSpecial fname(2); + + // If they passed in a single tangible, convert it to a tangible list. + int64_t place_id = LS.tanid(place); + if (place_id != 0) { + LS.newtable(tanlist); + LS.rawset(tanlist, 1, place); + } else { + LS.set(tanlist, place); + if (!LS.istable(tanlist)) { + luaL_error(L, "tangible.start expects a tangible or list of tangibles"); + return 0; + } + } + + // Check the entire tangible list for validity. + for (int i = 1; ; i++) { + LS.rawget(place, tanlist, i); + if (LS.isnil(place)) break; + w->tangible_get(LS, place, false); + } + + // Start threads on all the tangibles. + for (int i = 1; ; i++) { + LS.rawget(place, tanlist, i); + if (LS.isnil(place)) break; + + // Confirm that the place is a valid tangible, + // and get the tangible ID. + place_id = LS.tanid(place); + + // Get place's metatable and threads table. + LS.getmetatable(mt, place); + assert(LS.istable(mt)); + LS.rawget(plthreads, mt, "threads"); + assert(LS.istable(plthreads)); + + // Get the function closure. + if (LS.isfunction(fname)) { + LS.set(func, fname); + } else if (LS.isstring(fname)) { + LS.rawget(classtab, mt, "__index"); + assert(LS.istable(classtab)); + LS.rawget(func, classtab, fname); + if (!LS.isfunction(func)) { + eng::string cfname = LS.ckstring(fname); + luaL_error(L, "tangible doesn't have method: %s", cfname.c_str()); + return 0; + } + } else { + luaL_error(L, "invalid function, expected closure or string"); + return 0; + } + + // Create a new thread, set up function and arguments. + lua_State *CO = LS.newthread(thread); + lua_pushvalue(L, func.index()); + for (int i = 0; i < varlen; i++) { + lua_pushvalue(L, i + 3); + } + lua_xmove(L, CO, varlen + 1); + + // Create the thread info table. + LS.newtable(thinfo); + LS.rawset(thinfo, "thread", thread); + LS.rawset(thinfo, "actorid", w->lthread_actor_id_); + LS.rawset(thinfo, "isnew", true); + LS.rawset(thinfo, "useppool", false); + LS.rawset(thinfo, "print", false); + + // Get a thread ID for the new thread, store it in + // the thread table. + int64_t tid = w->alloc_id_predictable(); + LS.rawset(plthreads, tid, thinfo); + + // Push the thread's ID into the runnable thread queue. + w->schedule(0, tid, place_id); + } + return LS.result(); +} + + +LuaDefine(wait, "nticks", + "|Wait the specified number of ticks.") { + World *w = World::fetch_global_pointer(L); + w->guard_blockable(L, "wait"); + + // Parse the argument. + LuaArg seconds; + LuaDefStack LS(L, seconds); + int64_t n = LS.ckinteger(seconds); + if ((n < 0) || (n > 1000000)) { + luaL_error(L, "Argument to wait must be between 0 and 1000000"); + return LS.result(); + } + + // Schedule a continuation. + w->schedule(w->clock_ + n, w->lthread_thread_id_, w->lthread_place_id_); + return lua_yield(L, 0); +} + +LuaDefine(nopredict, "", + "|Stop predictive execution of this thread.") { + World *w = World::fetch_global_pointer(L); + w->guard_nopredict(L, "nopredict"); + + if (lua_gettop(L) != 0) { + luaL_error(L, "nopredict takes no arguments"); + } + return 0; +} + +LuaDefine(math_random, "(args...)", + "|Generate random numbers." + "|" + "|What it generates depends on the arguments:" + "|" + "| () - a float in range [0.0, 1.0)" + "| (high) - an int between 1 and high inclusive" + "| (low, high) - an int between low and high inclusive" + "|" + "|You may also pass in a randomstate table" + "|as an optional first argument." + "|" + "|math.random tries to cooperate with predictive" + "|reexecution to be as predictable as possible." + "|To achieve predictability, we used an ad-hoc" + "|random number generator. It passes a variety of" + "|statistical tests, but it's not well-studied." + "|" + "|If you want actually want nonpredictability, or" + "|if you need the assurance of a well-studied random" + "|number generator, use math.mtrandom or" + "|math.cryptrandom instead.") { + // Parse the arguments. + // This is hairy because there's a lot of possibilities. + bool passed_in_randomstate = false; + int arg = 1; + if ((lua_gettop(L) >= arg) && (lua_istable(L, arg))) { + passed_in_randomstate = true; + arg += 1; + } + bool have_range = false; + int64_t low, high; + if ((lua_gettop(L) >= arg) && (lua_type(L, arg) == LUA_TNUMBER)) { + double lowf, highf; + if ((lua_gettop(L) >= arg+1) && (lua_type(L, arg+1) == LUA_TNUMBER)) { + lowf = std::floor(lua_tonumber(L, arg)); + highf = std::floor(lua_tonumber(L, arg + 1)); + arg += 2; + } else { + lowf = 1; + highf = std::floor(lua_tonumber(L, arg)); + arg += 1; + } + if ((lowf < -LuaCoreStack::MAXINT) || (highf > LuaCoreStack::MAXINT)) { + luaL_error(L, "math.random range exceeds MAXINT"); + return 0; + } + if (lowf > highf) { + luaL_error(L, "math.random range low > high"); + return 0; + } + low = int64_t(lowf); + high = int64_t(highf); + have_range = true; + } + if (lua_gettop(L) >= arg) { + luaL_error(L, "math.random accepts an optional randomstate and an optional range"); + return 0; + } + + // Generate the seed, count, and salt. + // The salt prevents accidental duplication between user-specified + // seeds and system-generated seeds. + uint64_t seed, count, salt; + if (passed_in_randomstate) { + lua_pushstring(L, "seed"); + lua_rawget(L, 1); + lua_pushstring(L, "count"); + lua_rawget(L, 1); + if ((lua_type(L, -1) != LUA_TNUMBER) || + (lua_type(L, -2) != LUA_TNUMBER)) { + luaL_error(L, "Not a valid randomstate table"); + return 0; + } + double dseed = lua_tonumber(L, -2); + double dcount = lua_tonumber(L, -1); + seed = uint64_t(dseed) & LuaCoreStack::MAXINT; + count = uint64_t(dcount) & LuaCoreStack::MAXINT; + if (dseed < 0) { + salt = 0x35c9a6082a097ade; + } else { + salt = 0x4785d086ead90c20; + } + lua_pop(L, 2); + lua_pushstring(L, "count"); + lua_pushnumber(L, double((count + 1) & LuaCoreStack::MAXINT)); + lua_rawset(L, 1); + } else { + World *w = World::fetch_global_pointer(L); + if (w->lthread_use_ppool_) { + Tangible *actor = w->tangible_get(w->lthread_actor_id_); + seed = w->lthread_actor_id_; + count = actor->id_player_pool_.get_seqno(); + salt = 0x3ab0fb84aedc3764; + } else { + // TODO: maybe throw in a 'donotpredict' here. + seed = 123456; + count = w->id_global_pool_.get_seqno(); + salt = 0x6f493c90faf0139d; + } + } + + if (!have_range) { + // Generate the hash and convert to a double. + uint64_t hash = util::hash_ints(seed, count, salt, 456); + lua_pushnumber(L, util::hash_to_double(hash)); + } else { + // Generate the hash and scale it into the desired range. + // This code is not quite right: the results are not quite + // uniform, this is especially true for very long ranges. + uint64_t hash = util::hash_ints(seed, count, salt, 456); + uint64_t range = (high - low) + 1; + uint64_t offset = (hash & 0x7FFFFFFFFFFFFFFF) % range; + int64_t result = low + int64_t(offset); + lua_pushnumber(L, result); + } + return 1; +} + +LuaDefine(math_randomstate, "seed", + "|Create and return a randomstate table." + "|This is a lua table that stores the state for a random" + "|number generator. A randomstate table can be passed" + "|to math.random." + "|" + "|You can optionally omit the seed, in which case a" + "|seed will be chosen randomly. Automatically-generated" + "|seeds are guaranteed never to be the same as" + "|user-specified seeds.") { + double seed; + if (lua_gettop(L) == 0) { + World *w = World::fetch_global_pointer(L); + int64_t iseed = (w->id_global_pool_.get_seqno() & LuaCoreStack::MAXINT) + 1; + seed = -iseed; + } else if (lua_gettop(L) == 1) { + if (lua_type(L, 1) != LUA_TNUMBER) { + luaL_error(L, "math.randomstate takes an optional integer seed"); + return 0; + } + seed = lua_tonumber(L, 1); + if ((seed < 0.0) || (seed > LuaCoreStack::MAXINT) || (std::floor(seed) != seed)) { + luaL_error(L, "math.randomstate seed must be an integer 0-MAXINT"); + return 0; + } + } else { + luaL_error(L, "math.randomstate takes an optional integer seed"); + return 0; + } + + lua_newtable(L); + lua_pushstring(L, "seed"); + lua_pushnumber(L, seed); + lua_rawset(L, -3); + lua_pushstring(L, "count"); + lua_pushnumber(L, 0); + lua_rawset(L, -3); + return 1; +} + +LuaSandboxBuiltin(math_randomseed, "", ""); + +LuaDefine(pprint, "obj1, obj2, ...", + "|Pretty-print the specified objects." + "|" + "|See also: pprintx, which has a lot more options." + "|This function uses the default options: pretty print indented," + "|start at indentation level zero, and always expand the" + "|top-level table." + "|") { + World *w = World::fetch_global_pointer(L); + std::ostream *ostream = w->lthread_print_stream(); + LuaExtraArgs extra; + LuaDefStack LS(L, extra); + for (int i = 0; i < extra.size(); i++) { + pprint(LS, extra[i], PrettyPrintOptions(), ostream); + (*ostream) << std::endl; + } + return LS.result(); +} + +LuaDefine(pprintx, "options", + "|Pretty-print the specified object, with options" + "|" + "|Options is a table with these fields:" + "|" + "| value - the object to pretty-print" + "| indent - if false, suppress newlines and indentation (default: true)" + "| level - base level of indentation (default: zero)" + "| expand - if true, force expansion of top-level table (default: false)" + "|" + "|About the expand flag: normally, when you print a class, it just " + "|prints '', and when you print a tangible, it just" + "|prints ''. But sometimes, you want to see the details." + "|The expand flag forces it to expand the top-level table, even if the" + "|top-level table is a tangible or class." + "|") { + World *w = World::fetch_global_pointer(L); + std::ostream *ostream = w->lthread_print_stream(); + LuaArg loptions; + LuaVar value; + LuaDefStack LS(L, loptions, value); + PrettyPrintOptions options; + LuaKeywordParser kp(LS, loptions); + options.parse(kp); + if (!kp.parse(value, "value")) { + LS.set(value, LuaNil); + } + kp.final_check_throw(); + pprint(LS, value, options, ostream); + return LS.result(); +} + +LuaDefine(print, "obj1, obj2, ...", + "|Print object or objects.") { + World *w = World::fetch_global_pointer(L); + std::ostream *ostream = w->lthread_print_stream(); + LuaCoreStack LS(L); + int n = lua_gettop(L); + for (int i = 1; i <= n; i++) { + LuaSpecial root(i); + atomic_print(LS, root, false, ostream); + if (i < n) (*ostream) << " "; + } + (*ostream) << std::endl; + return 0; +} + +LuaDefine(dprint, "obj1, obj2, ...", + "|Print object or objects on the debug console.") { + std::ostringstream oss; + LuaCoreStack LS(L); + int n = lua_gettop(L); + for (int i = 1; i <= n; i++) { + LuaSpecial root(i); + atomic_print(LS, root, false, &oss); + if (i < n) oss << " "; + } + oss << std::endl; + util::dprintview(oss.str()); + return 0; +} + +LuaDefine(doc, "function", + "|Print documentation for specified function.") { + World *w = World::fetch_global_pointer(L); + std::ostream *ostream = w->lthread_print_stream(); + LuaArg func; + LuaDefStack LS(L, func); + eng::string doc = SourceDB::function_docs(LS, func); + if (doc == "") { + (*ostream) << "no doc found" << std::endl; + } + (*ostream) << doc; + return LS.result(); +} + +int lfn_http_request(lua_State *L, const char *method) { + World *w = World::fetch_global_pointer(L); + w->guard_blockable(L, "http.get"); + + LuaArg request; + LuaRet response; + LuaDefStack LS(L, request, response); + LuaKeywordParser kp(LS, request); + HttpClientRequest req; + + // Parse the request and make sure it's valid. + // If not, immediately pass a '400 bad request' back to lua. + req.set_method(method); + req.configure(kp); + kp.final_check_throw(); + req.set_defaults(); + eng::string error = req.check(); + if (!error.empty()) { + HttpParser::store_fail(LS, response, 400, util::ss("Bad Request: ", error)); + return LS.result(); + } + + // Give the request an ID. + req.set_request_id(w->id_global_pool_.get_one()); + req.set_place_id(w->lthread_place_id_); + req.set_thread_id(w->lthread_thread_id_); + + // Store it in the global request table. + w->http_requests_[req.request_id()] = req; + + // Block. + return lua_yield(L, 0); +} + +LuaDefine(http_get, "request", + "|Make an HTTP GET request. Returns an HTTP response." + "|See doc(http.clientrequest) and doc(http.clientresponse).") { + return lfn_http_request(L, "GET"); +} + +LuaDefine(http_head, "request", + "|Make an HTTP HEAD request. Returns an HTTP response." + "|See doc(http.clientrequest) and doc(http.clientresponse).") { + return lfn_http_request(L, "HEAD"); +} + +LuaDefine(http_post, "request", + "|Make an HTTP POST request. Returns an HTTP response." + "|See doc(http.clientrequest) and doc(http.clientresponse).") { + return lfn_http_request(L, "POST"); +} + +LuaDefine(global_set, "varname, value", + "|Store data in the global data table." + "|" + "|The variable name must be a string which is a valid" + "|lua identifier." + "|" + "|You can store global data using global.set, then you can" + "|retrieve it using global.get. You can also retrieve data using" + "|gv.varname, which is just shorthand for global.get. You may not" + "|store data using gv.varname=value, this yields the error 'Use " + "|global.set to store data in the global data table.'" + "|" + "|Values stored using global.set are transmitted to all" + "|connected clients immediately. When a new client connects," + "|he will receive all the global data." + "|" + "|The global data table is not the same thing as the lua " + "|environment table. Trying to store data in the lua environment" + "|table will seem to work, at first, but the data will not get" + "|difference transmitted, and will eventually be cleared and lost." + "|Therefore, it is essential that global data be stored in the" + "|global data table (using global.set) instead of in the lua" + "|environment table." + "|" + "|There are certain restrictions on the values that you store." + "|Only data that can be serialized according to doc(table.serialize)" + "|can be stored." + "|" + "|When you store the value, it is immediately serialized and then" + "|deserialized again, and the deserialized copy is stored in the" + "|variable." + "|" +// "|When you call global.get, you obtain the copy. Any attempt to" +// "|mutate the copy will fail with this lua error message: 'Tables" +// "|returned by global.get are immutable.' This rule prevents'" +// "|aliasing between global data and other data structures." + "|") { + LuaArg varname; + LuaArg value; + LuaDefStack LS(L, varname, value); + + // Check the varname argument. + eng::string gvar = LS.ckstring(varname); + if (!sv::is_lua_id(gvar)) { + luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); + return LS.result(); + } + + World *w = World::fetch_global_pointer(L); + w->set_global(LS, gvar, value); + return LS.result(); +} + +LuaDefine(global_get, "varname", + "|Get data stored using global.set" + "|" + "|See doc(global.set) for information on how to store global data." + "|" + "|Do not mutate data returned by global.get: doing so will produce" + "|unpredictable results. Instead, using global.set to mutate global" + "|variables." + "|") { + LuaArg varname; + LuaRet value; + LuaVar globaldb; + LuaDefStack LS(L, varname, value, globaldb); + LS.rawget(globaldb, LuaRegistry, "globaldb"); + LS.rawget(value, globaldb, varname); + return LS.result(); +} + +LuaDefine(global_once, "varname", + "|For a given string, returns true exactly once" + "|" + "|The semantics and difference transmission behavior of global.once" + "|are identical to the semantics of global.set, since global.once" + "|uses global.set under the covers." + "|") { + LuaArg varname; + LuaRet result; + LuaVar globaldb, flag; + LuaDefStack LS(L, varname, flag, result, globaldb); + + // Check the varname argument. + eng::string gvar = LS.ckstring(varname); + if (!sv::is_lua_id(gvar)) { + luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); + return LS.result(); + } + gvar += ":once"; + + LS.rawget(globaldb, LuaRegistry, "globaldb"); + LS.rawget(flag, globaldb, gvar); + + if (!LS.isnil(flag)) { + LS.set(result, false); + return LS.result(); + } + + LS.set(result, true); + World *w = World::fetch_global_pointer(L); + w->set_global(LS, gvar, result); + return LS.result(); +} + +LuaDefine(global_clearonce, "varname", + "|Reset the specified once-flag" + "|" + "|The semantics and difference transmission behavior of global.clearonce" + "|are identical to the semantics of global.set, since global.once" + "|uses global.set under the covers." + "|") { + LuaArg varname; + LuaVar null; + LuaDefStack LS(L, varname, null); + + // Check the varname argument. + eng::string gvar = LS.ckstring(varname); + if (!sv::is_lua_id(gvar)) { + luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); + return LS.result(); + } + gvar += ":once"; + + LS.set(null, LuaNil); + World *w = World::fetch_global_pointer(L); + w->set_global(LS, gvar, null); + return LS.result(); +} diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp new file mode 100644 index 00000000..5f4235c5 --- /dev/null +++ b/luprex/cpp/core/world-core.cpp @@ -0,0 +1,1226 @@ + +#include "world.hpp" +#include "idalloc.hpp" +#include "animqueue.hpp" +#include "traceback.hpp" +#include "pprint.hpp" +#include "util.hpp" +#include "serializelua.hpp" + +#include + +// Read a SimpleDynamic value from the streambuffer and push +// it onto the lua stack. +void push_simple_dynamic(lua_State *L, StreamBuffer *sb) { + SimpleDynamicTag type = sb->read_simple_dynamic_tag(); + switch (type) { + case SimpleDynamicTag::NUMBER: { + lua_pushnumber(L, sb->read_double()); + break; + } + case SimpleDynamicTag::BOOLEAN: { + lua_pushboolean(L, sb->read_bool() ? 1:0); + break; + } + case SimpleDynamicTag::STRING: { + std::string_view s = sb->read_string_view(); + lua_pushlstring(L, s.data(), s.size()); + break; + } + case SimpleDynamicTag::VECTOR: { + double x = sb->read_double(); + double y = sb->read_double(); + double z = sb->read_double(); + lua_newtable(L); + lua_pushnumber(L, x); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, y); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, z); + lua_rawseti(L, -2, 3); + break; + } + default: throw StreamCorruption(); + } +} + +// Given a lua value, try to pack it into the stream buffer as a simple dynamic. +bool encode_simple_dynamic(LuaCoreStack &LS, LuaSlot &slot, StreamBuffer *sb) { + switch (LS.type(slot)) { + case LUA_TNUMBER: + sb->write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); + sb->write_double(LS.cknumber(slot)); + return true; + case LUA_TSTRING: + sb->write_simple_dynamic_tag(SimpleDynamicTag::STRING); + sb->write_string(LS.ckstringview(slot)); + return true; + case LUA_TBOOLEAN: + sb->write_simple_dynamic_tag(SimpleDynamicTag::BOOLEAN); + sb->write_bool(LS.ckboolean(slot)); + return true; + case LUA_TTABLE: { + std::optional xyz = LS.tryxyz(slot); + if (!xyz.has_value()) return false; + sb->write_simple_dynamic_tag(SimpleDynamicTag::VECTOR); + sb->write_dxyz(xyz.value()); + return true; + } + default: return false; + } +} + + +void World::store_global_pointer(lua_State *L, World *v) { + lua_pushstring(L, "world"); + lua_pushlightuserdata(L, v); + lua_rawset(L, LUA_REGISTRYINDEX); +} + +World *World::fetch_global_pointer(lua_State *L) { + lua_pushstring(L, "world"); + lua_rawget(L, LUA_REGISTRYINDEX); + World *result = (World *)lua_touserdata(L, -1); + if (result == nullptr) { + luaL_error(L, "No world pointer stored."); + } + lua_pop(L, 1); + return result; +} + +World::~World() { +} + +World::World(WorldType wt) { + // Master world model by default. + world_type_ = wt; + + // Initialize the ID allocator in master mode. + if (is_authoritative()) { + id_global_pool_.init_master(); + } else { + id_global_pool_.init_synch(); + } + + // Prepare to manipulate the lua state. + LuaVar world, globtab; + LuaExtStack LS(state(), world, globtab); + + // Put the world pointer into the lua registry. + World::store_global_pointer(state(), this); + + // Clear the lthread state. + clear_lthread_state(); + + // Set the tabletype of the registry. + LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); + + // Set the tabletype of the global environment. + LS.getglobaltable(globtab); + LS.settabletype(globtab, LUA_TT_GLOBALENV); + + // Store the world type in the registry. + LS.set_world_type(wt); + + // Create the globaldb in the registry. + LS.rawset(LuaRegistry, "globaldb", LuaNewTable); + + // Initialize the SourceDB. + source_db_.init(state()); + + // Clear the clock. + clock_ = 0; + + // Initialize global variable state. + assign_seqno_ = 1; +} + +Tangible::Tangible(World *w, int64_t id) : world_(w), anim_queue_(), id_player_pool_(&w->id_global_pool_) { + plane_item_.set_id(id); + plane_item_.track(&w->plane_map_); +} + +void Tangible::update_plane_item() { + AnimCoreState pos = anim_queue_.get_final_core_state(); + plane_item_.set_pos(pos.plane, pos.xyz.x, pos.xyz.y, pos.xyz.z); +} + +void Tangible::serialize(StreamBuffer *sb) { + anim_queue_.serialize(sb); + id_player_pool_.serialize(sb); + print_buffer_.serialize(sb); +} + +void Tangible::deserialize(StreamBuffer *sb) { + anim_queue_.deserialize(sb); + id_player_pool_.deserialize(sb); + print_buffer_.deserialize(sb); + update_plane_item(); +} + +Tangible *World::tangible_get(int64_t id) { + auto iter = tangibles_.find(id); + if (iter == tangibles_.end()) { + return nullptr; + } else { + return iter->second.get(); + } +} + +const Tangible *World::tangible_get(int64_t id) const { + auto iter = tangibles_.find(id); + if (iter == tangibles_.end()) { + return nullptr; + } else { + return iter->second.get(); + } +} + +World::TanVector World::tangible_get_all(const IdVector &ids) const { + TanVector result(ids.size()); + for (int i = 0; i < int(ids.size()); i++) { + result[i] = tangible_get(ids[i]); + } + return result; +} + +Tangible *World::tangible_get(const LuaCoreStack &LS, LuaSlot tab, bool allowdel) { + int64_t id = LS.tanid(tab); + if (id == 0) { + luaL_error(LS.state(), "parameter is not a tangible"); + } + Tangible *result = tangible_get(id); + if (!allowdel) { + if (result == nullptr) { + luaL_error(LS.state(), "argument is a deleted tangible, which is not allowed here"); + } + } + return result; +} + +Tangible *World::tangible_make(const LuaCoreStack &LS0, LuaSlot database, int64_t id) { + assert(LS0.validpositiveinteger(id)); + LuaVar metatab; + LuaExtStack LS(LS0.state(), metatab); + + // Create the C++ part of the structure. + UniqueTangible &t = tangibles_[id]; + assert (t == nullptr); + t.reset(new Tangible(this, id)); + + // Set the login flags. + t->can_be_controlled_ = false; + t->is_controlled_ = false; + t->force_disconnect_ = false; + t->delete_on_disconnect_ = false; + + // AnimQueue initializes itself to a valid default state. + AnimState state; + state.add_defaults(nullptr); + t->anim_queue_.clear(state); + t->update_plane_item(); + + // Fetch the tangible's Lua database and metatable. + LS.maketan(database, id); + LS.getmetatable(metatab, database); + + // Set up the inventory and thread table. + LS.rawset(database, "inventory", LuaNewTable); + LS.rawset(metatab, "threads", LuaNewTable); + + return t.get(); +} + +Tangible *World::tangible_make(int64_t id) { + LuaVar database; + LuaExtStack LS(state(), database); + return tangible_make(LS, database, id); +} + +void World::tangible_delete(int64_t id) { + lua_State *L = state(); + LuaVar tangibles, database, metatab; + LuaExtStack LS(L, tangibles, database, metatab); + + // Fetch the C++ side of the tangible. + auto iter = tangibles_.find(id); + if (iter == tangibles_.end()) { + return; // Nothing to delete. + } + + // Fetch the lua side of the tangible. + LS.maketan(database, id); + assert(LS.istable(database)); + LS.getmetatable(metatab, database); + + // Clear out the database and the metatable. + LS.cleartable(database, false); + LS.cleartable(metatab, true); + + // Now put the bare minimum info back into the metatable. + LS.rawset(metatab, "id", id); + LS.rawset(metatab, "__metatable", false); + + // Remove the C++ portion from the tangibles table. + tangibles_.erase(iter); +} + +void World::get_near(PlaneScan &scan, util::IdVector *into) const { + into->clear(); + // If 'near' is set, update the plane and center. + int64_t actor_id = scan.near(); + if (actor_id != 0) { + const Tangible *player = tangible_get(actor_id); + if (player == nullptr) { + return; + } + const PlaneItem &pi = player->plane_item_; + scan.set_plane(pi.plane()); + scan.set_center(util::XYZ(pi.x(), pi.y(), pi.z())); + } + plane_map_.scan(scan, into); +} + +void World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted, util::IdVector *into) const { + PlaneScan scan; + scan.set_radius(radius); + scan.set_shape(PlaneScan::SPHERE); + scan.set_sorted(sorted); + scan.set_omit_nowhere(exclude_nowhere); + scan.set_near(player_id, !omit_player); + get_near(scan, into); +} + +void World::get_encoded_animation_queues(uint32_t count, const int64_t *ids, util::SharedStdStringVec &into) { + into.resize(count); + for (int i = 0; i < int(count); i++) { + Tangible *tan = tangible_get(ids[i]); + if (tan == nullptr) { + into[i] = AnimQueue::get_encoded_blank_queue(); + } else { + into[i] = tan->anim_queue_.get_encoded_queue(); + } + } +} + +World::Redirects World::fetch_redirects() { + World::Redirects result = std::move(redirects_); + redirects_.clear(); + return result; +} + +int64_t World::create_login_actor() { + assert(stack_is_clear()); + int64_t id = id_global_pool_.get_one(); + { + LuaVar database, classtab, mt, func; + LuaExtStack LS(state(), database, classtab, mt, func); + Tangible *tan = tangible_make(LS, database, id); + + // Set the login flags. + if (is_authoritative()) { + tan->can_be_controlled_ = true; + tan->is_controlled_ = true; + tan->force_disconnect_ = false; + tan->delete_on_disconnect_ = true; + } + + LS.makeclass(classtab, "login"); + LS.getmetatable(mt, database); + LS.rawset(mt, "__index", classtab); + tan->configure_id_pool_for_actor(); + tan->print_buffer_.clear(); + + if (is_authoritative()) { + LS.rawget(func, classtab, "initialize"); + spawn(LS, id, id, func, true, 0, false); + } + } + if (is_authoritative()) { + run_scheduled_threads(); + } + return id; +} + +void World::disconnected(int64_t actor_id) { + Tangible *tan = tangible_get(actor_id); + assert(tan != nullptr); + assert(tan->is_controlled_); + tan->is_controlled_ = false; + tan->force_disconnect_ = false; + if (tan->delete_on_disconnect_) { + util::dprintf("Deleted actor: %lld\n", actor_id); + tangible_delete(actor_id); + } +} + +eng::string World::probe_lua_expr(int64_t actor_id, std::string_view lua) { + assert(stack_is_clear()); + lua_State *L = state(); + + Tangible *actor = tangible_get(actor_id); + if (actor == nullptr) { + return ""; + } + + LuaVar closure; + LuaExtStack LS(L, closure); + + // create the compiled closure. + int status = luaL_loadbuffer(L, lua.data(), lua.size(), "=probe"); + lua_replace(L, closure.index()); + if (status != LUA_OK) { + // The closure is actually an error message. Do nothing. + // This should normally not happen: LuaConsole should filter + // out syntax errors. + return ""; + } + + // Call the closure. + int top = lua_gettop(L); + lua_pushvalue(L, closure.index()); + open_lthread_state(actor_id, actor_id, 0, false, true); + eng::string msg = traceback_pcall(L, 0, LUA_MULTRET); + + // If there's an error message, print it. + // Otherwise, pretty-print the results. + std::ostream *ostream = lthread_print_stream(); + if (msg.empty()) { + for (int i = top + 1; i <= lua_gettop(L); i++) { + LuaSpecial root(i); + pprint(LS, root, PrettyPrintOptions(), ostream); + // TODO: this endl is unnecessary if we just printed a newline. + (*ostream) << std::endl; + } + } else { + (*ostream) << msg << std::endl; + } + + // Collect the lthread_prints (and also make sure they + // don't go into the printbuffer). + eng::string result = lthread_prints_->str(); + lthread_prints_.reset(); + + close_lthread_state(); + return result; +} + +void World::probe_lua_call(int64_t place_id, int64_t actor_id, std::string_view datapack, StreamBuffer *retvals) { + assert(stack_is_clear()); + lua_State *L = state(); + + // Get the actor and place, C++ version. Make sure both exist. + Tangible *tactor = tangible_get(actor_id); + Tangible *tplace = tangible_get(place_id); + if ((tactor == nullptr) || (tplace == nullptr)) { + return; + } + + // Use a streambuffer to parse the datapack. + StreamBuffer datasb(datapack); + + // Extract the class and function name from the datapack. + eng::string classname; + eng::string funcname; + try { + classname = datasb.read_string_limit(100); + funcname = datasb.read_string_limit(100); + } catch (const StreamException &ex) { + return; + } + if ((!sv::is_lua_id(classname)) || (!sv::is_lua_id(funcname))) { + return; + } + + LuaVar lclass, lfunc, actor, place, tangibles, retvec, retval; + LuaExtStack LS(L, lclass, lfunc, actor, place, tangibles, retvec, retval); + + // Get the actor and place, lua version. Make sure both exist. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(actor, tangibles, actor_id); + LS.rawget(place, tangibles, place_id); + if (!LS.istable(actor) || !LS.istable(place)) { + return; + } + + // Get the class table and closure + eng::string err = LS.getclass(lclass, classname); + if ((!err.empty()) || (!LS.istable(lclass))) { + return; + } + LS.rawget(lfunc, lclass, funcname); + if (!LS.isfunction(lfunc)) { + return; + } + + // Call the closure. + lua_pushvalue(L, lfunc.index()); + lua_pushvalue(L, actor.index()); + lua_pushvalue(L, place.index()); + int nargs = 2; + try { + while (!datasb.empty()) { + push_simple_dynamic(L, &datasb); + nargs++; + } + } catch (const StreamException &exc) { + return; + } + + open_lthread_state(actor_id, place_id, 0, false, true); + eng::string msg = traceback_pcall(L, nargs, 1); + + // Send any prints to the console. + eng::string prints = lthread_prints_->str(); + lthread_prints_.reset(); + util::dprint(prints); + + if (msg.empty()) { + lua_replace(L, retvec.index()); + if (LS.istable(retvec)) { + for (int i = 1 ; ; i++) { + LS.rawget(retval, retvec, i); + bool ok = encode_simple_dynamic(LS, retval, retvals); + if (!ok) break; + } + } + } else { + util::dprint(msg); + } + + close_lthread_state(); +} + + +// This is called from World::update_source, and also +// from World::patch_source in the difference transmitter. +// +// For the moment, errors are channeled to util::dprint, +// and 'print' statements just go to std::cerr. Neither +// of these is ideal. We need to get serious about setting +// up error handling. +// +// We also need to figure out a solution for what happens if +// some lua source file tries to modify, say, tangible state +// in top-level code. +// +void World::rebuild_sourcedb() { + for (const eng::string &mod: source_db_.modules()) { + open_lthread_state(0, 0, 0, false, true); + eng::string err = source_db_.rebuild_module(mod); + eng::string prints = lthread_prints_->str(); + lthread_prints_.reset(); + close_lthread_state(); + if (!err.empty() || !prints.empty()) { + util::dprint("Loading Module ", mod); + if (!err.empty()) util::dprint(err); + if (!prints.empty()) util::dprint(prints); + } + } +} + +void World::update_source(const util::LuaSourceVec &source) { + assert(stack_is_clear()); + source_db_.update(source); + rebuild_sourcedb(); + assert(stack_is_clear()); +} + +void World::update_source(const util::LuaSourcePtr &source) { + if (source != nullptr) { + update_source(*source); + } +} + +void World::update_source(std::string_view sourcepack) { + if (!sourcepack.empty()) { + try { + StreamBuffer sb(sourcepack); + util::LuaSourceVec sv; + SourceDB::deserialize_source(&sv, &sb); + update_source(sv); + } catch (const StreamException &ex) { + return; + } + } +} + +void World::http_response(const HttpParser &response) { + // Find the request. + auto iter = http_requests_.find(response.request_id()); + if (iter == http_requests_.end()) { + return; + } + HttpClientRequest request = iter->second; + http_requests_.erase(iter); + + lua_State *CO; + { + // Get the place and thread as lua objects. + LuaVar tangibles, place, mt, threads, thinfo, thread; + LuaExtStack LS(state(), tangibles, place, mt, threads, thinfo, thread); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(place, tangibles, request.place_id()); + if (!LS.istable(place)) { + return; + } + LS.getmetatable(mt, place); + if (!LS.istable(mt)) { + return; + } + LS.rawget(threads, mt, "threads"); + if (!LS.istable(threads)) { + return; + } + LS.rawget(thinfo, threads, request.thread_id()); + if (!LS.istable(thinfo)) { + return; + } + LS.rawget(thread, thinfo, "thread"); + if (!LS.isthread(thread)) { + return; + } + CO = LS.ckthread(thread); + } + + // Push the response onto the awakening thread. + lua_pushnil(CO); + LuaSpecial responsetable(lua_gettop(CO)); + LuaCoreStack LSCO(CO); + response.store(LSCO, responsetable); + + // Awaken the thread, with its new return value. + schedule(0, request.thread_id(), request.place_id()); + run_scheduled_threads(); +} + +void World::http_responses(const HttpParserVec &responses) { + for (const HttpParser &response : responses) { + http_response(response); + } +} + +void World::abort_all_http_requests(int status_code, std::string_view error) { + HttpParser abortresponse; + abortresponse.fail(status_code, error); + while (!http_requests_.empty()) { + abortresponse.set_request_id(http_requests_.begin()->first); + http_response(abortresponse); + } +} + +HttpServerResponse World::http_serve(const HttpParser &request) { + assert(stack_is_clear()); + HttpServerResponse response; + + // We're only supposed to be passed complete requests. + assert(request.complete()); + + // If the request is HTTP/1.1, then the response should be HTTP/1.1 + response.set_http11(request.http11()); + + // If the incoming request has already been detected to be + // invalid by the HTTP parser, then just send the error + // message back to the client without involving lua at all. + if (request.errstatus()) { + response.fail(request.status(), request.error()); + return response; + } + + lua_State *L = state(); + LuaVar www, func, reqtab; + LuaExtStack LS(L, www, func, reqtab); + + // Get the www class. If there's no such class, + // return a 503 Service Unavailable to the client. + eng::string err = LS.getclass(www, "www"); + if (!err.empty()) { + response.fail(503, "class www doesn't exist"); + return response; + } + + // Get the name of the desired function. + std::string_view orig_fn = request.first_path_component("index"); + eng::string lua_fn = HttpParser::to_lua_identifier(orig_fn); + if (lua_fn.empty()) { + response.fail(404, util::ss("cannot convert to lua function name: ", orig_fn)); + return response; + } + + // Get the closure. If there's no such closure, + // return a 404 Not Found to the client. + LS.rawget(func, www, lua_fn); + if (!LS.isfunction(func)) { + response.fail(404, util::ss("no such lua function: www.", lua_fn)); + return response; + } + + // Store the request into a lua table. + request.store(LS, reqtab); + + // Call the function. + int oldtop = lua_gettop(L); + lua_pushvalue(L, func.index()); + lua_pushvalue(L, reqtab.index()); + open_lthread_state(0, 0, 0, false, false); + eng::string msg = traceback_pcall(L, 1, LUA_MULTRET); + close_lthread_state(); + + // If the call threw an error, return + // a 500 Internal Server Error to the client. + if (!msg.empty()) { + response.fail(500, msg); + return response; + } + + // If the call didn't return a single table, return + // a 500 Internal Server Error to the client. + int newtop = lua_gettop(L); + if ((newtop != oldtop + 1) || (!lua_istable(L, newtop))) { + response.fail(500, util::ss("lua function www.", lua_fn, " didn't return a table")); + return response; + } + + // Try to convert the table into a response. + LuaKeywordParser kp(LS, LuaSpecial(newtop)); + response.configure(kp); + response.set_defaults(); + eng::string kperr = kp.final_check(); + if (!kperr.empty()) { + response.fail(500, kperr); + } + return response; +} + +void World::run_unittests() { + assert(stack_is_clear()); + source_db_.run_unittests(); + assert(stack_is_clear()); +} + +void World::invoke(const Invocation &inv) { + switch (inv.kind()) { + case InvocationKind::LUA_INVOKE: + invoke_lua_call(inv.actor(), inv.place(), inv.datapack()); + break; + case InvocationKind::LUA_EXPR: + invoke_lua_expr(inv.actor(), inv.place(), inv.datapack()); + break; + case InvocationKind::FLUSH_PRINTS: + invoke_flush_prints(inv.actor(), inv.place(), inv.datapack()); + break; + case InvocationKind::TICK: + invoke_tick(inv.actor(), inv.place(), inv.datapack()); + break; + case InvocationKind::LUA_SOURCE: + invoke_lua_source(inv.actor(), inv.place(), inv.datapack()); + break; + default: + // Do nothing. Standard behavior for any invalid command is to + // simply do nothing at all. Perhaps eventually we may add a flag + // to the world model to indicate that we've detected an invalid + // command, to allow us to close the connection to a client that + // is misbehaving. + break; + } +} + +bool World::spawn(LuaCoreStack &LS0, int64_t actor_id, int64_t place_id, LuaSlot func, + bool passactorplace, int nargs, bool print) { + lua_State *L = LS0.state(); + LuaVar actor, place, mt, index, tangibles, thread, threads, thinfo; + LuaExtStack LS(L, actor, place, mt, index, tangibles, thread, threads, thinfo); + + // Get the actor and place, C++ version. Make sure both exist. + Tangible *tactor = tangible_get(actor_id); + Tangible *tplace = tangible_get(place_id); + if ((tactor == nullptr) || (tplace == nullptr)) { + return false; + } + + // Get the actor and place, lua version. Make sure both exist. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(actor, tangibles, actor_id); + LS.rawget(place, tangibles, place_id); + if (!LS.istable(actor) || !LS.istable(place)) { + return false; + } + + // Get an ID for the thread. + // We currently always use the actor pool. We may extend + // this to allow the use of the place pool. + int64_t tid = tactor->id_player_pool_.get_one(); + + // Get the place's metatable. + LS.getmetatable(mt, place); + if (!LS.istable(mt)) { + return false; + } + + // If the function is a string, look it up in the place's class. + if (LS.isstring(func)) { + LS.rawget(index, mt, "__index"); + if (!LS.istable(index)) { + return false; + } + LS.rawget(func, index, func); + } + + // Make sure the function is a function. + if (!LS.isfunction(func)) { + return false; + } + + // Create a new thread. Push function, and maybe actor and place. + lua_State *CO = LS.newthread(thread); + lua_pushvalue(L, func.index()); + if (passactorplace) { + lua_pushvalue(L, actor.index()); + lua_pushvalue(L, place.index()); + } + lua_xmove(L, CO, passactorplace ? 3:1); + + // Push any extra arguments. Extra arguments were pushed onto + // the lua stack before calling spawn, so they are located at oldtop. + if (nargs > 0) { + int base = LS.oldtop() - nargs + 1; + for (int i = 0; i < nargs; i++) { + lua_pushvalue(L, base + i); + } + lua_xmove(L, CO, nargs); + } + + // Create the thread info table. + LS.newtable(thinfo); + LS.rawset(thinfo, "thread", thread); + LS.rawset(thinfo, "actorid", actor_id); + LS.rawset(thinfo, "isnew", true); + LS.rawset(thinfo, "useppool", true); + LS.rawset(thinfo, "print", print); + + // Store the thread into place's thread table. + LS.rawget(threads, mt, "threads"); + if (!LS.istable(threads)) { + return false; + } + LS.rawset(threads, tid, thinfo); + + schedule(0, tid, place_id); + return true; +} + +void World::invoke_flush_prints(int64_t actor_id, int64_t place_id, std::string_view datapack) { + assert(stack_is_clear()); + // Check argument sanity. + if (actor_id != place_id) { + return; + } + int64_t line = sv::to_int64(datapack, -1); + if ((line < 0)||(line > INT_MAX)) { + return; + } + Tangible *tactor = tangible_get(actor_id); + if (tactor == nullptr) { + return; + } + tactor->print_buffer_.discard_upto(line); + assert(stack_is_clear()); +} + +void World::invoke_lua_expr(int64_t actor_id, int64_t place_id, std::string_view datapack) { + assert(stack_is_clear()); + { + lua_State *L = state(); + LuaVar func; + LuaExtStack LS(L, func); + + // create the compiled closure. + int status = luaL_loadbuffer(L, datapack.data(), datapack.size(), "=invoke"); + lua_replace(L, func.index()); + if (status != LUA_OK) { + // The closure is actually an error message. Do nothing. + // This should normally not happen: LuaConsole should filter + // out syntax errors. + return; + } + + // Spawn the thread and run it. + int nargs = 0; + spawn(LS, actor_id, place_id, func, false, nargs, true); + } + run_scheduled_threads(); + assert(stack_is_clear()); +} + + +void World::invoke_lua_call(int64_t actor_id, int64_t place_id, std::string_view datapack) { + assert(stack_is_clear()); + + // Use a streambuffer to parse the datapack. + StreamBuffer datasb(datapack); + + // Extract the class and function name from the datapack. + eng::string classname; + eng::string funcname; + try { + classname = datasb.read_string_limit(100); + funcname = datasb.read_string_limit(100); + } catch (const StreamException &ex) { + return; + } + if ((!sv::is_lua_id(classname)) || (!sv::is_lua_id(funcname))) { + return; + } + // TODO: Add check for permit_invoke(classname, funcname) + + { + lua_State *L = state(); + LuaVar lclass, lfunc; + LuaExtStack LS(L, lclass, lfunc); + + // Get the class table and closure + eng::string err = LS.getclass(lclass, classname); + if ((!err.empty()) || (!LS.istable(lclass))) { + return; + } + LS.rawget(lfunc, lclass, funcname); + if (!LS.isfunction(lfunc)) { + return; + } + + // Spawn a thread, pushing extra arguments from the datapack. + int nargs = 0; + try { + while (!datasb.empty()) { + push_simple_dynamic(L, &datasb); + nargs++; + } + } catch (const StreamException &exc) { + return; + } + spawn(LS, actor_id, place_id, lfunc, true, nargs, false); + } + run_scheduled_threads(); + assert(stack_is_clear()); +} + +void World::invoke_tick(int64_t actor_id, int64_t place_id, std::string_view datapack) { + if (!is_authoritative()) { + return; + } + clock_ += 1; + run_scheduled_threads(); +} + +void World::invoke_lua_source(int64_t actor_id, int64_t place_id, std::string_view datapack) { + if (!is_authoritative()) { + return; + } + // We need some kind of authentication here. + update_source(datapack); +} + +void World::guard_blockable(lua_State *L, const char *fn) { + if (lthread_thread_id_ == 0) { + // in a probe, blocking functions like http.get throw an error. + luaL_error(L, "cannot %s in a probe", fn); + assert(false); + } + if (!is_authoritative()) { + // in a nonauth model, blocking functions like http.get are converted to nopredict. + lua_yield(L, 0); + luaL_error(L, "unexplained nopredict failure in %s", fn); + assert(false); + } +} + +void World::guard_nopredict(lua_State *L, const char *fn) { + // Caution: this code must be equivalent to the + // code in LuaCoreStack::guard_nopredict. + if (lthread_thread_id_ == 0) { + return; + } + if (!is_authoritative()) { + lua_yield(L, 0); + luaL_error(L, "unexplained nopredict failure in %s", fn); + } +} + +void World::schedule(int64_t clk, int64_t thid, int64_t plid) { + if (clk > 0) { + assert(is_authoritative()); + } + thread_sched_.add(clk, thid, plid); +} + +void World::run_scheduled_threads() { + assert(stack_is_clear()); + lua_State *L = state(); + LuaVar tangibles, place, mt, threads, thinfo, actorid, isnew, useppool, thread, print; + LuaExtStack LS(L, tangibles, place, mt, threads, thinfo, actorid, isnew, useppool, thread, print); + + LS.rawget(tangibles, LuaRegistry, "tangibles"); + while (thread_sched_.ready(clock_)) { + SchedEntry sched = thread_sched_.pop(); + LS.rawget(place, tangibles, sched.place_id()); + if (!LS.istable(place)) { + continue; + } + LS.getmetatable(mt, place); + if (!LS.istable(mt)) { + continue; + } + LS.rawget(threads, mt, "threads"); + if (!LS.istable(threads)) { + continue; + } + LS.rawget(thinfo, threads, sched.thread_id()); + if (!LS.istable(thinfo)) { + continue; + } + LS.rawget(actorid, thinfo, "actorid"); + if (!LS.isnumber(actorid)) { + continue; + } + LS.rawget(isnew, thinfo, "isnew"); + if (!LS.isboolean(isnew)) { + continue; + } + LS.rawget(useppool, thinfo, "useppool"); + if (!LS.isboolean(useppool)) { + continue; + } + LS.rawget(thread, thinfo, "thread"); + if (!LS.isthread(thread)) { + continue; + } + + // Resume the coroutine. + lua_State *CO = LS.ckthread(thread); + open_lthread_state(LS.ckinteger(actorid), sched.place_id(), sched.thread_id(), LS.ckboolean(useppool), true); + int nargs = LS.ckboolean(isnew) ? (lua_gettop(CO) - 1) : lua_gettop(CO); + int status = lua_resume(CO, nullptr, nargs); + std::ostream *ostream = lthread_print_stream(); + + if (status == LUA_OK) { + // Successfully ran to completion. Print any return values. + // Remove from thread table. + LS.rawget(print, thinfo, "print"); + LS.rawset(threads, sched.thread_id(), LuaNil); + LuaCoreStack LSCO(CO); + if (LS.ckboolean(print)) { + for (int i = 1; i <= lua_gettop(CO); i++) { + pprint(LSCO, LuaSpecial(i), PrettyPrintOptions(), ostream); + (*ostream) << std::endl; + } + } + } else if (status == LUA_YIELD) { + if (is_authoritative()) { + LS.rawset(thinfo, "isnew", false); + LS.rawset(thinfo, "useppool", false); + } else { + // When a nonauthoritative model yields, for any reason, + // the thread is discarded. This is also used as a way to implement + // nopredict: the thread that wants to 'nopredict' just yields, + // knowing that this will cause it to be killed. + LS.rawset(threads, sched.thread_id(), LuaNil); + } + } else { + // Generated an error. Add a traceback, print, and kill the coroutine. + // Currently, the error is sent to the actor. That seems... not right in the long run. + if (is_authoritative()) { + traceback_coroutine(CO); + (*ostream) << lua_tostring(CO, -1); + } + LS.rawset(threads, sched.thread_id(), LuaNil); + } + close_lthread_state(); + } +} + +int64_t World::alloc_id_predictable() { + if (!lthread_use_ppool_) { + return id_global_pool_.get_one(); + } + Tangible *t = tangible_get(lthread_actor_id_); + if (t == nullptr) { + return id_global_pool_.get_one(); + } + return t->id_player_pool_.get_one(); +} + +const PrintBuffer *World::get_printbuffer(int64_t actor_id) { + Tangible *actor = tangible_get(actor_id); + if (actor != nullptr) { + return &actor->print_buffer_; + } + return nullptr; +} + +void World::clear_lthread_state() { + lthread_prints_.reset(); + lthread_actor_id_ = 0; + lthread_place_id_ = 0; + lthread_thread_id_ = 0; + lthread_use_ppool_ = false; +} + +void World::open_lthread_state(int64_t actor, int64_t place, int64_t thread, bool ppool, bool prints) { + lthread_actor_id_ = actor; + lthread_place_id_ = place; + lthread_thread_id_ = thread; + lthread_use_ppool_ = ppool; + if (prints) { + lthread_prints_.reset(new eng::ostringstream); + } else { + lthread_prints_.reset(); + } +} + +void World::close_lthread_state() { + // Copy prints from lthread_prints_ stringstream into + // the appropriate actor's PrintBuffer. + if (lthread_prints_ != nullptr) { + const eng::string &output = lthread_prints_->str(); + if (output.size() > 0) { + Tangible *actor = tangible_get(lthread_actor_id_); + if (actor != nullptr) { + actor->print_buffer_.add_string(output, is_authoritative()); + } + } + } + // Now clean up everything. + clear_lthread_state(); +} + +std::ostream *World::lthread_print_stream() const { + if (lthread_prints_ != nullptr) { + return lthread_prints_.get(); + } else { + return &std::cerr; + } +} + +void World::set_global(LuaCoreStack &LS0, const eng::string &gvar, LuaSlot value) { + lua_State *L = LS0.state(); + LuaVar globaldb, copy; + LuaExtStack LS(L, globaldb, copy); + + // Serialize then deserialize the data, to produce a copy. + StreamBuffer sb; + eng::string error = serialize_lua(LS, value, &sb); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + return; + } + eng::string serialized(sb.view()); + error = deserialize_lua(LS, copy, &sb); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + return; + } + + // Store the copy in the globalDB. + LS.rawget(globaldb, LuaRegistry, "globaldb"); + LS.rawset(globaldb, gvar, copy); + + // Store the serialized blob. + gvname_to_serial_[gvar] = serialized; + + // Implement the tracking so that we can rapidly determine which global + // variables need to be difference transmitted. + // + // In the master model, we generate a sequence number for the assignment. + // We store the mapping from global variable name to that sequence number + // and vice versa. + // + // On the client side, we just record the global variable in a list + // of recently modified globals. + // + if (is_authoritative()) { + int64_t &seqno = gvname_to_seqno_[gvar]; + seqno_to_gvname_.erase(seqno); + seqno = assign_seqno_++; + seqno_to_gvname_[seqno] = gvar; + } else { + gvname_modified_.insert(gvar); + } +} + + +void World::serialize(StreamBuffer *sb) { + assert(stack_is_clear()); + assert(redirects_.empty()); + // int64_t wc0 = sb->total_writes(); + lua_snap_.serialize(sb); + id_global_pool_.serialize(sb); + sb->write_int64(clock_); + thread_sched_.serialize(sb); + http_requests_.serialize(sb); + sb->write_uint32(tangibles_.size()); + for (const auto &p : tangibles_) { + sb->write_int64(p.first); + p.second->serialize(sb); + } + assert(stack_is_clear()); +} + +void World::deserialize(StreamBuffer *sb) { + assert(stack_is_clear()); + redirects_.clear(); + lua_snap_.deserialize(sb); + id_global_pool_.deserialize(sb); + clock_ = sb->read_int64(); + thread_sched_.deserialize(sb); + http_requests_.deserialize(sb); + // Mark all tangibles for deletion by setting ID to zero. + for (const auto &p : tangibles_) { + p.second->plane_item_.set_id(0); + } + // Deserialize tangibles. + size_t ntan = sb->read_uint32(); + for (size_t i = 0; i < ntan; i++) { + int64_t id = sb->read_int64(); + UniqueTangible &t = tangibles_[id]; + if (t == nullptr) { + t.reset(new Tangible(this, id)); + } else { + t->plane_item_.set_id(id); + } + t->deserialize(sb); + } + // Delete tangibles that didn't get deserialized. + for (auto iter = tangibles_.begin(); iter != tangibles_.end(); ) { + if (iter->second->plane_item_.id() == 0) { + tangibles_.erase(iter++); + } else { + ++iter; + } + } + // After a save and load, http requests no longer should exist + abort_all_http_requests(425, "http requests aborted by loading a save game"); + assert(stack_is_clear()); +} + +void World::snapshot() { + assert(snapshot_.empty()); + serialize(&snapshot_); + assert(!snapshot_.empty()); +} + +void World::rollback() { + assert(!snapshot_.empty()); + deserialize(&snapshot_); + assert(snapshot_.empty()); +} + +// This is the main routine for the DLL. We have to use a registration device +// to register this main routine with DrivenEngine. DrivenEngine will then call +// it exactly once the first time that the driver initializes an EngineWrapper. +// +void engine_initialization() { + SourceDB::register_lua_builtins(); + AnimQueue::initialize_module(); +} + +static DrivenEngineInitializerReg eireg(engine_initialization); diff --git a/luprex/cpp/core/world-difftab.cpp b/luprex/cpp/core/world-difftab.cpp new file mode 100644 index 00000000..881ff08f --- /dev/null +++ b/luprex/cpp/core/world-difftab.cpp @@ -0,0 +1,521 @@ +//////////////////////////////////////////////////////////////////// +// +// This file contains the code to compare the contents of tables. +// The top level functions in this file are: +// +// World::diff_numbered_tables +// World::diff_tangible_databases +// World::patch_numbered_tables +// World::patch_tangible_databases +// +// It also contains these unit testing support routines: +// +// table.diffcompare +// table.diffapply +// +// This file also contains all the support code needed to implement +// this stuff. +// +//////////////////////////////////////////////////////////////////// + +#include "luastack.hpp" +#include "streambuffer.hpp" +#include "table.hpp" +#include "world.hpp" + +// Given a table and an tnmap, return the table number of the table. +// Returns zero if the table doesn't have a table number. +// +static int get_table_number(LuaCoreStack &MLS, LuaSlot mval, LuaSlot mtnmap) { + lua_State *L = MLS.state(); + lua_pushvalue(L, mval.index()); + lua_rawget(L, mtnmap.index()); + int result = 0; + if (lua_type(L, -1) == LUA_TNUMBER) { + result = lua_tointeger(L, -1); + } + lua_pop(L, 1); + return result; +} + + +static bool equivalent_values(LuaCoreStack &MLS, LuaSlot mval, LuaSlot mtnmap, + LuaCoreStack &SLS, LuaSlot sval, LuaSlot stnmap) { + switch (MLS.xtype(mval)) { + case LUA_TBOOLEAN: { + if (SLS.type(sval) != LUA_TBOOLEAN) return false; + return MLS.tryboolean(mval) == SLS.tryboolean(sval); + } + case LUA_TNUMBER: { + if (SLS.type(sval) != LUA_TNUMBER) return false; + return MLS.trynumber(mval) == SLS.trynumber(sval); + } + case LUA_TSTRING: { + if (SLS.type(sval) != LUA_TSTRING) return false; + return MLS.trystring(mval) == SLS.trystring(sval); + } + case LUA_TLIGHTUSERDATA: { + if (SLS.type(sval) != LUA_TLIGHTUSERDATA) return false; + return MLS.trytoken(mval) == SLS.trytoken(sval); + } + case LUA_TFUNCTION: { + // Cannot really compare. Just return true if the types match. + return SLS.type(sval) == MLS.type(mval); + } + case LUA_TT_GENERAL: { + int midx = get_table_number(MLS, mval, mtnmap); + if (midx == 0) { + return SLS.isnil(sval); + } + int sidx = get_table_number(SLS, sval, stnmap); + return midx == sidx; + } + case LUA_TT_CLASS: { + if (SLS.xtype(sval) != LUA_TT_CLASS) return false; + // What if it's an ill-formed class? + return MLS.classname(mval) == SLS.classname(sval); + } + case LUA_TT_TANGIBLE: { + if (SLS.xtype(sval) != LUA_TT_TANGIBLE) return false; + // What if it's an ill-formed tangible? + return MLS.tanid(mval) == SLS.tanid(sval); + } + case LUA_TT_GLOBALENV: { + return (SLS.xtype(sval) == LUA_TT_GLOBALENV); + } + default: + // We're forcing anything else to go to NIL, + // and once it's NIL, we consider it 'equal'. + return SLS.type(sval) == LUA_TNIL; + } +} + +static void transmit_value(LuaCoreStack &MLS, LuaSlot mval, LuaSlot mtnmap, StreamBuffer *sb) { + switch (MLS.xtype(mval)) { + case LUA_TBOOLEAN: { + sb->write_uint8(LUA_TBOOLEAN); + sb->write_bool(*MLS.tryboolean(mval)); + return; + } + case LUA_TNUMBER: { + sb->write_uint8(LUA_TNUMBER); + sb->write_double(*MLS.trynumber(mval)); + return; + } + case LUA_TSTRING: { + sb->write_uint8(LUA_TSTRING); + sb->write_string(*MLS.trystring(mval)); + return; + } + case LUA_TLIGHTUSERDATA: { + sb->write_uint8(LUA_TLIGHTUSERDATA); + sb->write_uint64((*MLS.trytoken(mval)).value); + return; + } + case LUA_TT_GENERAL: { + int midx = get_table_number(MLS, mval, mtnmap); + if (midx == 0) { + sb->write_uint8(LUA_TNIL); + } else { + sb->write_uint8(LUA_TT_GENERAL); + sb->write_uint32(midx); + } + return; + } + case LUA_TT_CLASS: { + sb->write_uint8(LUA_TT_CLASS); + sb->write_string(MLS.classname(mval)); + return; + } + case LUA_TT_TANGIBLE: { + sb->write_uint8(LUA_TT_TANGIBLE); + sb->write_int64(MLS.tanid(mval)); + return; + } + case LUA_TT_GLOBALENV: { + sb->write_uint8(LUA_TT_GLOBALENV); + return; + } + default: + sb->write_uint8(LUA_TNIL); + return; + } +} + +static void transmit_value_debug_string(StreamBuffer *sb, eng::ostringstream &oss) { + int kind = sb->read_uint8(); + switch (kind) { + case LUA_TBOOLEAN: { + bool b = sb->read_bool(); + oss << (b ? "true":"false"); + return; + } + case LUA_TNUMBER: { + oss << sb->read_double(); + return; + } + case LUA_TSTRING: { + oss << sb->read_string(); + return; + } + case LUA_TLIGHTUSERDATA: { + LuaToken token(sb->read_uint64()); + oss << "[" << token.str() << "]"; + } + case LUA_TT_GENERAL: { + oss << "table " << sb->read_int32(); + return; + } + case LUA_TT_CLASS: { + oss << "class " << sb->read_string(); + return; + } + case LUA_TT_TANGIBLE: { + oss << "tan " << sb->read_int64(); + return; + } + case LUA_TT_GLOBALENV: { + oss << "globals"; + return; + } + case LUA_TNIL: { + oss << "nil"; + return; + } + default: + assert(false); // Should not get here. + } +} + +static bool diff_tables(LuaCoreStack &SLS0, LuaSlot stnmap, LuaSlot stab, + LuaCoreStack &MLS0, LuaSlot mtnmap, LuaSlot mtab, + bool cmeta, StreamBuffer *sb) { + LuaVar skey, mkey, sval, mval, mnil; + LuaExtStack SLS(SLS0.state(), skey, sval); + LuaExtStack MLS(MLS0.state(), mkey, mval, mnil); + assert(MLS.istable(mtab)); + assert(SLS.istable(stab)); + MLS.set(mnil, LuaNil); + int nupdates = 0; + + sb->write_int32(0); + int wc = sb->total_writes(); + + MLS.set(mkey, LuaNil); + while (MLS.next(mtab, mkey, mval)) { + if (!MLS.issortablekey(mkey)) continue; + MLS.movesortablekey(mkey, SLS, skey); + SLS.rawget(sval, stab, skey); + if (!equivalent_values(MLS, mval, mtnmap, SLS, sval, stnmap)) { + transmit_value(MLS, mkey, mtnmap, sb); + transmit_value(MLS, mval, mtnmap, sb); + nupdates += 1; + } + } + + SLS.set(skey, LuaNil); + while (SLS.next(stab, skey, sval)) { + if (!SLS.issortablekey(skey)) continue; + SLS.movesortablekey(skey, MLS, mkey); + MLS.rawget(mval, mtab, mkey); + if (MLS.isnil(mval)) { + transmit_value(MLS, mkey, mtnmap, sb); + transmit_value(MLS, mval, mtnmap, sb); + nupdates += 1; + } + } + + if (cmeta) { + SLS.getmetatable(sval, stab); + MLS.getmetatable(mval, mtab); + if (!equivalent_values(MLS, mval, mtnmap, SLS, sval, stnmap)) { + transmit_value(MLS, mnil, mtnmap, sb); + transmit_value(MLS, mval, mtnmap, sb); + nupdates += 1; + } + } + + sb->overwrite_int32(wc, nupdates); + return (nupdates > 0); +} + +static eng::string diff_tables_debug_string(StreamBuffer *sb) { + eng::vector sorted; + eng::ostringstream oss; + int ndiffs = sb->read_int32(); + for (int i = 0; i < ndiffs; i++) { + transmit_value_debug_string(sb, oss); + oss << "="; + transmit_value_debug_string(sb, oss); + sorted.push_back(oss.str()); + oss.str(""); + } + std::sort(sorted.begin(), sorted.end()); + for (const eng::string &s : sorted) { + oss << s << ";"; + } + return oss.str(); +} + +static void set_transmitted_value(LuaCoreStack &LS, LuaSlot tangibles, LuaSlot ntmap, LuaSlot target, StreamBuffer *sb, const char *dbinfo, DebugCollector *dbc) { + int kind = sb->read_uint8(); + switch (kind) { + case LUA_TBOOLEAN: { + bool value = sb->read_bool(); + DebugLine(dbc) << dbinfo << (value ? "true" : "false"); + LS.set(target, value); + return; + } + case LUA_TNUMBER: { + double value = sb->read_double(); + DebugLine(dbc) << dbinfo << value; + LS.set(target, value); + return; + } + case LUA_TSTRING: { + eng::string value = sb->read_string(); + DebugLine(dbc) << dbinfo << "'" << value << "'"; + LS.set(target, value); + return; + } + case LUA_TLIGHTUSERDATA: { + LuaToken value(sb->read_uint64()); + DebugLine(dbc) << dbinfo << "[" << value.str() << "]"; + LS.set(target, value); + return; + } + case LUA_TT_GENERAL: { + int index = sb->read_int32(); + DebugLine(dbc) << dbinfo << "table " << index; + LS.rawget(target, ntmap, index); + return; + } + case LUA_TT_CLASS: { + eng::string value = sb->read_string(); + DebugLine(dbc) << dbinfo << "class " << value; + LS.makeclass(target, value); + return; + } + case LUA_TT_TANGIBLE: { + int64_t id = sb->read_int64(); + DebugLine(dbc) << dbinfo << "tan " << id; + LS.maketan(target, id); + return; + } + case LUA_TT_GLOBALENV: { + DebugLine(dbc) << dbinfo << "global env"; + LS.getglobaltable(target); + return; + } + case LUA_TNIL: { + DebugLine(dbc) << dbinfo << "nil"; + LS.set(target, LuaNil); + return; + } + default: + assert(false); // Should not get here. + } +} + +static void patch_table(LuaCoreStack &LS0, LuaSlot tangibles, LuaSlot ntmap, LuaSlot tab, StreamBuffer *sb, DebugCollector *dbc) { + LuaVar key, val; + LuaExtStack LS(LS0.state(), key, val); + int ndiffs = sb->read_int32(); + for (int i = 0; i < ndiffs; i++) { + set_transmitted_value(LS, tangibles, ntmap, key, sb, "key=", dbc); + set_transmitted_value(LS, tangibles, ntmap, val, sb, "val=", dbc); + if (LS.isnil(key)) { + LS.setmetatable(tab, val); + } else { + LS.rawset(tab, key, val); + } + } +} + +void World::patch_numbered_tables(StreamBuffer *sb, DebugCollector *dbc) { + lua_State *L = state(); + LuaVar tangibles, ntmap, tab; + LuaExtStack LS(L, tangibles, ntmap, tab); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(ntmap, LuaRegistry, "ntmap"); + assert(LS.istable(tangibles)); + assert(LS.istable(ntmap)); + + int nmodified = sb->read_int32(); + for (int i = 0; i < nmodified; i++) { + int index = sb->read_int32(); + LS.rawget(tab, ntmap, index); + assert(LS.istable(tab)); + DebugHeader(dbc) << "Lua Table " << index << ":"; + patch_table(LS, tangibles, ntmap, tab, sb, dbc); + } +} + +void World::diff_numbered_tables(lua_State *master, StreamBuffer *sb) { + lua_State *synch = state(); + LuaVar sntmap, mntmap, stnmap, mtnmap, stab, mtab; + LuaExtStack SLS(synch, sntmap, stnmap, stab); + LuaExtStack MLS(master, mntmap, mtnmap, mtab); + SLS.rawget(sntmap, LuaRegistry, "ntmap"); + MLS.rawget(mntmap, LuaRegistry, "ntmap"); + SLS.rawget(stnmap, LuaRegistry, "tnmap"); + MLS.rawget(mtnmap, LuaRegistry, "tnmap"); + int m_ntables = MLS.rawlen(mntmap); + int s_ntables = SLS.rawlen(sntmap); + assert(m_ntables == s_ntables); + + sb->write_int32(0); + int write_count_after = sb->total_writes(); + int nmodified = 0; + int s_top = lua_gettop(synch); + int m_top = lua_gettop(master); + for (int id = 1; id <= m_ntables; id++) { + MLS.rawget(mtab, mntmap, id); + if (MLS.istable(mtab)) { + SLS.rawget(stab, sntmap, id); + assert(SLS.istable(stab)); + int tw = sb->total_writes(); + sb->write_int32(id); + nmodified += 1; + if (!diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, sb)) { + sb->unwrite_to(tw); + nmodified -= 1; + } + } + assert(lua_gettop(synch) == s_top); + assert(lua_gettop(master) == m_top); + } + sb->overwrite_int32(write_count_after, nmodified); +} + +void World::patch_tangible_databases(StreamBuffer *sb, DebugCollector *dbc) { + lua_State *L = state(); + LuaVar tangibles, ntmap, tab; + LuaExtStack LS(L, tangibles, ntmap, tab); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(ntmap, LuaRegistry, "ntmap"); + assert(LS.istable(tangibles)); + assert(LS.istable(ntmap)); + + int nmodified = sb->read_int32(); + for (int i = 0; i < nmodified; i++) { + int64_t id = sb->read_int64(); + LS.rawget(tab, tangibles, id); + assert(LS.istable(tab)); + DebugHeader(dbc) << "Tangible DB " << id << ":"; + patch_table(LS, tangibles, ntmap, tab, sb, dbc); + } +} + +void World::diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb) { + lua_State *synch = state(); + LuaVar stnmap, mtnmap, stangibles, mtangibles, stab, mtab; + LuaExtStack SLS(synch, stnmap, stangibles, stab); + LuaExtStack MLS(master, mtnmap, mtangibles, mtab); + SLS.rawget(stnmap, LuaRegistry, "tnmap"); + MLS.rawget(mtnmap, LuaRegistry, "tnmap"); + SLS.rawget(stangibles, LuaRegistry, "tangibles"); + MLS.rawget(mtangibles, LuaRegistry, "tangibles"); + + sb->write_int32(0); + int write_count_after = sb->total_writes(); + int nmodified = 0; + int s_top = lua_gettop(synch); + int m_top = lua_gettop(master); + for (int64_t id : basis) { + MLS.rawget(mtab, mtangibles, id); + SLS.rawget(stab, stangibles, id); + assert(MLS.istable(mtab)); + assert(SLS.istable(stab)); + int tw = sb->total_writes(); + sb->write_int64(id); + nmodified += 1; + if (!diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, false, sb)) { + sb->unwrite_to(tw); + nmodified -= 1; + } + assert(lua_gettop(synch) == s_top); + assert(lua_gettop(master) == m_top); + } + sb->overwrite_int32(write_count_after, nmodified); +} + +LuaDefine(table_diffcompare, "mtnmap,mtab,stnmap,stab", "for unit testing only") { + LuaArg mtnmap, mtab, mstnmap, mstab; + LuaRet dbgstring; + LuaVar tthread; + LuaDefStack MLS(L, mtnmap, mtab, mstnmap, mstab, dbgstring, tthread); + // Check the arguments. + MLS.cktable(mtnmap, "mtnmap"); + MLS.cktable(mstnmap, "mstnmap"); + MLS.cktable(mtab, "mtab"); + MLS.cktable(mstab, "mstab"); + + // Create a temporary thread to be the 'synch model'. We'll use the + // existing thread as the 'master model'. Move two tables to the synch thread. + lua_State *synch = lua_newthread(L); + lua_replace(L, tthread.index()); + lua_pushvalue(L, mstnmap.index()); + lua_pushvalue(L, mstab.index()); + lua_xmove(L, synch, 2); + LuaArg stnmap,stab; + LuaDefStack SLS(synch, stnmap, stab); + + // Call tablecmp_diff. + StreamBuffer sb; + diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, &sb); + + // Convert the output to a debug string. + MLS.set(dbgstring, diff_tables_debug_string(&sb)); + return MLS.result(); +} + +LuaDefine(table_diffapply, "mtnmap,mtab,mstab", "for unit testing only") { + LuaArg mtnmap, mtab, mstab; + LuaRet eql, eqlstr, rtab; + LuaVar tthread, tangibles, mntmap, key, val; + LuaDefStack MLS(L, mtnmap, mtab, mstab, eql, eqlstr, rtab, tthread, tangibles, mntmap, key, val); + // Check the arguments. + MLS.cktable(mtnmap, "mtnmap"); + MLS.cktable(mtab, "mtab"); + MLS.cktable(mstab, "mstab"); + + // Get the tangibles map. + MLS.rawget(tangibles, LuaRegistry, "tangibles"); + + // Invert the tnmap to make the ntmap. + MLS.set(mntmap, LuaNewTable); + MLS.set(key, LuaNil); + while (MLS.next(mtnmap, key, val)) { + MLS.rawset(mntmap, val, key); + } + + // Create a temporary thread to be the 'synch model'. We'll use the + // existing thread as the 'master model'. + lua_State *synch = lua_newthread(L); + lua_replace(L, tthread.index()); + lua_pushvalue(L, mtnmap.index()); + lua_pushvalue(L, mstab.index()); + lua_xmove(L, synch, 2); + LuaArg stnmap, stab; + LuaDefStack SLS(synch, stnmap, stab); + + // Call diff_tables and patch_tables + StreamBuffer sb; + diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, &sb); + + patch_table(MLS, tangibles, mntmap, mstab, &sb, nullptr); + + bool eq = table_equal(MLS, mstab, mtab); + MLS.set(eql, eq); + if (eq) { + MLS.set(eqlstr, "tables equal"); + } else { + MLS.set(eqlstr, "tables were supposed to be equal"); + } + MLS.set(rtab, stab); + + return MLS.result(); +} + + diff --git a/luprex/cpp/core/world-diffxmit.cpp b/luprex/cpp/core/world-diffxmit.cpp new file mode 100644 index 00000000..d86a26e8 --- /dev/null +++ b/luprex/cpp/core/world-diffxmit.cpp @@ -0,0 +1,400 @@ + +#include "world.hpp" +#include "serializelua.hpp" + +util::IdVector World::get_visible_union(int64_t actor_id, World *master) { + util::IdVector v1, v2; + master->get_near(actor_id, RadiusVisibility, true, false, false, &v1); + get_near(actor_id, RadiusVisibility, true, false, false, &v2); + return util::sort_union_id_vectors(v1, v2); +} + +int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) { + DebugBlock dbb(dbc, "patch_actor"); + int64_t actor_id = sb->read_int64(); + Tangible *s_actor = tangible_get(actor_id); + if (s_actor == nullptr) { + DebugLine(dbc) << "create new actor " << actor_id; + s_actor = tangible_make(actor_id); + s_actor->id_player_pool_.deserialize(sb); + s_actor->anim_queue_.deserialize(sb); + s_actor->print_buffer_.deserialize(sb); + } else { + DebugHeader(dbc) << "patching actor " << actor_id << ":"; + s_actor->id_player_pool_.patch(sb, dbc); + s_actor->anim_queue_.patch(sb, dbc); + s_actor->print_buffer_.patch(sb, dbc); + } + s_actor->update_plane_item(); + return actor_id; +} + +void World::diff_actor(int64_t actor_id, World *master, StreamBuffer *xsb) { + StreamBuffer tsb; + + // Get the actor in both models. s_actor may be nil. + const Tangible *s_actor = tangible_get(actor_id); + const Tangible *m_actor = master->tangible_get(actor_id); + assert(m_actor != nullptr); + + // Calculate diffs. + tsb.write_int64(actor_id); + if (s_actor == nullptr) { + m_actor->id_player_pool_.serialize(&tsb); + m_actor->anim_queue_.serialize(&tsb); + m_actor->print_buffer_.serialize(&tsb); + } else { + s_actor->id_player_pool_.diff(m_actor->id_player_pool_, &tsb); + s_actor->anim_queue_.diff(m_actor->anim_queue_, &tsb); + s_actor->print_buffer_.diff(m_actor->print_buffer_, &tsb); + } + + // Forward to client, and apply to server-synchronous. + tsb.copy_into(xsb); + patch_actor(&tsb, nullptr); + assert(tsb.empty()); +} + +void World::patch_visible(StreamBuffer *sb, DebugCollector *dbc) { + DebugBlock dbb(dbc, "patch_visible"); + // Receive create messages. + int count = sb->read_int32(); + for (int i = 0; i < count; i++) { + int64_t id = sb->read_int64(); + DebugLine(dbc) << "patch_visible create tan " << id; + Tangible *t = tangible_make(id); + t->anim_queue_.deserialize(sb); + t->update_plane_item(); + } + + // Receive delete messages + count = sb->read_int32(); + for (int i = 0; i < count; i++) { + int64_t id = sb->read_int64(); + DebugLine(dbc) << "patch_visible delete tan " << id; + tangible_delete(id); + } + + // Receive update messages + count = sb->read_int32(); + for (int i = 0; i < count; i++) { + int64_t id = sb->read_int64(); + DebugLine(dbc) << "patch_visible animqueue tan " << id; + Tangible *t = tangible_get(id); + assert(t != nullptr); + t->anim_queue_.patch(sb, dbc); + t->update_plane_item(); + } +} + +void World::diff_visible(const util::IdVector &visible, World *master, + StreamBuffer *xsb) { + StreamBuffer tsb; + + // Get the specified tangibles in both models. + // Some tangibles may be missing in the master, some may be missing in the + // sync. + TanVector mvis = master->tangible_get_all(visible); + TanVector svis = tangible_get_all(visible); + assert(mvis.size() == svis.size()); + + // For each tangible that exists in the master, but not + // in the synchronous model, send a create message. + tsb.write_int32(0); + int64_t count_pos = tsb.total_writes(); + int count = 0; + for (int i = 0; i < int(svis.size()); i++) { + const Tangible *mt = mvis[i]; + const Tangible *st = svis[i]; + if ((st == nullptr) && (mt != nullptr)) { + count += 1; + tsb.write_int64(mt->id()); + mt->anim_queue_.serialize(&tsb); + } + } + tsb.overwrite_int32(count_pos, count); + + // For each tangible present in the synchronous model that doesn't + // exist in the master model, send command to delete it. + tsb.write_int32(0); + count_pos = tsb.total_writes(); + count = 0; + for (int i = 0; i < int(svis.size()); i++) { + const Tangible *mt = mvis[i]; + const Tangible *st = svis[i]; + if ((mt == nullptr) && (st != nullptr)) { + count += 1; + tsb.write_int64(st->id()); + } + } + tsb.overwrite_int32(count_pos, count); + + // For each tangible present in both models, compare + // the animation queues. + tsb.write_int32(0); + count_pos = tsb.total_writes(); + count = 0; + for (int i = 0; i < int(svis.size()); i++) { + const Tangible *mt = mvis[i]; + const Tangible *st = svis[i]; + if ((mt != nullptr) && (st != nullptr)) { + int64_t unwind = tsb.total_writes(); + tsb.write_int64(st->id()); + if (st->anim_queue_.diff(mt->anim_queue_, &tsb)) { + count++; + } else { + tsb.unwrite_to(unwind); + } + } + } + tsb.overwrite_int32(count_pos, count); + + // Forward to client, and apply to server-synchronous. + tsb.copy_into(xsb); + patch_visible(&tsb, nullptr); + assert(tsb.empty()); + + // Copy the version number from master animqueue to synch animqueue. + for (int i = 0; i < int(mvis.size()); i++) { + const Tangible *m_tan = mvis[i]; + if (m_tan != nullptr) { + Tangible *s_tan = const_cast(svis[i]); + if (s_tan == nullptr) { + s_tan = tangible_get(m_tan->id()); + assert(s_tan != nullptr); + } + } + } +} + +void World::patch_luatabs(StreamBuffer *sb, DebugCollector *dbc) { + DebugBlock dbb(dbc, "patch_luatabs"); + int64_t actor_id = sb->read_int64(); + util::HashValue closehash = sb->read_hashvalue(); + int ncreate = sb->read_int32(); + util::IdVector closetans; + get_near(actor_id, RadiusClose, true, false, true, &closetans); + assert(closehash == util::hash_id_vector(closetans)); + number_lua_tables(closetans); + create_new_tables(ncreate); + // DebugLine(dbc) << "lua tables: " << nt << " existing " << ncreate << " + // new"; + patch_tangible_databases(sb, dbc); + patch_numbered_tables(sb, dbc); + unnumber_lua_tables(); +} + +void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) { + StreamBuffer tsb; + + // Calculate the set of close tangibles. + util::IdVector mclosetans, sclosetans; + master->get_near(actor_id, RadiusClose, true, false, true, &mclosetans); + get_near(actor_id, RadiusClose, true, false, true, &sclosetans); + assert(mclosetans == sclosetans); + util::HashValue closehash = util::hash_id_vector(mclosetans); + + // Number and pair tables in the synchronous and master model. + number_lua_tables(mclosetans); + pair_lua_tables(mclosetans, master->state()); + int ncreate = number_remaining_tables(mclosetans, master->state()); + create_new_tables(ncreate); + + // Difference transmit. + tsb.write_int64(actor_id); + tsb.write_hashvalue(closehash); + tsb.write_int32(ncreate); + diff_tangible_databases(mclosetans, master->state(), &tsb); + diff_numbered_tables(master->state(), &tsb); + + // Forward to client, and apply to server-synchronous. + tsb.copy_into(xsb); + assert(tsb.read_int64() == actor_id); + assert(tsb.read_hashvalue() == closehash); + assert(tsb.read_int32() == ncreate); + patch_tangible_databases(&tsb, nullptr); + patch_numbered_tables(&tsb, nullptr); + assert(tsb.empty()); + + // Unnumber tables in both models. + unnumber_lua_tables(); + master->unnumber_lua_tables(); +} + +void World::patch_tanclass(StreamBuffer *sb, DebugCollector *dbc) { + DebugBlock dbb(dbc, "patch_tanclass"); + lua_State *L = state(); + LuaVar tangibles, tab, meta, sclass; + LuaExtStack LS(L, tangibles, tab, meta, sclass); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + + int nmodified = sb->read_int32(); + for (int i = 0; i < nmodified; i++) { + int64_t id = sb->read_int64(); + LS.rawget(tab, tangibles, id); + assert(LS.istable(tab)); + LS.getmetatable(meta, tab); + eng::string name = sb->read_string(); + DebugLine(dbc) << "tanclass " << id << "=" << name; + if (name == "") { + LS.rawset(meta, "__index", LuaNil); + } else { + LS.makeclass(sclass, name); + LS.rawset(meta, "__index", sclass); + } + } +} + +void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) { + StreamBuffer tsb; + + { + LuaVar stangibles, mtangibles, stab, mtab, smeta, mmeta, sclass, mclass; + LuaExtStack SLS(state(), stangibles, stab, smeta, sclass); + LuaExtStack MLS(master->state(), mtangibles, mtab, mmeta, mclass); + SLS.rawget(stangibles, LuaRegistry, "tangibles"); + MLS.rawget(mtangibles, LuaRegistry, "tangibles"); + + // Calculate the set of close tangibles. + // TODO: we've already calculated this in an earlier function. This is + // wasteful. + util::IdVector closetans; + master->get_near(actor_id, RadiusClose, true, false, true, &closetans); + + tsb.write_int32(0); + int write_count_after = tsb.total_writes(); + int nmodified = 0; + for (int64_t id : closetans) { + MLS.rawget(mtab, mtangibles, id); + SLS.rawget(stab, stangibles, id); + MLS.getmetatable(mmeta, mtab); + SLS.getmetatable(smeta, stab); + MLS.rawget(mclass, mmeta, "__index"); + SLS.rawget(sclass, smeta, "__index"); + eng::string mname = MLS.classname(mclass); + eng::string sname = SLS.classname(sclass); + if (mname != sname) { + tsb.write_int64(id); + tsb.write_string(mname); + nmodified += 1; + } + } + tsb.overwrite_int32(write_count_after, nmodified); + } + + // Forward to client, and apply to server-synchronous. + tsb.copy_into(xsb); + patch_tanclass(&tsb, nullptr); + assert(tsb.empty()); +} + +void World::patch_source(StreamBuffer *sb, DebugCollector *dbc) { + DebugBlock dbb(dbc, "patch_source"); + bool modified = source_db_.patch(sb, dbc); + if (modified) { + rebuild_sourcedb(); + DebugLine(dbc) << "Source DB rebuilt"; + } +} + +void World::diff_source(World *master, StreamBuffer *sb) { + StreamBuffer tsb; + source_db_.diff(master->source_db_, &tsb); + tsb.copy_into(sb); + patch_source(&tsb, nullptr); + assert(tsb.empty()); +} + +const eng::string &World::get_gvname_serial(const eng::string &gvar) { + static eng::string empty; + auto iter = gvname_to_serial_.find(gvar); + if (iter == gvname_to_serial_.end()) { + return empty; + } else { + return iter->second; + } +} + +void World::patch_globals(StreamBuffer *sb, DebugCollector *dbc) { + DebugBlock dbb(dbc, "patch_globals"); + int64_t seqno = sb->read_int64(); + int32_t total = sb->read_int32(); + if (total > 0) { + lua_State *L = state(); + LuaVar globaldb, copy; + LuaExtStack LS(L, globaldb, copy); + LS.rawget(globaldb, LuaRegistry, "globaldb"); + for (int i = 0; i < total; i++) { + eng::string gvar = sb->read_string(); + eng::string serial = sb->read_string(); + gvname_to_serial_[gvar] = serial; + StreamBuffer sb(serial); + eng::string error = deserialize_lua(LS, copy, &sb); + if (error.empty()) { + LS.rawset(globaldb, gvar, copy); + } else { + DebugLine(dbc) << "Invalid global serialized data: " << gvar << ":" << error; + } + } + } + assign_seqno_ = seqno; + gvname_to_seqno_.clear(); + seqno_to_gvname_.clear(); + gvname_modified_.clear(); +} + +void World::diff_globals(World *master, StreamBuffer *sb) { + StreamBuffer tsb; + tsb.write_int64(master->assign_seqno_); + tsb.write_int32(0); + int64_t count_pos = tsb.total_writes(); + int32_t total_mods = 0; + for (const eng::string &gvar : gvname_modified_) { + const eng::string &mval = master->get_gvname_serial(gvar); + const eng::string &sval = get_gvname_serial(gvar); + if (mval != sval) { + total_mods += 1; + tsb.write_string(gvar); + tsb.write_string(mval); + } + } + auto iter = master->seqno_to_gvname_.lower_bound(assign_seqno_); + while (iter != master->seqno_to_gvname_.end()) { + const auto &gvar = iter->second; + if (gvname_modified_.find(gvar) == gvname_modified_.end()) { + const eng::string &mval = master->get_gvname_serial(gvar); + const eng::string &sval = get_gvname_serial(gvar); + if (mval != sval) { + total_mods += 1; + tsb.write_string(gvar); + tsb.write_string(mval); + } + } + iter++; + } + tsb.overwrite_int32(count_pos, total_mods); + tsb.copy_into(sb); + patch_globals(&tsb, nullptr); + assert(tsb.empty()); +} + +int64_t World::patch_everything(StreamBuffer *sb, DebugCollector *dbc) { + DebugBlock dbb(dbc, "patch_everything"); + int64_t actor_id = patch_actor(sb, dbc); + patch_visible(sb, dbc); + patch_luatabs(sb, dbc); + patch_tanclass(sb, dbc); + patch_source(sb, dbc); + patch_globals(sb, dbc); + return actor_id; +} + +void World::diff_everything(int64_t actor_id, World *master, StreamBuffer *sb) { + diff_actor(actor_id, master, sb); + util::IdVector visible = get_visible_union(actor_id, master); + diff_visible(visible, master, sb); + diff_luatabs(actor_id, master, sb); + diff_tanclass(actor_id, master, sb); + diff_source(master, sb); + diff_globals(master, sb); +} diff --git a/luprex/cpp/core/world-pairtab.cpp b/luprex/cpp/core/world-pairtab.cpp new file mode 100644 index 00000000..a5b03559 --- /dev/null +++ b/luprex/cpp/core/world-pairtab.cpp @@ -0,0 +1,268 @@ +//////////////////////////////////////////////////////////////////// +// +// This file contains the code to pair up tables in the synchronous +// model with tables in the master model. Here are the top-level +// routines in this file: +// +// World::number_lua_tables +// World::pair_lua_tables +// World::number_remaining_tables +// World::create_new_tables +// World::unnumber_lua_tables +// +//////////////////////////////////////////////////////////////////// + +#include "luastack.hpp" +#include "streambuffer.hpp" +#include "world.hpp" + +int World::number_lua_tables(const IdVector &basis) { + // This is conceptually recursive, but we're going to use an + // explicit stack (the lua stack). + lua_State *L = state(); + int nextid = 1; + { + LuaVar tnmap, ntmap, tangibles, tab, key, val, xid; + LuaExtStack LS(L, tnmap, ntmap, tangibles, tab, key, val, xid); + LS.set(tnmap, LuaNewTable); + LS.set(ntmap, LuaNewTable); + LS.rawset(LuaRegistry, "tnmap", tnmap); + LS.rawset(LuaRegistry, "ntmap", ntmap); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + int top = lua_gettop(L); + + // Push all subtables onto the stack. Note that we may push + // the same table twice, that's OK. + for (int64_t id : basis) { + LS.rawget(tab, tangibles, id); + assert(LS.istable(tab)); + // Traverse subtables. + LS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { + lua_checkstack(L, 10); + lua_pushvalue(L, val.index()); + } + } + } + + // Pop tables from the stack one by one. If the table is not + // already numbered, number it and push subtables onto the stack. + while (lua_gettop(L) > top) { + lua_replace(L, tab.index()); + LS.rawget(xid, tnmap, tab); + if (LS.isnil(xid)) { + int id = nextid++; + LS.rawset(tnmap, tab, id); + LS.rawset(ntmap, id, tab); + // Traverse the metatable. + LS.getmetatable(val, tab); + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { + lua_checkstack(L, 10); + lua_pushvalue(L, val.index()); + } + // Traverse the subtables. + LS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { + lua_checkstack(L, 10); + lua_pushvalue(L, val.index()); + } + } + } + } + } + + assert(stack_is_clear()); + return nextid - 1; +} + +void World::pair_lua_tables(const IdVector &basis, lua_State *master) { + lua_State *synch = state(); + LuaVar stangibles, mtangibles, sntmap, mntmap, stnmap, mtnmap, stab, mtab, skey, mkey, sval, mval, sidx, midx; + LuaExtStack SLS(synch, stangibles, stab, skey, sval, sntmap, stnmap, sidx); + LuaExtStack MLS(master, mtangibles, mtab, mkey, mval, mntmap, mtnmap, midx); + + // Fetch the tangible databases + SLS.rawget(stangibles, LuaRegistry, "tangibles"); + MLS.rawget(mtangibles, LuaRegistry, "tangibles"); + + // Fetch the synchronous model tnmap and ntmap + SLS.rawget(stnmap, LuaRegistry, "tnmap"); + SLS.rawget(sntmap, LuaRegistry, "ntmap"); + assert(SLS.istable(stnmap)); + assert(SLS.istable(sntmap)); + + // Initialize the master model tnmap and ntmap + MLS.set(mtnmap, LuaNewTable); + MLS.set(mntmap, LuaNewTable); + MLS.rawset(LuaRegistry, "tnmap", mtnmap); + MLS.rawset(LuaRegistry, "ntmap", mntmap); + int s_ntables = SLS.rawlen(sntmap); + for (int i = 1; i <= s_ntables; i++) { + MLS.rawset(mntmap, i, 0); + } + + // Keep track of which tables are already paired + eng::vector paired; + paired.assign(s_ntables + 1, false); + + // This records the top of the stack. + int mtop = lua_gettop(master); + + for (int64_t id : basis) { + MLS.rawget(mtab, mtangibles, id); + SLS.rawget(stab, stangibles, id); + assert(MLS.istable(mtab)); + assert(SLS.istable(stab)); + MLS.set(mkey, LuaNil); + while (MLS.next(mtab, mkey, mval)) { + if (!MLS.issortablekey(mkey)) continue; + if (!MLS.istable(mval)) continue; + MLS.movesortablekey(mkey, SLS, skey); + SLS.rawget(sval, stab, skey); + if (!SLS.istable(sval)) continue; + lua_checkstack(master, 20); + lua_checkstack(synch, 20); + lua_pushvalue(master, mval.index()); + lua_pushvalue(synch, sval.index()); + } + } + + while (lua_gettop(master) > mtop) { + lua_replace(master, mtab.index()); + lua_replace(synch, stab.index()); + // If the master table is not a general table, skip. + if (MLS.gettabletype(mtab) != LUA_TT_GENERAL) continue; + // If the master table is already paired, skip. + MLS.rawget(midx, mtnmap, mtab); + if (MLS.isnumber(midx)) continue; + // If the synch table doesn't have a number, skip. + SLS.rawget(sidx, stnmap, stab); + auto idx = SLS.trynumber(sidx); + if (!idx) continue; + assert((*idx >= 1) && (*idx <= s_ntables)); + // Pair the tables. + MLS.rawset(mtnmap, mtab, *idx); + MLS.rawset(mntmap, *idx, mtab); + paired[*idx] = true; + // Potentially pair the metatables. + MLS.getmetatable(mval, mtab); + if (MLS.istable(mval)) { + SLS.getmetatable(sval, stab); + if (SLS.istable(sval)) { + lua_pushvalue(master, mval.index()); + lua_pushvalue(synch, sval.index()); + } + } + // Pair the subtables. + MLS.set(mkey, LuaNil); + while (MLS.next(mtab, mkey, mval)) { + if (!MLS.issortablekey(mkey)) continue; + if (!MLS.istable(mval)) continue; + MLS.movesortablekey(mkey, SLS, skey); + SLS.rawget(sval, stab, skey); + if (!SLS.istable(sval)) continue; + lua_checkstack(master, 20); + lua_checkstack(synch, 20); + lua_pushvalue(master, mval.index()); + lua_pushvalue(synch, sval.index()); + } + } +} + +int World::number_remaining_tables(const IdVector &basis, lua_State *master) { + // This is conceptually recursive, but we're going to use an + // explicit stack (the lua stack). + eng::vector visited; + int ntables; + { + lua_State *L = master; + LuaVar tnmap, ntmap, tangibles, tab, key, val, xid; + LuaExtStack LS(L, tnmap, ntmap, tangibles, tab, key, val, xid); + LS.rawget(tnmap, LuaRegistry, "tnmap"); + LS.rawget(ntmap, LuaRegistry, "ntmap"); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + ntables = LS.rawlen(ntmap); + visited.assign(ntables + 1, false); + int top = lua_gettop(L); + + // Push all subtables onto the stack. Note that we may push + // the same table twice, that's OK. + for (int64_t id : basis) { + LS.rawget(tab, tangibles, id); + assert(LS.istable(tab)); + LS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { + lua_checkstack(L, 10); + lua_pushvalue(L, val.index()); + } + } + } + + // Pop tables from the stack one by one. If the table is not + // numbered, number it. If it is not visited, visit it. + while (lua_gettop(L) > top) { + lua_replace(L, tab.index()); + int id = 0; + LS.rawget(xid, tnmap, tab); + if (!LS.isnumber(xid)) { + id = visited.size(); + LS.rawset(tnmap, tab, id); + LS.rawset(ntmap, id, tab); + visited.push_back(false); + } else { + id = LS.cknumber(xid); + assert((id >= 0) && (id < int(visited.size()))); + } + if (!visited[id]) { + visited[id] = true; + // Traverse the metatable. + LS.getmetatable(val, tab); + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { + lua_checkstack(L, 10); + lua_pushvalue(L, val.index()); + } + // Traverse the subtables. + LS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + if (LS.istable(val) && LS.gettabletype(val)==LUA_TT_GENERAL) { + lua_checkstack(L, 10); + lua_pushvalue(L, val.index()); + } + } + } + } + } + assert(stack_is_clear()); + return visited.size() - 1 - ntables; +} + +void World::create_new_tables(int n) { + { + LuaVar tnmap, ntmap, tab; + LuaExtStack LS(state(), tnmap, ntmap, tab); + LS.rawget(tnmap, LuaRegistry, "tnmap"); + LS.rawget(ntmap, LuaRegistry, "ntmap"); + assert(LS.istable(tnmap)); + assert(LS.istable(ntmap)); + int ntables = LS.rawlen(ntmap); + int nextid = ntables + 1; + for (int i = 0; i < n; i++) { + int id = nextid++; + LS.newtable(tab); + LS.rawset(ntmap, id, tab); + LS.rawset(tnmap, tab, id); + } + } + assert(stack_is_clear()); +} + +void World::unnumber_lua_tables() { + // All we have to do is remove these tables from the registry. + LuaExtStack LS(state()); + LS.rawset(LuaRegistry, "tnmap", LuaNil); + LS.rawset(LuaRegistry, "ntmap", LuaNil); +} + diff --git a/luprex/cpp/core/world-testing.cpp b/luprex/cpp/core/world-testing.cpp new file mode 100644 index 00000000..1872f9dd --- /dev/null +++ b/luprex/cpp/core/world-testing.cpp @@ -0,0 +1,519 @@ +#include "world.hpp" +#include "pprint.hpp" +#include "json.hpp" +#include + + +void World::tangible_clear_anim_queue_to_empty(int64_t id) { + Tangible *t = tangible_get(id); + assert(t != nullptr); + AnimState state; + t->anim_queue_.clear(state); + t->update_plane_item(); +} + + +void World::tangible_clear_plane_and_xyz(int64_t id, const eng::string &plane, const util::DXYZ &xyz) { + Tangible *t = tangible_get(id); + assert(t != nullptr); + AnimState state; + state.set_string("plane", plane); + state.set_dxyz("xyz", xyz); + state.set_persistent("plane"); + state.set_persistent("xyz"); + t->anim_queue_.clear(state); + t->update_plane_item(); +} + +void World::tangible_walkto(int64_t id, float x, float y) { + Tangible *t = tangible_get(id); + assert(t != nullptr); + AnimState state = t->anim_queue_.get_final_persistent(); + state.set_string("action", "walkto"); + state.set_dxyz("xyz", util::DXYZ(x,y,0.0)); + t->anim_queue_.add(state); +} + +eng::string World::tangible_anim_debug_string(int64_t id) const { + const Tangible *t = tangible_get(id); + if (t == 0) return "no such tangible"; + return t->anim_queue_.steps_debug_string(); +} + + +eng::string World::tangible_id_pool_debug_string(int64_t id) const { + const Tangible *t = tangible_get(id); + if (t == 0) return "no such tangible"; + return t->id_player_pool_.debug_string(); +} + + +eng::string World::tangible_ids_debug_string() const { + util::IdVector idv; + for (const auto &pair : tangibles_) { + idv.push_back(pair.first); + } + std::sort(idv.begin(), idv.end()); + return util::id_vector_debug_string(idv); +} + +eng::string World::tangibles_near_debug_string(int64_t actor, int64_t distance) { + assert(stack_is_clear()); + lua_State *L = state(); + LuaVar tangibles, tanobj, classtab; + LuaExtStack LS(L, tangibles, tanobj, classtab); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + eng::ostringstream result; + util::IdVector tans; + get_near(actor, distance, true, false, true, &tans); + for (int64_t id : tans) { + const Tangible *tan = tangible_get(id); + LS.rawget(tanobj, tangibles, id); + LS.tangetclass(classtab, tanobj); + eng::string cname = LS.classname(classtab); + AnimState state = tan->anim_queue_.get_final_persistent(); + result << id << " (" << cname << "): " << state.debug_string() << std::endl; + } + return result.str(); +} + +eng::string World::tangible_pprint(int64_t id) const { + lua_State *L = state(); + LuaVar tangibles, tan, meta; + LuaExtStack LS(L, tangibles, tan, meta); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(tan, tangibles, id); + eng::ostringstream oss; + if (LS.istable(tan)) { + PrettyPrintOptions opts; + opts.indent = false; + pprint(LS, tan, opts, &oss); + } else { + oss << "{}"; + } + return oss.str(); +} + +eng::string World::numbered_tables_debug_string() const { + lua_State *L = state(); + LuaVar ntmap, tab, tid; + LuaExtStack LS(L, ntmap, tab, tid); + eng::vector result; + eng::ostringstream oss; + + // Fetch the numbered tables map. + LS.rawget(ntmap, LuaRegistry, "ntmap"); + + // Iterate over the map. For each table, if it has + // a TID, output that. Otherwise, output "unknown". + for (int i = 1; i < 10000; i++) { + LS.rawget(tab, ntmap, i); + if (!LS.istable(tab)) break; + LS.rawget(tid, tab, "TID"); + if (LS.isstring(tid)) { + result.push_back(LS.ckstring(tid)); + } else { + result.push_back("unknown"); + } + } + std::sort(result.begin(), result.end()); + for (const eng::string &s : result) { + oss << s << ";"; + } + return oss.str(); +} + +eng::string World::paired_tables_debug_string(lua_State *master) const { + lua_State *synch = state(); + LuaVar mntmap, sntmap, mtab, stab, mtid, stid; + LuaExtStack MLS(master, mntmap, mtab, mtid); + LuaExtStack SLS(synch, sntmap, stab, stid); + eng::vector> result; + eng::ostringstream oss; + + // Fetch the numbered tables map. + MLS.rawget(mntmap, LuaRegistry, "ntmap"); + SLS.rawget(sntmap, LuaRegistry, "ntmap"); + int m_ntables = MLS.rawlen(mntmap); + int s_ntables = MLS.rawlen(sntmap); + assert(m_ntables == s_ntables); + + for (int i = 1; i <= m_ntables; i++) { + MLS.rawget(mtab, mntmap, i); + SLS.rawget(stab, sntmap, i); + if (MLS.istable(mtab) && SLS.istable(stab)) { + eng::string mname = "unknown"; + eng::string sname = "unknown"; + MLS.rawget(mtid, mtab, "TID"); + if (MLS.isstring(mtid)) { + mname = MLS.ckstring(mtid); + } + SLS.rawget(stid, stab, "TID"); + if (SLS.isstring(stid)) { + sname = SLS.ckstring(stid); + } + result.push_back(std::make_pair(mname, sname)); + } + } + std::sort(result.begin(), result.end()); + for (const auto &pair : result) { + oss << pair.first << "=" << pair.second << ";"; + } + return oss.str(); +} + +void World::tangible_set_string(int64_t id, const eng::string &path, const eng::string &value) { + lua_State *L = state(); + LuaVar tangibles, tab, subtab; + LuaExtStack LS(L, tangibles, tab, subtab); + + // Fetch the lua side of the tangible. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(tab, tangibles, id); + assert(LS.istable(tab)); + + // Split the path parts into the table names and final part. + util::StringVec pathparts = util::split(path, '.'); + assert(pathparts.size() >= 1); + eng::string finalpart = pathparts.back(); + pathparts.pop_back(); + + // Create subtables as necessary. + for (const eng::string &subname : pathparts) { + LS.rawget(subtab, tab, subname); + if (LS.isnil(subtab)) { + LS.set(subtab, LuaNewTable); + LS.rawset(tab, subname, subtab); + } + assert(LS.istable(subtab)); + LS.set(tab, subtab); + } + + // Set the string value. + LS.rawset(tab, finalpart, value); +} + +void World::tangible_copy_global(int64_t id, const eng::string &path, const eng::string &global) { + lua_State *L = state(); + LuaVar tangibles, tab, subtab, globtab, value; + LuaExtStack LS(L, tangibles, tab, subtab, globtab, value); + + // Fetch the lua side of the tangible. + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(tab, tangibles, id); + assert(LS.istable(tab)); + + // Split the path parts into the table names and final part. + util::StringVec pathparts = util::split(path, '.'); + assert(pathparts.size() >= 1); + eng::string finalpart = pathparts.back(); + pathparts.pop_back(); + + // Create subtables as necessary. + for (const eng::string &subname : pathparts) { + LS.rawget(subtab, tab, subname); + if (LS.isnil(subtab)) { + LS.set(subtab, LuaNewTable); + LS.rawset(tab, subname, subtab); + } + assert(LS.istable(subtab)); + LS.set(tab, subtab); + } + + // Copy the global value. + LS.getglobaltable(globtab); + LS.rawget(value, globtab, global); + LS.rawset(tab, finalpart, value); +} + +void World::tangible_set_class(int64_t id, const eng::string &c) const { + LuaVar tangibles, tan, meta, sclass; + LuaExtStack LS(state(), tangibles, tan, meta, sclass); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(tan, tangibles, id); + assert(LS.istable(tan)); + LS.getmetatable(meta, tan); + if (c == "") { + LS.set(sclass, LuaNil); + } else { + LS.makeclass(sclass, c); + } + LS.rawset(meta, "__index", sclass); +} + +void World::set_global_json(const eng::string &gvar, const eng::string &json) { + LuaVar decoded; + LuaExtStack LS(state(), decoded); + bool ok = json::decode(LS, decoded, json); + if (!ok) { + luaL_error(state(), "invalid json"); + return; + } + set_global(LS, gvar, decoded); +} + +eng::string World::get_global_json(const eng::string &gvar) { + LuaVar value, globaldb; + LuaExtStack LS(state(), globaldb, value); + LS.rawget(globaldb, LuaRegistry, "globaldb"); + LS.rawget(value, globaldb, gvar); + eng::string out; + eng::string error = json::encode(LS, value, out, false, 10000); + if (!error.empty()) { + luaL_error(state(), "%s", error.c_str()); + return ""; + } + return out; +} + + +eng::string World::tangible_get_class(int64_t id) const { + LuaVar tangibles, tan, meta, sclass; + LuaExtStack LS(state(), tangibles, tan, meta, sclass); + LS.rawget(tangibles, LuaRegistry, "tangibles"); + LS.rawget(tan, tangibles, id); + assert(LS.istable(tan)); + LS.getmetatable(meta, tan); + LS.rawget(sclass, meta, "__index"); + eng::string result = LS.classname(sclass); + return result; +} + + + +static bool worlds_identical(const UniqueWorld &w1, const UniqueWorld &w2) { + StreamBuffer sbw1, sbw2; + w1->serialize(&sbw1); + w2->serialize(&sbw2); + return sbw1.contents_equal(&sbw2); +} + +LuaDefine(unittests_world1animdiff, "", "some unit tests") { + UniqueWorld m(new World(WORLD_TYPE_MASTER)); + UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); + UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE)); + StreamBuffer sb; + util::IdVector ids = util::id_vector_create(123, 345); + + // Create some tangibles, and add some animations. + m->tangible_make(123); + m->tangible_make(345); + m->tangible_clear_anim_queue_to_empty(123); + m->tangible_clear_anim_queue_to_empty(345); + m->tangible_walkto(123, 3, 4); + m->tangible_walkto(345, 6, 2); + LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123,345"); + LuaAssertStrEq(L, m->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0"); + LuaAssertStrEq(L, m->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0"); + + // Now difference transmit all that to the client. + ss->diff_visible(ids, m.get(), &sb); + cs->patch_visible(&sb, nullptr); + LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123,345"); + LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0"); + LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0"); + LuaAssert(L, worlds_identical(ss, cs)); + + // Now add some more animation records to the master. + m->tangible_walkto(123, 7, 3); + m->tangible_walkto(345, 2, 5); + LuaAssertStrEq(L, m->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0; action:walkto xyz:7,3,0"); + LuaAssertStrEq(L, m->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0; action:walkto xyz:2,5,0"); + + // Now difference transmit all that to the client again. + ss->diff_visible(ids, m.get(), &sb); + cs->patch_visible(&sb, nullptr); + LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0; action:walkto xyz:7,3,0"); + LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0; action:walkto xyz:2,5,0"); + LuaAssert(L, worlds_identical(ss, cs)); + + // Delete tangible 345. + m->tangible_delete(345); + LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123"); + + // And difference transmit + ss->diff_visible(ids, m.get(), &sb); + cs->patch_visible(&sb, nullptr); + LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123"); + LuaAssert(L, worlds_identical(ss, cs)); + + return 0; +} + +LuaDefine(unittests_world2pairtab, "", "some unit tests") { + UniqueWorld m(new World(WORLD_TYPE_MASTER)); + UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); + StreamBuffer sb; + int ncreate; + + // Create a master model containing some general tables, and + // some specialty tables (not numberable). + m->tangible_make(123); + m->tangible_set_string(123, "inventory.TID", "inventory"); + m->tangible_set_string(123, "transactions.TID", "transactions"); + m->tangible_set_string(123, "skills.TID", "skills"); + m->tangible_set_string(123, "skills.leet.TID", "skills.leet"); + m->tangible_set_string(123, "inventory.cplx.TID", "inventory.cplx"); + m->tangible_copy_global(123, "math", "math"); + m->tangible_copy_global(123, "gltab", "_G"); + + // Now we're going to create a synchronous model that's similar to, but not + // exactly the same as that master model. + ss->tangible_make(123); + ss->tangible_set_string(123, "inventory.TID", "inventory"); + ss->tangible_set_string(123, "skills.TID", "skills"); + ss->tangible_set_string(123, "skills.crap.TID", "skills.crap"); + ss->tangible_set_string(123, "skills.leet.TID", "skills.leet"); + ss->tangible_set_string(123, "math.TID", "math"); + ss->tangible_set_string(123, "gltab.TID", "gltab"); + + // Now we're going to test the numbering and pairing of tables. + // Only these tables should pair: inventory, skills, and skills.leet + ss->number_lua_tables(util::id_vector_create(123)); + LuaAssertStrEq(L, ss->numbered_tables_debug_string(), + "gltab;inventory;math;skills;skills.crap;skills.leet;"); + ss->pair_lua_tables(util::id_vector_create(123), m->state()); + LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()), + "inventory=inventory;skills=skills;skills.leet=skills.leet;"); + + // Test the creation of new tables during difference transmission. + // The master world model above has two tables that couldn't be paired + // to the client: inventory.cplx, and transactions. These two tables + // should be paired to new, created tables. + ncreate = m->number_remaining_tables(util::id_vector_create(123), m->state()); + LuaAssert(L, ncreate == 2); + ss->create_new_tables(ncreate); + LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()), + "inventory=inventory;inventory.cplx=unknown;skills=skills;skills.leet=skills.leet;transactions=unknown;"); + return 0; +} + +LuaDefine(unittests_world3diffluatab, "", "some unit tests") { + UniqueWorld m(new World(WORLD_TYPE_MASTER)); + UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); + UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE)); + StreamBuffer sb; + + // Initialize all three models so that a tangible exists. + m->tangible_make(123); + ss->tangible_make(123); + cs->tangible_make(123); + m->tangible_make(345); + ss->tangible_make(345); + cs->tangible_make(345); + + m->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0)); + ss->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0)); + cs->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0)); + m->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0)); + ss->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0)); + cs->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0)); + + // Put some data into the master model. + m->tangible_set_string(123, "bacon", "crispy"); + m->tangible_set_string(123, "inventory.gold", "wealthy"); + m->tangible_set_string(123, "skills.hunting", "leet"); + m->tangible_set_string(123, "skills.magic.fireball", "weak"); + m->tangible_set_string(345, "inventory.gold", "poor"); + m->tangible_set_string(345, "phone", "867-5309"); + + // The data in the master model should now look like this: + const char *expect_123 = + "{ " + "bacon='crispy', " + "inventory={ gold='wealthy' }, " + "skills={ " + "hunting='leet', " + "magic={ fireball='weak' } " + "} " + "}"; + const char *expect_345 = + "{ " + "inventory={ gold='poor' }, " + "phone='867-5309' " + "}"; + LuaAssertStrEq(L, m->tangible_pprint(123), expect_123); + LuaAssertStrEq(L, m->tangible_pprint(345), expect_345); + + // Difference transmit. + ss->diff_luatabs(123, m.get(), &sb); + cs->patch_luatabs(&sb, nullptr); + + // Verify that the data was transmitted. + LuaAssertStrEq(L, ss->tangible_pprint(123), expect_123); + LuaAssertStrEq(L, cs->tangible_pprint(123), expect_123); + LuaAssertStrEq(L, ss->tangible_pprint(345), expect_345); + LuaAssertStrEq(L, cs->tangible_pprint(345), expect_345); + LuaAssert(L, worlds_identical(ss, cs)); + + return 0; +} + +LuaDefine(unittests_world4difftanclass, "", "some unit tests") { + UniqueWorld m(new World(WORLD_TYPE_MASTER)); + UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); + UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE)); + StreamBuffer sb; + + // Initialize all three models so that a tangible exists. + m->tangible_make(123); + ss->tangible_make(123); + cs->tangible_make(123); + + // Change the lua class of the tangible. + m->tangible_set_class(123, "chicken"); + LuaAssertStrEq(L, m->tangible_get_class(123), "chicken"); + + // Difference transmit. + ss->diff_tanclass(123, m.get(), &sb); + cs->patch_tanclass(&sb, nullptr); + + // Verify that the data was transmitted. + LuaAssertStrEq(L, ss->tangible_get_class(123), "chicken"); + LuaAssertStrEq(L, cs->tangible_get_class(123), "chicken"); + LuaAssert(L, worlds_identical(ss, cs)); + + // Change the class again. + m->tangible_set_class(123, ""); + LuaAssertStrEq(L, m->tangible_get_class(123), ""); + + // Difference transmit. + ss->diff_tanclass(123, m.get(), &sb); + cs->patch_tanclass(&sb, nullptr); + + // Verify that the data was transmitted. + LuaAssertStrEq(L, ss->tangible_get_class(123), ""); + LuaAssertStrEq(L, cs->tangible_get_class(123), ""); + LuaAssert(L, worlds_identical(ss, cs)); + + return 0; +} + +LuaDefine(unittests_world5diffglobals, "", "some unit tests") { + UniqueWorld m(new World(WORLD_TYPE_MASTER)); + UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE)); + UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE)); + StreamBuffer sb; + + m->set_global_json("x", "3"); + ss->diff_globals(m.get(), &sb); + cs->patch_globals(&sb, nullptr); + LuaAssertStrEq(L, cs->get_global_json("x"), "3"); + + m->set_global_json("x", "4"); + m->set_global_json("x", "5"); + m->set_global_json("y", "6"); + ss->diff_globals(m.get(), &sb); + cs->patch_globals(&sb, nullptr); + LuaAssertStrEq(L, cs->get_global_json("x"), "5"); + LuaAssertStrEq(L, cs->get_global_json("y"), "6"); + + cs->set_global_json("x", "2"); + ss->set_global_json("x", "2"); + ss->diff_globals(m.get(), &sb); + cs->patch_globals(&sb, nullptr); + LuaAssertStrEq(L, cs->get_global_json("x"), "5"); + + return 0; +} + diff --git a/luprex/cpp/core/world.hpp b/luprex/cpp/core/world.hpp new file mode 100644 index 00000000..fe6e8a0a --- /dev/null +++ b/luprex/cpp/core/world.hpp @@ -0,0 +1,696 @@ + +#ifndef WORLD_HPP +#define WORLD_HPP + +#include "wrap-set.hpp" +#include "wrap-unordered-map.hpp" +#include "wrap-map.hpp" + +#include +#include + +#include "luastack.hpp" +#include "planemap.hpp" +#include "idalloc.hpp" +#include "animqueue.hpp" +#include "invocation.hpp" +#include "streambuffer.hpp" +#include "debugcollector.hpp" +#include "printbuffer.hpp" +#include "sched.hpp" +#include "http.hpp" +#include "source.hpp" +#include "luasnap.hpp" + +class World; + +class Tangible : public eng::opnew { +private: + friend class World; + + // Serialize and deserialize + // + // The tangible's ID is not serialized. When you serialize a tangible, you + // should probably serialize the ID separately. + // + // The Lua portion of the tangible is not serialized here. Instead, the lua + // portion is serialized when you serialize the lua state as a whole. + // + // PlaneItem is not serialized. The deserialize routine rebuilds the + // PlaneItem from the AnimQueue. + // + // World pointer is not serialized. + // + void serialize(StreamBuffer *sb); + void deserialize(StreamBuffer *sb); + +public: + // Always points back to the world model. + World *world_; + + // Animation queue. + // + AnimQueue anim_queue_; + + // Plane Item. + // + // The PlaneItem also contains this tangible's ID. + // To move this PlaneItem, update the anim_queue first, then call + // update_plane_item, which copies the data from the anim_queue. + // + PlaneItem plane_item_; + + // Player ID pool + // + // This is present in every tangible, whether a player or not. + // However, the fifo is only enabled in logged-in players. + // + IdPlayerPool id_player_pool_; + + // Print Buffer + // + // Stores the console output for this actor until it can be + // probed by the client. Most tangibles have empty printbuffers, + // which are stored as just a null pointer internally. + // + PrintBuffer print_buffer_; + + // Can-Be-Controlled flag. + // + // This flag indicates whether the tangible can be controlled + // by a client. Clients will not be allowed to attach to tangibles + // who don't have this flag. If this flag is true, the + // tangible cannot be deleted using a mere 'tangible.delete', instead, + // you have to use 'tangible.deleteplayer'. + // + bool can_be_controlled_; + + // Is Controlled Flag. + // + // This flag is set to true when a client is controlling this player. + // It gets set back to false when the client logs out or attaches + // to a different player. This can only be set in master models. + // + bool is_controlled_; + + // Force disconnect flag. + // + // This flag is used to force the client to log out ASAP. This flag + // can only be set in master models. + // + bool force_disconnect_; + + // Delete on Logout Flag. + // + // This flag can be set on a controlled player. When the player + // disconnects, their character will be deleted. This flag can only + // be set if the is_controlled_ flag is true. + // + bool delete_on_disconnect_; + + // constructor. + // + Tangible(World *w, int64_t id); + + // Get the ID + // + int64_t id() const { return plane_item_.id(); } + + void update_plane_item(); + bool is_an_actor() { return (id_player_pool_.get_fifo_capacity() > 0); } + void configure_id_pool_for_actor() { id_player_pool_.set_fifo_capacity(3); id_player_pool_.refill(); } +}; + +using UniqueTangible = std::unique_ptr; + +class World : public eng::opnew { +public: + using IdVector = util::IdVector; + using TanVector = eng::vector; + using Redirects = eng::map; + const float RadiusVisibility = 1000.0; + const float RadiusClose = 1000.0; + + // Constructor. + // + // The constructor also calls 'lua_open' to create a new + // lua interpreter for this world model. + // + World(WorldType wt); + + // Destructor. + // + // Not currently functional. + // + ~World(); + + // get_lua_state + // + // Get the lua interpreter associated with this world model. + // + lua_State *state() const { return lua_snap_.state(); } + + // get_near + // + // Get a list of tangibles in any arbitrary region. + // + void get_near(PlaneScan &sc, IdVector *into) const; + + // get_near + // + // Get a list of the tangibles that are near the player. If + // 'exclude_nowhere' is true, exclude any tangibles on the nowhere plane + // (but still include the player himself). The unsorted version returns the + // tangibles in an unpredictable order. If sorted is false, return them in + // an unpredictable order. This is a thin wrapper around the more general + // form of 'get_near', above. + // + void get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted, IdVector *into) const; + + // get encoded animation queues. + // + // This is used by the graphics engine to get the animation queues. + // + void get_encoded_animation_queues(uint32_t count, const int64_t *ids, util::SharedStdStringVec &into); + + // Make a tangible. + // + // You must provide a valid previously-unused ID. Otherwise, leaves the lua + // stack untouched. Returns a pointer to the C++ part of the tangible, and + // optionally stores the Lua part in a stack slot. + // + Tangible *tangible_make(const LuaCoreStack &LS0, LuaSlot tan, int64_t id); + Tangible *tangible_make(int64_t id); + + // Get a pointer to the specified tangible. + // + // If there's no such tangible, or if the tangible is deleted, + // returns nullptr. + // + Tangible *tangible_get(int64_t id); + const Tangible *tangible_get(int64_t id) const; + + // Get a pointer to the specified tangible. + // + // The value on the lua stack should be a lua tangible. + // + // If the 'allowdel' flag is true, then it is valid to pass in + // a deleted tangible. In that case, this function returns nullptr, + // but this is not a Lua error. + // + Tangible *tangible_get(const LuaCoreStack &LS, LuaSlot slot, bool allowdel); + + // Get pointers to many tangibles. + // + TanVector tangible_get_all(const IdVector &ids) const; + + // Delete the specified tangible. + // + // If there's no such tangible, this is a no-op. + // + void tangible_delete(int64_t id); + + // Create a login actor. + // + // Creates a tangible of class 'login' and returns its ID. + // This is used to create a temporary actor which is used during + // the login process. + // + // If this is a master model, The function 'login.initialize' + // called. Then, the following login flags are set: + // can_be_controlled, is_controlled, and delete_on_disconnect. + // + // In a client model, 'login.initialize' is not called, + // and the login flags are not used in client models. + // + int64_t create_login_actor(); + + // Log out a connected player. + // + // This is to be called after a client disconnects. + // + void disconnected(int64_t actor_id); + + // Fetch all redirects and clear the redirects table. + // + Redirects fetch_redirects(); + + // Probe an arbitrary lua expression. + // + // Any print-statements in the lua code are sent into + // a stringstream. The return value of probe_lua is the string + // from the stringstream. If the lua expression returns a + // value, that is also printed to the stringstream. + // + eng::string probe_lua_expr(int64_t actor_id, std::string_view lua); + + // Probe that calls lua function, passing arguments. + // + // Print statements are discarded. The lua function may return a vector + // of values. If so, the values are packed into a StreamBuffer. + // + void probe_lua_call(int64_t place_id, int64_t actor_id, std::string_view datapack, StreamBuffer *retvals); + + // Invoke an Invocation object. + // + // This is the primary dispatcher for all operations that mutate a world model. + // To mutate a world model, create an invocation, then invoke it. + // + // It is legal to mutate a world model without using 'Invoke', but + // only in authoritative world models. + // + void invoke(const Invocation &inv); + + // Get the PrintBuffer of the actor. + // + const PrintBuffer *get_printbuffer(int64_t actor_id); + + // Update the source database from disk. + // + // Special case: if the source pointer is nullptr, does not update. + // The final form takes a sourcepk, a serialized representation + // of a LuaSourceVec. + // + void update_source(const util::LuaSourceVec &source); + void update_source(const util::LuaSourcePtr &source); + void update_source(std::string_view sourcepk); + + // Rebuild the source database. + // + void rebuild_sourcedb(); + + // Supply an HTTP response to an outstanding HTTP request. + // + void http_response(const HttpParser &response); + void http_responses(const HttpParserVec &responses); + + // Abort all HTTP requests. This is typically used after + // reloading a world from a save-game. The http requests that + // were in progress are long-since dead. + // + void abort_all_http_requests(int status_code, std::string_view error); + + // Serve an HTTP query coming in from outside. + // + // Note: the lua code for the http_serve runs in a nonblocking + // context. It must produce a result instantly. + // + HttpServerResponse http_serve(const HttpParser &request); + + // Run all unit tests. + // + void run_unittests(); + + // fetch_global_pointer + // + // Given a lua state, fetch the world model associated with + // that lua state. + // + static World *fetch_global_pointer(lua_State *L); + + // Check if the world is authoritative. + // + bool is_authoritative() const { return util::is_authoritative(world_type_); } + + // Get a table showing all outstanding HTTP requests. + // + const HttpClientRequestMap &http_requests() const { return http_requests_; } + + // Serialize and deserialize. + // + void serialize(StreamBuffer *sb); + void deserialize(StreamBuffer *sb); + + // Snapshot and rollback. + // + // These are used by the client to convert the synchronous model + // to an asynchronous model and back. + // + void snapshot(); + void rollback(); + bool snapshot_empty() { return snapshot_.empty(); } + + // Run any threads which according to the scheduler queue are ready. + // + void run_scheduled_threads(); + + // Check that the main thread has nothing on the stack + // + bool stack_is_clear() const { return lua_gettop(state()) == 0; } + + // Set the lthread state. + // + // Whenever lua code is running, and ONLY when lua code is running, + // we store the following information in the world model: + // + // * lthread_actor_id: current actor + // * lthread_place_id: current place + // * lthread_use_ppool: true if we should use the player ID pool. + // * lthread_prints_: a stringstream which will collect 'print' statements. + // + // As soon as the lua code stops executing, these variables are + // cleared. + // + void clear_lthread_state(); + void open_lthread_state(int64_t actor_id, int64_t place_id, int64_t thread_id, bool ppool, bool prints); + void close_lthread_state(); + + std::ostream *lthread_print_stream() const; + + // Set a global variable. + // + // This will throw lua errors if there's a problem. + // + void set_global(LuaCoreStack &LS0, const eng::string &gvar, LuaSlot value); + + // Get the serialized value of a global variable. + // + // This accessor is used during difference transmission. + // + const eng::string &get_gvname_serial(const eng::string &gvar); + + // Allocate a single ID. + // + // The rules are as follows: + // * if lthread_use_ppool is false, uses the global pool. + // * if lthread_actor_id is not a valid actor id, uses the global pool. + // * otherwise, uses the player pool of lthread_actor_id. + // + int64_t alloc_id_predictable(); + + // If we're in a probe, generate an error. + // If we're in a nonauthoritative model, do a nopredict yield. + // Otherwise, return. + void guard_blockable(lua_State *L, const char *fn); + + // If we're in a probe, return. + // If we're in a nonauthoritative model, do a nopredict yield. + // Otherwise, return. + void guard_nopredict(lua_State *L, const char *fn); + +private: + // Add a thread to the scheduler queue. + // + void schedule(int64_t clk, int64_t thid, int64_t plid); + + // Store a pointer to a world model into a lua registry. + // + static void store_global_pointer(lua_State *L, World *w); + + // Invoke the lua_call operation. + // + void invoke_lua_call(int64_t actor_id, int64_t place_id, std::string_view datapack); + + // Invoke a lua string. + // + void invoke_lua_expr(int64_t actor_id, int64_t place_id, std::string_view datapack); + + // Invoke the flush-prints operation. + // + void invoke_flush_prints(int64_t actor_id, int64_t place_id, std::string_view datapack); + + // Invoke the tick operation. + // + void invoke_tick(int64_t actor_id, int64_t place_id, std::string_view datapack); + + // Invoke the lua_source operation. + // + void invoke_lua_source(int64_t actor_id, int64_t place_id, std::string_view datapack); + + // Low level spawn thread function. + // + bool spawn(LuaCoreStack &LS0, int64_t actor_id, int64_t place_id, LuaSlot func, bool passactorplace, int nargs, bool print); + +public: + //////////////////////////////////////////////////////////////////////////// + // + // TESTING SUPPORT + // + // The following functions are not designed to be useful for production + // code, they're designed to be helpful for unit testing. + // + //////////////////////////////////////////////////////////////////////////// + + // Clear the animation queue and remove all persistent state. + // + void tangible_clear_anim_queue_to_empty(int64_t id); + + // Clear the animation queue to a reasonable starting position. + // + void tangible_clear_plane_and_xyz(int64_t id, const eng::string &plane, const util::DXYZ &xyz); + + // Add a 'walkto' animation to the specified tangible. + // + void tangible_walkto(int64_t id, float x, float y); + + // Get the tangible's animation queue as a debug string. + // + eng::string tangible_anim_debug_string(int64_t id) const; + + // Get the tangible's ID Pool as a debug string. + // + eng::string tangible_id_pool_debug_string(int64_t id) const; + + // Get a list of all existing tangibles as a comma-separated string. + // + eng::string tangible_ids_debug_string() const; + + // Get a list of all tangibles near the target as a string. + // + eng::string tangibles_near_debug_string(int64_t actor, int64_t distance); + + // Shows the TID (table ID) of the tables that were numbered. + // TIDs are in alphabetical order. Any table without a TID + // shows up as "unknown" + // + eng::string numbered_tables_debug_string() const; + + // Paired tables debug string. Shows TID=TID pairs, sorted alphabetically. + // + eng::string paired_tables_debug_string(lua_State *master) const; + + // Store a string in the tangible's database. + // + void tangible_set_string(int64_t id, const eng::string &path, const eng::string &value); + + // Copy a from the lua global environment into the tangible's database. + // + // This is for unit testing. + // + void tangible_copy_global(int64_t id, const eng::string &path, const eng::string &global); + + // Pretty-print the entire tangible database and return it as a string. + // + eng::string tangible_pprint(int64_t id) const; + + // Set the tangible's lua class. + // + void tangible_set_class(int64_t id, const eng::string &c) const; + + // Get the tangible's lua class (returns empty string if none). + // + eng::string tangible_get_class(int64_t id) const; + + // Store json in a global variable. + // + void set_global_json(const eng::string &gvar, const eng::string &json); + + // Get a global variable as json. + // + eng::string get_global_json(const eng::string &gvar); + +public: + /////////////////////////////////////////////////////////// + // + // world-difftab: Nonrecursive table comparison + // + // These routines compare tables in the master lua to the corresponding + // tables in the synchronous lua. This is a nonrecursive process, because + // the recursion has already been done during the table enumeration process. + // + /////////////////////////////////////////////////////////// + + void patch_numbered_tables(StreamBuffer *sb, DebugCollector *dbc); + void diff_numbered_tables(lua_State *master, StreamBuffer *sb); + + void patch_tangible_databases(StreamBuffer *sb, DebugCollector *dbc); + void diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb); + + void patch_tangible_classes(StreamBuffer *sb, DebugCollector *dbc); + void diff_tangible_classes(const IdVector &basis, lua_State *master, StreamBuffer *sb); + +public: + /////////////////////////////////////////////////////////// + // + // Difference transmission + // + /////////////////////////////////////////////////////////// + + util::IdVector get_visible_union(int64_t actor_id, World *master); + + int64_t patch_actor(StreamBuffer *sb, DebugCollector *dbc); + void diff_actor(int64_t actor_id, World *master, StreamBuffer *sb); + + void patch_visible(StreamBuffer *sb, DebugCollector *dbc); + void diff_visible(const util::IdVector &ids, World *master, StreamBuffer *sb); + + void patch_luatabs(StreamBuffer *sb, DebugCollector *dbc); + void diff_luatabs(int64_t actor_id, World *master, StreamBuffer *sb); + + void patch_tanclass(StreamBuffer *sb, DebugCollector *dbc); + void diff_tanclass(int64_t actor_id, World *master, StreamBuffer *sb); + + void patch_source(StreamBuffer *sb, DebugCollector *dbc); + void diff_source(World *master, StreamBuffer *sb); + + void patch_globals(StreamBuffer *sb, DebugCollector *dbc); + void diff_globals(World *master, StreamBuffer *sb); + + // This is the main entry point for difference transmission. + // + int64_t patch_everything(StreamBuffer *sb, DebugCollector *dbc); + void diff_everything(int64_t actor, World *master, StreamBuffer *sb); + +public: + /////////////////////////////////////////////////////////// + // + // world-pairtab: Numbering and pairing of lua tables. + // + // The following routines pair up tables in the synchronous + // model with tables in the master model, by assigning matching + // table numbers. This is not one subroutine but several, because + // some of the steps happen on the server, some on the client, + // and so forth. + // + // The goal of these routines is to build these data structures: + // + // Table-to-number mapping is stored in registry.tnmap + // Number-to-table mapping is stored in registry.ntmap + // + /////////////////////////////////////////////////////////// + + // In the synchronous models, number tables recursively. + // + // This is a simple recursive traversal, which numbers tables. + // This creates the initial ntmap in the synchronous models. + // + int number_lua_tables(const IdVector &basis); + + // Pair tables in the master model to tables in the synch model. + // + // Recursively walk the master and synchronous model in parallel, + // copying table numbers from the synchronous ntmap into the master's ntmap. + // + void pair_lua_tables(const IdVector &basis, lua_State *master); + + // Number previously unpaired tables in the master model. + // + // This finds every not-yet-numbered table in the master model, + // and appends these tables to the master's ntmap. Once they're + // in the ntmap, they can be paired by simply creating new tables + // in the synchronous model. + // + int number_remaining_tables(const IdVector &basis, lua_State *master); + + // Create new tables in the synchronous models. + // + // Creates new tables in the synchronous model and appends these + // new tables to the synchronous model's ntmap. + // + void create_new_tables(int n); + + // Delete the table numbering. + // + // This simply removes registry.tnmap and registry.ntmap + // + void unnumber_lua_tables(); + +private: + // Type of model + WorldType world_type_; + + // A lua intepreter with snapshot function. + // + LuaSnap lua_snap_; + + // The Global ID Pool. + // + IdGlobalPool id_global_pool_; + + // Source Database. + // + SourceDB source_db_; + PlaneMap plane_map_; + + // Lua Global Variables + // + // assign_seqno: sequence number generator for global variable assignments (master and client) + // gvname_to_serial: global variable name to serialized data. (master and client) + // gvname_to_seqno: global variable name to sequence number. (master only) + // seqno_to_gvname: sequence number to global variable name. (master only) + // gvname_modified: set of global variables recently locally modified. (client only) + // + int64_t assign_seqno_; + eng::map gvname_to_serial_; + eng::map gvname_to_seqno_; + eng::map seqno_to_gvname_; + eng::set gvname_modified_; + + // Tangibles table. + // + eng::unordered_map tangibles_; + + // Current time. + // + int64_t clock_; + + // Thread schedule: must include every thread, except + // for the one currently-executing thread. + // + Schedule thread_sched_; + + // Outstanding HTTP requests, indexed by request ID. + // Authoritative models only. + // + HttpClientRequestMap http_requests_; + + // Serialized snapshot of world model. + // + StreamBuffer snapshot_; + + // Redirects. + // + Redirects redirects_; + + // lthread variables: see set_lthread_state for explanation. + // + int64_t lthread_actor_id_; + int64_t lthread_place_id_; + int64_t lthread_thread_id_; + int64_t lthread_use_ppool_; + std::unique_ptr lthread_prints_; + + friend class Tangible; + friend void global_set(LuaCoreStack &LS0, const eng::string &gvar, LuaSlot value); + friend int lfn_tangible_animate(lua_State *L); + friend int lfn_tangible_build(lua_State *L); + friend int lfn_tangible_redirect(lua_State *L); + friend int lfn_tangible_actor(lua_State *L); + friend int lfn_tangible_place(lua_State *L); + friend int lfn_tangible_nopredict(lua_State *L); + friend int lfn_tangible_near(lua_State *L); + friend int lfn_tangible_scan(lua_State *L); + friend int lfn_tangible_find(lua_State *L); + friend int lfn_tangible_start(lua_State *L); + friend int lfn_math_random(lua_State *L); + friend int lfn_math_randomstate(lua_State *L); + friend int lfn_wait(lua_State *L); + friend int lfn_nopredict(lua_State *L); + friend int lfn_http_request(lua_State *L, const char *method); + friend int lfn_global_set(lua_State *L); +}; + +using UniqueWorld = std::unique_ptr; + + +#endif // WORLD_HPP diff --git a/luprex/cpp/drv/driver-linux.cpp b/luprex/cpp/drv/driver-linux.cpp new file mode 100644 index 00000000..23cbba86 --- /dev/null +++ b/luprex/cpp/drv/driver-linux.cpp @@ -0,0 +1,271 @@ + + +#include "drvutil.hpp" +#include "osdrvutil.hpp" +#include "sslutil.hpp" +#include "readline.hpp" +#include "../core/enginewrapper.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using SOCKET=int; +const int INVALID_SOCKET = -1; + +struct termios orig_termios; + +std::filesystem::path get_exe_path() { + char result[ PATH_MAX ]; + ssize_t count = readlink( "/proc/self/exe", result, PATH_MAX ); + return std::filesystem::path(std::string( result, (count > 0) ? count : 0 )); +} + +void set_nonblocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + assert(flags != -1); + int status = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assert(status != -1); +} + +static void disable_tty_raw() { + tcsetattr(0, TCSAFLUSH, &orig_termios); +} + +static void enable_tty_raw() { + int status = tcgetattr(0, &orig_termios); + assert(status >= 0); + atexit(disable_tty_raw); + struct termios raw = orig_termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_lflag &= ~(ECHO | ICANON); + raw.c_oflag |= OPOST; + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 0; + status = tcsetattr(0, TCSAFLUSH, &raw); + assert(status >= 0); +} + +static SOCKET open_connection(const char *host, const char *port, std::string &err) { + struct addrinfo *addrs = nullptr; + struct addrinfo *goodaddr = nullptr; + struct addrinfo hints; + SOCKET sock = INVALID_SOCKET; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_NUMERICSERV; + + err.clear(); + int status = getaddrinfo(host, port, &hints, &addrs); + if (status != 0) { + err = gai_strerror(status); + goto error_general; + } + if (addrs == nullptr) { + err = "no such host found"; + goto error_general; + } + goodaddr = addrs; + assert(goodaddr->ai_family == AF_INET); + assert(goodaddr->ai_socktype == SOCK_STREAM); + assert(goodaddr->ai_protocol == IPPROTO_TCP); + sock = socket(goodaddr->ai_family, goodaddr->ai_socktype, goodaddr->ai_protocol); + if (sock <= 0) goto error_errno; + + set_nonblocking(sock); + + status = connect(sock, goodaddr->ai_addr, goodaddr->ai_addrlen); + if ((status != 0) && (errno != EINPROGRESS)) goto error_errno; + + freeaddrinfo(addrs); + return sock; + +error_errno: + err = drvutil::strerror_str(errno); +error_general: + if (sock != INVALID_SOCKET) close(sock); + if (addrs != nullptr) freeaddrinfo(addrs); + return INVALID_SOCKET; +} + +static SOCKET listen_on_port(int port, std::string &err) { + int status, enable; + err.clear(); + + SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock <= 0) goto error_errno; + + enable = 1; + status = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (status != 0) goto error_errno; + + struct sockaddr_in server; + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = htons(port); + + status = bind(sock, (struct sockaddr *)&server, sizeof(server)); + if (status != 0) goto error_errno; + + status = listen(sock, 10); + if (status != 0) goto error_errno; + + set_nonblocking(sock); + return sock; + +error_errno: + err = drvutil::strerror_str(errno); + if (sock >= 0) close(sock); + return INVALID_SOCKET; +} + +static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) { + err.clear(); + SOCKET chsock = accept(listen_socket, nullptr, nullptr); + if (chsock >= 0) { + set_nonblocking(chsock); + return chsock; + } else { + if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != ECONNABORTED)) { + err = drvutil::strerror_str(errno); + } + return INVALID_SOCKET; + } +} + +// the return values for socket_send: +// +// positive: sent bytes successfully +// negative: error. +// If the error message is empty, then it's "would block" +// Any other error generates an error message. +// +static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) { + int wbytes = send(socket, bytes, nbytes, 0); + if (wbytes < 0) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { + err.clear(); + } else { + err = drvutil::strerror_str(errno); + } + return -1; + } else { + err.clear(); + return wbytes; + } +} + +static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) { + int nrecv = recv(socket, bytes, nbytes, 0); + if (nrecv < 0) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { + err.clear(); + } else { + err = drvutil::strerror_str(errno); + } + return -1; + } else if (nrecv == 0) { + err.clear(); + return 0; + } else { + err.clear(); + return nrecv; + } +} + +static int socket_close(SOCKET socket) { + return close(socket); +} + +static int socket_poll(struct pollfd *pollvec, int pollcount, int mstimeout, std::string &err) { + // socket_poll is implicitly expected to also poll stdin, + // if the OS allows that. Linux does, so we add stdin to the + // poll vector. The poll vector is required to have at + // least one free space in order to do this. + pollvec[pollcount].fd = 0; + pollvec[pollcount].events = POLLIN; + pollcount += 1; + + // Do the poll. + int status = poll(pollvec, pollcount, mstimeout); + if (status < 0) { + err = drvutil::strerror_str(errno); + return -1; + } + return 0; +} + +// Write unicode onto the console. +static void console_write(const std::u32string &cps) { + std::string utf8 = drvutil::utf32_to_utf8(cps); + write(1, utf8.c_str(), utf8.size()); +} + +static std::u32string console_read() { + std::u32string result; + char buffer[512]; + int nread = read(0, buffer, 512); + if (nread > 0) { + std::string_view s(buffer, nread); + result = drvutil::utf8_to_utf32(s, nullptr); + } + return result; +} + +static void call_init_engine_wrapper(const std::filesystem::path &luprexroot, EngineWrapper *w) { + using InitFn = void (*)(EngineWrapper *); + InitFn initfn = (InitFn)dlsym(nullptr, "init_engine_wrapper"); + if (initfn == nullptr) { + std::string path = luprexroot / "build/linux/luprexlib.so"; + void *dll_handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + assert(dll_handle != nullptr); + initfn = (InitFn)dlsym(dll_handle, "init_engine_wrapper"); + } + assert(initfn != nullptr); + initfn(w); +} + +static void ssl_load_certificate_authorities(SSL_CTX *ctx) { + assert(SSL_CTX_set_default_verify_paths(ctx) == 1); +} + +static void disable_randomization(int argc, char *argv[]) { + const int old_personality = personality(ADDR_NO_RANDOMIZE); + if (!(old_personality & ADDR_NO_RANDOMIZE)) { + const int new_personality = personality(ADDR_NO_RANDOMIZE); + if (new_personality & ADDR_NO_RANDOMIZE) { + execv(argv[0], argv); + } + } +} + +static void os_initialize(int argc, char **argv) { + disable_randomization(argc, argv); + enable_tty_raw(); +} + + diff --git a/luprex/cpp/drv/driver-windows.cpp b/luprex/cpp/drv/driver-windows.cpp new file mode 100644 index 00000000..31896443 --- /dev/null +++ b/luprex/cpp/drv/driver-windows.cpp @@ -0,0 +1,328 @@ +#define WINVER 0x0600 +#define _WIN32_WINNT 0x0600 + +#include "drvutil.hpp" +#include "osdrvutil.hpp" +#include "sslutil.hpp" +#include "readline.hpp" +#include "../core/enginewrapper.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// OpenSSL requires plain ascii pathnames. Returns empty string +// if the path cannot be converted to plain ascii. +std::string path_to_plain_ascii(const std::filesystem::path &path) { + std::wstring s = path.native(); + for (wchar_t c : s) { + if ((c < 1) || (c > 127)) return ""; + } + std::ostringstream oss; + for (wchar_t c : s) { + oss << ((char)c); + } + return oss.str(); +} + +std::filesystem::path get_exe_path() { + WCHAR exepath[MAX_PATH]; + DWORD status = GetModuleFileNameW( NULL, exepath, MAX_PATH ); + assert(status != 0); + return std::filesystem::path(exepath); +} + +static void set_nonblocking(SOCKET sock) { + u_long mode = 1; // 1 to enable non-blocking socket + int status = ioctlsocket(sock, FIONBIO, &mode); + assert(status == 0); +} + +static PADDRINFOA find_good_addr(PADDRINFOA addrinfo) { + for (PADDRINFOA addr = addrinfo; addr != nullptr; addr = addr->ai_next) { + if (addr->ai_family == AF_INET) { + return addr; + } + } + return nullptr; +} + +static SOCKET open_connection(const char *host, const char *port, std::string &err) { + PADDRINFOA addrs = nullptr; + PADDRINFOA goodaddr = nullptr; + SOCKET sock = INVALID_SOCKET; + + err.clear(); + int status = getaddrinfo(host, port, nullptr, &addrs); + while (status == WSATRY_AGAIN) { + status = getaddrinfo(host, port, nullptr, &addrs); + } + if (status == WSAHOST_NOT_FOUND) { + err = "host not found"; + goto error; + } + if (status != 0) { + err = "DNS resolution malfunction"; + goto error; + } + goodaddr = find_good_addr(addrs); + if (goodaddr == nullptr) { + err = "host not an internet host"; + goto error; + } + sock = socket(goodaddr->ai_family, SOCK_STREAM, IPPROTO_TCP); + if (sock == INVALID_SOCKET) { + err = "could not create a socket"; + goto error; + } + set_nonblocking(sock); + status = connect(sock, goodaddr->ai_addr, goodaddr->ai_addrlen); + if (status != 0) { + int errcode = WSAGetLastError(); + if (errcode != WSAEWOULDBLOCK) { + err = "connect failure"; + goto error; + } + } + freeaddrinfo(addrs); + return sock; + +error: + if (sock != INVALID_SOCKET) closesocket(sock); + if (addrs != nullptr) freeaddrinfo(addrs); + return SOCKET_ERROR; +} + +SOCKET listen_on_port(int port, std::string &err) { + int status; + err.clear(); + SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + err = "could not create a socket"; + goto error; + } + + struct sockaddr_in server; + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = htons(port); + + status = bind(sock, (struct sockaddr *)&server, sizeof(server)); + if (status < 0) { + err = "could not bind port"; + goto error; + } + status = listen(sock, 10); + if (status < 0) { + err = "could not listen on socket"; + goto error; + } + set_nonblocking(sock); + return sock; + +error: + if (sock != INVALID_SOCKET) closesocket(sock); + return SOCKET_ERROR; +} + +static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) { + SOCKET chsock = accept(listen_socket, nullptr, nullptr); + if (chsock != INVALID_SOCKET) { + set_nonblocking(chsock); + return chsock; + } else { + int errcode = WSAGetLastError(); + if ((errcode == WSAEWOULDBLOCK) || (errcode == WSAECONNRESET)) { + return INVALID_SOCKET; + } else { + err = "accept failed"; + return INVALID_SOCKET; + } + } +} + +// the return values for socket_send: +// +// positive: sent bytes successfully +// negative: error. +// If the error message is empty, then it's "would block" +// Any other error generates an error message. +// + +static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) { + int wbytes = send(socket, bytes, nbytes, 0); + if (wbytes == SOCKET_ERROR) { + int errcode = WSAGetLastError(); + if (errcode == WSAEWOULDBLOCK) { + err.clear(); + } else { + err = "send failure"; + } + return -1; + } else { + assert(wbytes > 0); + err.clear(); + return wbytes; + } +} + +static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) { + int nrecv = recv(socket, bytes, nbytes, 0); + if (nrecv < 0) { + int errcode = WSAGetLastError(); + if (errcode == WSAEWOULDBLOCK) { + err = ""; + } else { + err = "recv failure"; + } + return -1; + } else if (nrecv == 0) { + err.clear(); + return 0; + } else { + err.clear(); + return nrecv; + } +} + +static int socket_close(SOCKET socket) { + return closesocket(socket); +} + +static int socket_poll(struct pollfd *pollvec, int pollcount, int mstimeout, std::string &err) { + if (pollcount == 0) { + if (mstimeout > 0) Sleep(mstimeout); + return 0; + } + int status = WSAPoll(pollvec, pollcount, mstimeout); + if (status < 0) { + err = drvutil::strerror_str(WSAGetLastError()); + return -1; + } + return status; +} + +static void init_winsock() { + WSADATA data; + int errcode = WSAStartup(2, &data); + if (errcode != 0) { + fprintf(stderr, "Winsock didn't initalize, error %d", errcode); + exit(1); + } +} + + +static void console_write(const std::u32string &cps) { + if (cps.size() == 0) return; + // Convert to wstring. Any character not representable as a single wchar_t + // is replaced with a box. It's not ideal, but it's pretty good. + std::wstring ws(cps.size(), 0); + for (int i = 0; i < int(cps.size()); i++) { + char32_t c = cps[i]; + if (drvutil::is_single_wchar_t(c)) ws[i] = (wchar_t)c; + else ws[i] = 0x2610; + } + HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE); + assert(hstdout != INVALID_HANDLE_VALUE); + DWORD nwrote; + std::wstring_view v(ws); + while (v.size() > 0) { + int nwrite = v.size(); + if (nwrite > 10000) nwrite = 10000; + assert(WriteConsoleW(hstdout, v.data(), nwrite, &nwrote, nullptr)); + assert(nwrote > 0); + v.remove_prefix(nwrote); + } +} + +static std::u32string console_read() { + HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE); + assert(hstdin != INVALID_HANDLE_VALUE); + INPUT_RECORD inrecords[512]; + DWORD nread, nevents; + if (GetNumberOfConsoleInputEvents(hstdin, &nevents)) { + if (int(nevents) > 0) { + if (int(nevents) > 512) nevents = 512; + ReadConsoleInputW(hstdin, inrecords, nevents, &nread); + std::u32string result(nread, 0); + int len = 0; + for (int i = 0; i < int(nread); i++) { + const INPUT_RECORD &inr = inrecords[i]; + if (inr.EventType != KEY_EVENT) continue; + const KEY_EVENT_RECORD &key = inr.Event.KeyEvent; + if (!key.bKeyDown) continue; + result[len++] = key.uChar.UnicodeChar; + } + return result.substr(0, len); + } + } + return std::u32string(); +} + +static void ssl_load_certificate_authorities(SSL_CTX *ctx) { + HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT"); + PCCERT_CONTEXT pContext = NULL; + X509 *x509; + X509_STORE *store = SSL_CTX_get_cert_store(ctx); + + if (!hStore) { + fprintf(stderr, "Cannot open system certificate store.\n"); + exit(1); + } + + while ((pContext = CertEnumCertificatesInStore(hStore, pContext))) { + const unsigned char *encoded_cert = pContext->pbCertEncoded; + x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + } + } + + CertCloseStore(hStore, 0); +} + +static void call_init_engine_wrapper(const std::filesystem::path &luprexroot, EngineWrapper *w) { + HMODULE exe = GetModuleHandleA(NULL); + using InitFn = void (*)(EngineWrapper *); + InitFn initfn = (InitFn)GetProcAddress(exe, "init_engine_wrapper"); + if (initfn == nullptr) { +#if defined(_MSC_VER) + std::wstring path = luprexroot / "build/visual/luprexlib.dll"; +#elif defined(__GNUC__) + std::wstring path = luprexroot / "build/mingw/luprexlib.dll"; +#else + #error "Cannot detect OS type" +#endif + HMODULE dll = LoadLibraryW(path.c_str()); + assert(dll != nullptr); + initfn = (InitFn)GetProcAddress(dll, "init_engine_wrapper"); + } + assert(initfn != nullptr); + initfn(w); +} + +void os_initialize(int argc, char **argv) { + init_winsock(); +} + diff --git a/luprex/cpp/drv/driver.cpp b/luprex/cpp/drv/driver.cpp new file mode 100644 index 00000000..f614d940 --- /dev/null +++ b/luprex/cpp/drv/driver.cpp @@ -0,0 +1,689 @@ + +#if defined(__linux__) +#include "driver-linux.cpp" +#elif defined(_WIN32) +#include "driver-windows.cpp" +#endif + +#define POLLVEC_SIZE (DRV_MAX_CHAN + 1) +#define MAX_BIO_BUFFER (128 * 1024) + + +static void if_error_print_and_exit(const std::string_view str) { + if (!str.empty()) { + std::cerr << std::endl << "error: " << str << std::endl; + exit(1); + } +} + +static void dprint_callback(const char *oneline, size_t size) { + fwrite("**", 1, 2, stderr); + fwrite(oneline, 1, size, stderr); + fwrite("\n", 1, 1, stderr); + fflush(stderr); +} + +inline bool file_exists(const std::filesystem::path &name) { + std::ifstream f(name); + return f.good(); +} + +std::filesystem::path find_luprex_root(std::filesystem::path exepath) { + std::filesystem::path pp = exepath.parent_path(); + if (file_exists(pp / "lua/control.lst")) { + return pp; + } + pp = pp.parent_path(); + if (file_exists(pp / "lua/control.lst")) { + return pp; + } + pp = pp.parent_path(); + if (file_exists(pp / "lua/control.lst")) { + return pp; + } + assert(false && "Could not find lua/control.lst"); + return ""; +} + +class Driver { + public: + enum ChanState { + CHAN_INACTIVE, + CHAN_PLAINTEXT, + CHAN_SSL_CONNECTING, + CHAN_SSL_ACCEPTING, + CHAN_SSL_READWRITE, + }; + + struct ChanInfo { + int chid; + SOCKET socket; + SSL *ssl; + BIO *recv_bio; + BIO *send_bio; + + // If recent_error is set, that means that a recent IO operation generated + // an error. As a special case, EOF on read is considered an error, we use + // the string "EOF" for this case. + std::string recent_error; + + // OpenSSL has a rule: if you try to SSL_write and it returns + // SSL_ERROR_WANT_READ, then you have to retry the write with the same + // number of bytes. In this event, we record how many bytes we + // attempted to write, which will enable us to retry. + int retry_write_nbytes; + + // True if the channel needs to be advanced. + bool need_advance; + + ChanState state; + uint32_t nbytes; + const char *bytes; + + bool marked_for_deletion() const { return state == CHAN_INACTIVE; } + }; + + std::filesystem::path luprexroot; + EngineWrapper engw; + std::vector chans_; + std::map listen_sockets_; + bool read_console_recently_; + std::unique_ptr pollvec_; + std::unique_ptr chbuf_; + ReadlineDevice readline_device_; + + sslutil::UniqueCTX ssl_server_ctx_; + sslutil::UniqueCTX ssl_client_secure_ctx_; + sslutil::UniqueCTX ssl_client_insecure_ctx_; + + // Return the amount of 'space left' in a BIO. This is a fiction, + // because MEM BIOs technically have unlimited capacity. We're + // artificially limiting them to a certain size because there's no + // reason to buffer huge amounts of data. + // + int bio_space(BIO *bio) { + int space = (MAX_BIO_BUFFER) - BIO_pending(bio); + if (space < 0) space = 0; + return space; + } + + // This is a terribly inefficient way to discard data that has + // already been processed. There has to be something better. + // + void bio_discard(BIO *b, int nbytes) { + while (nbytes > 0) { + int nread = nbytes; + if (nread > DRV_SHORTSTRING_SIZE) nread = DRV_SHORTSTRING_SIZE; + int ndropped = BIO_read(b, chbuf_.get(), nread); + assert(ndropped == nread); + nbytes -= ndropped; + } + } + + void make_channel(SOCKET sock, int chid, SSL_CTX *ctx, ChanState state) { + ChanInfo newchan; + newchan.chid = chid; + newchan.socket = sock; + newchan.recv_bio = BIO_new(BIO_s_mem()); + newchan.send_bio = BIO_new(BIO_s_mem()); + newchan.recent_error.clear(); + newchan.retry_write_nbytes = 0; + newchan.need_advance = true; + + if (state == CHAN_PLAINTEXT) { + newchan.ssl = nullptr; + } else { + newchan.ssl = SSL_new(ctx); + SSL_set_bio(newchan.ssl, newchan.recv_bio, newchan.send_bio); + } + + newchan.state = state; + newchan.nbytes = 0; + newchan.bytes = 0; + chans_.push_back(newchan); + } + + void close_channel(ChanInfo &chan, std::string_view err) { + // std::cerr << "Closing channel " << chan.chid << " with " << err << std::endl; + assert(chan.state != CHAN_INACTIVE); + + // Close and release the SSL channel. + // This frees the BIO objects as well. + if (chan.ssl != nullptr) { + SSL_free(chan.ssl); + chan.ssl = nullptr; + } + chan.recv_bio = nullptr; + chan.send_bio = nullptr; + chan.recent_error.clear(); + chan.retry_write_nbytes = 0; + chan.need_advance = false; + + // Close and release the socket. + assert(chan.socket != INVALID_SOCKET); + assert(socket_close(chan.socket) == 0); + chan.socket = INVALID_SOCKET; + + // Close everything else. + engw.play_notify_close(&engw, chan.chid, err.size(), err.data()); + chan.state = CHAN_INACTIVE; + chan.chid = -1; + chan.nbytes = 0; + chan.bytes = 0; + } + + void handle_listen_ports() { + uint32_t nports; const uint32_t *ports; + engw.get_listen_ports(&engw, &nports, &ports); + for (uint32_t i = 0; i < nports; i++) { + int port = ports[i]; + if (listen_sockets_.find(port) == listen_sockets_.end()) { + std::string err; + SOCKET sock = listen_on_port(port, err); + if_error_print_and_exit(err); + assert(sock != INVALID_SOCKET); + listen_sockets_[port] = sock; + } + } + } + + void handle_lua_source() { + if (engw.get_rescan_lua_source(&engw)) { + drvutil::ostringstream oss; + std::string err = drvutil::package_lua_source(".", &oss); + if_error_print_and_exit(err); + std::string_view ossv = oss.view(); + engw.play_call_function(&engw, InvocationKind::LUA_SOURCE, 0, ossv.size(), ossv.data(), nullptr, nullptr); + } + } + + + void handle_console_output() { + while (true) { + uint32_t ndata; const char *data; + engw.get_outgoing(&engw, 0, &ndata, &data); + if (ndata == 0) break; + if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE; + std::string_view src(data, ndata); + int consumed; + std::u32string cps = drvutil::utf8_to_utf32(src, &consumed); + readline_device_.print(cps); + engw.play_sent_outgoing(&engw, 0, consumed); + } + } + + void handle_console_input() { + read_console_recently_ = false; + uint32_t promptlen; + const char *promptdata; + engw.get_console_prompt(&engw, &promptlen, &promptdata); + std::u32string prompt = drvutil::utf8_to_utf32(std::string_view(promptdata, promptlen), nullptr); + readline_device_.set_prompt(prompt); + while (true) { + std::u32string cps = console_read(); + if (cps.size() == 0) break; + read_console_recently_ = true; + for (char32_t c : cps) { + std::u32string line = readline_device_.putcode(c); + if (!line.empty()) { + std::string utf8 = drvutil::utf32_to_utf8(line); + engw.play_recv_incoming(&engw, 0, utf8.size(), utf8.c_str()); + } + } + } + } + + void handle_new_outgoing_sockets() { + uint32_t nchids; const uint32_t *chids; + engw.get_new_outgoing(&engw, &nchids, &chids); + for (uint32_t i = 0; i < nchids; i++) { + uint32_t chid = chids[i]; + std::string err, cert, host, port; + const char *target = engw.get_target(&engw, chid); + drvutil::split_target(target, cert, host, port); + if (cert.empty() || host.empty() || port.empty()) { + std::string message = "invalid target: "; + message += target; + engw.play_notify_close(&engw, chid, message.size(), message.c_str()); + continue; + } + SSL_CTX *ctx = nullptr; + if (cert == "cert") { + ctx = ssl_client_secure_ctx_.get(); + } else if (cert == "nocert") { + ctx = ssl_client_insecure_ctx_.get(); + } else { + std::string message = "invalid cert rule: "; + message += target; + engw.play_notify_close(&engw, chid, message.size(), message.c_str()); + continue; + } + SOCKET sock = open_connection(host.c_str(), port.c_str(), err); + if (sock == INVALID_SOCKET) { + engw.play_notify_close(&engw, chid, err.size(), err.c_str()); + continue; + } + make_channel(sock, chid, ctx, CHAN_SSL_CONNECTING); + } + engw.play_clear_new_outgoing(&engw); + } + + void accept_connection(int port, SOCKET sock) { + std::string err; + SOCKET socket = accept_on_socket(sock, err); + if_error_print_and_exit(err); + if (socket != INVALID_SOCKET) { + uint32_t chid = engw.play_notify_accept(&engw, port); + make_channel(socket, chid, ssl_server_ctx_.get(), CHAN_SSL_ACCEPTING); + } + } + + // Copy data from the socket into the recv bio. + // + // If it detects an error or EOF, sets the recent_errno flag. + // + void transfer_socket_to_recv_bio(ChanInfo &chan) { + if ((chan.state == CHAN_INACTIVE) || (!chan.recent_error.empty())) { + return; + } + + std::string err; + int nread = socket_recv(chan.socket, chbuf_.get(), DRV_SHORTSTRING_SIZE, err); + // std::cerr << "chan " << chan.chid << " recv " << nread << " err=" << err << std::endl; + if (nread < 0) { + chan.recent_error = err; + } else { + if (nread == 0) { + chan.recent_error = "EOF"; + } else { + int nstored = BIO_write(chan.recv_bio, chbuf_.get(), nread); + assert(nstored == nread); + chan.need_advance = true; + // std::cerr << "chan " << chan.chid << " stored " << nread << " bytes" << std::endl; + } + } + } + + // Copy data from the send BIO into the socket. + // + // If it detects an error, sets the recent_errno flag. + // It is an error to call this when there is nothing in the send BIO. + // + void transfer_send_bio_to_socket(ChanInfo &chan) { + if ((chan.state == CHAN_INACTIVE) || (!chan.recent_error.empty())) { + return; + } + + char *data; + int ndata = BIO_get_mem_data(chan.send_bio, &data); + if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE; + + // It is an error to call this function when there is nothing in the send BIO. + assert(ndata > 0); + + std::string err; + int nwrote = socket_send(chan.socket, data, ndata, err); + // std::cerr << "chan " << chan.chid << " send " << nwrote << " err=" << err << std::endl; + if (nwrote < 0) { + chan.recent_error = err; + } else { + assert(nwrote != 0); + bio_discard(chan.send_bio, nwrote); + chan.need_advance = true; + } + } + + // Close the channel if there's a serious OpenSSL error. + // + // The 'retval' is the return value of the SSL function that returned an + // error. + // + // All errors are considered serious except for SSL_ERROR_WANT_READ, which + // is not serious because it is transient. However, if you get an + // SSL_ERROR_WANT_READ when there's tons of data available in the read + // buffer, that's inexplicable and therefore serious. + // + void if_error_is_serious_close_channel(ChanInfo &chan, int retval) { + int error = SSL_get_error(chan.ssl, retval); + //std::cerr << "chan " << chan.chid << " ssl error = " << error << std::endl; + + // Should never have write errors, because we're + // using a memory BIO with unlimited capacity. + assert(error != SSL_ERROR_WANT_WRITE); + + // If we get a read error, make sure it's plausible: + // if the recv bio is full, that makes no sense. + if (error == SSL_ERROR_WANT_READ) { + if (bio_space(chan.recv_bio) == 0) { + close_channel(chan, "ssl waiting for data, but there's tons of data"); + } + return; + } + + // Any other error is an actual error. Close + // the channel. + std::string errstr = sslutil::error_string(); + if (errstr == "") errstr = "unknown error"; + close_channel(chan, errstr); + } + + void advance_plaintext(ChanInfo &chan) { + uint32_t ndata; const char *data; + + // Transfer all data from the recv BIO into the channel. + ndata = BIO_get_mem_data(chan.recv_bio, &data); + if (ndata > 0) { + engw.play_recv_incoming(&engw, chan.chid, ndata, data); + bio_discard(chan.recv_bio, ndata); + } + + // Transfer all data from the channel to the send BIO. + engw.get_outgoing(&engw, chan.chid, &ndata, &data); + if (ndata > 0) { + int nwrote = BIO_write(chan.send_bio, data, ndata); + assert(nwrote == int(ndata)); + engw.play_sent_outgoing(&engw, chan.chid, ndata); + } + } + + void advance_ssl_connecting(ChanInfo &chan) { + int retval = SSL_connect(chan.ssl); + //std::cerr << "chan " << chan.chid << " ssl_connect returns " << retval << std::endl; + if (retval == 1) { + chan.state = CHAN_SSL_READWRITE; + chan.need_advance = true; + } else { + if_error_is_serious_close_channel(chan, retval); + } + } + + void advance_ssl_accepting(ChanInfo &chan) { + int retval = SSL_accept(chan.ssl); + //std::cerr << "chan " << chan.chid << " ssl_accept returns " << retval << std::endl; + if (retval == 1) { + chan.state = CHAN_SSL_READWRITE; + chan.need_advance = true; + } else { + if_error_is_serious_close_channel(chan, retval); + } + } + + void advance_ssl_readwrite(ChanInfo &chan) { + // Read as much as we can, which of course will be limited + // by the fact that the recv_bio contains finite data. + while (true) { + int read_result = SSL_read(chan.ssl, chbuf_.get(), DRV_SHORTSTRING_SIZE); + if (read_result > 0) { + engw.play_recv_incoming(&engw, chan.chid, read_result, chbuf_.get()); + } else { + if_error_is_serious_close_channel(chan, read_result); + break; + } + } + + // The read process could have generated an error which could + // have closed the channel. If so, don't try writing. + if (chan.state == CHAN_INACTIVE) { + return; + } + + // Try to write data. + while (chan.nbytes) { + uint32_t wbytes; + if (chan.retry_write_nbytes > 0) { + wbytes = chan.retry_write_nbytes; + assert(wbytes < chan.nbytes); + } else { + wbytes = chan.nbytes; + if (wbytes > DRV_SHORTSTRING_SIZE) wbytes = DRV_SHORTSTRING_SIZE; + } + if (wbytes == 0) break; + int write_result = SSL_write(chan.ssl, chan.bytes, wbytes); + if (write_result > 0) { + engw.play_sent_outgoing(&engw, chan.chid, write_result); + chan.retry_write_nbytes = 0; + chan.nbytes -= write_result; + chan.bytes += write_result; + } else { + if_error_is_serious_close_channel(chan, write_result); + chan.retry_write_nbytes = wbytes; + break; + } + } + } + + void advance_channel(ChanInfo &chan) { + sslutil::clear_all_errors(); + + // We set the need_advance flag to false here, but + // the rest of the advance routine is allowed to set + // it back to true in the event that the advance routine + // only processes some of the available data. + chan.need_advance = false; + + switch (chan.state) { + case CHAN_PLAINTEXT: + advance_plaintext(chan); + break; + case CHAN_SSL_CONNECTING: + advance_ssl_connecting(chan); + break; + case CHAN_SSL_ACCEPTING: + advance_ssl_accepting(chan); + break; + case CHAN_SSL_READWRITE: + advance_ssl_readwrite(chan); + break; + default: + assert(false); + break; + } + } + + void handle_socket_input_output() { + std::string err; + int mstimeout = read_console_recently_ ? 10 : 100; + + // Peek output buffers and determine channel release flags. + bool any_released = false; + for (ChanInfo &chan : chans_) { + engw.get_outgoing(&engw, chan.chid, &chan.nbytes, &chan.bytes); + if (chan.nbytes > 0) { + chan.need_advance = true; + } + if (chan.nbytes == 0) { + if (engw.get_channel_released(&engw, chan.chid)) { + if (BIO_pending(chan.send_bio) == 0) { + close_channel(chan, ""); + any_released = true; + } + } + } + } + + // Delete any released channels + if (any_released) { + drvutil::remove_marked_items(chans_); + } + + // Construct the struct pollfd vector. + int pollsize = 0; + for (const ChanInfo &chan : chans_) { + struct pollfd &pfd = pollvec_[pollsize++]; + assert(chan.socket != INVALID_SOCKET); + pfd.fd = chan.socket; + pfd.events = 0; + pfd.revents = 0; + // If there's room in the receive buffer, set POLLIN + if (bio_space(chan.recv_bio) > 0) { + pfd.events |= POLLIN; + } + // If there's data in the outgoing buffer, set POLLOUT + if (BIO_pending(chan.send_bio) > 0) { + pfd.events |= POLLOUT; + } + if (chan.need_advance) { + mstimeout = 0; + } + } + for (const auto &p : listen_sockets_) { + struct pollfd &pfd = pollvec_[pollsize++]; + pfd.fd = p.second; + pfd.events = POLLIN; + pfd.revents = 0; + } + + // Do the poll. + socket_poll(pollvec_.get(), pollsize, mstimeout, err); + if_error_print_and_exit(err); + + // Advance channels where possible and then check listen sockets. + int index = 0; + for (ChanInfo &chan : chans_) { + struct pollfd &pfd = pollvec_[index++]; + if ((pfd.revents & POLLIN) != 0) { + transfer_socket_to_recv_bio(chan); + } + if ((pfd.revents & POLLOUT) != 0) { + transfer_send_bio_to_socket(chan); + } + if (chan.need_advance || (!chan.recent_error.empty())) { + advance_channel(chan); + } + if (!chan.recent_error.empty()) { + if (chan.recent_error == "EOF") { + close_channel(chan, ""); + } else { + close_channel(chan, chan.recent_error); + } + chan.recent_error.clear(); + } + chan.nbytes = 0; + chan.bytes = 0; + } + for (auto &p : listen_sockets_) { + struct pollfd &pfd = pollvec_[index++]; + if (pfd.revents & (POLLIN | POLLERR)) { + accept_connection(p.first, p.second); + } + } + + // Delete any newly-inactive channels + drvutil::remove_marked_items(chans_); + } + + int replay_logfile(const char *fn, bool verbose) { + engw.replay_initialize(&engw, fn); + if_error_print_and_exit(engw.error); + while (engw.rlog) { + engw.replay_step(&engw); + } + if_error_print_and_exit(engw.error); + return 0; + } + + static void replay_cb_sent_outgoing(void *vp, int chid, int ndata, const char *data) { + if (chid == 0) { + std::cerr.write(data, ndata); + } + } + + int drive(int argc, char *argv[]) { + // Set up the console readline device. + readline_device_.set_print_callback(console_write); + + // Remove the program name from argv. + std::string program = argv[0]; + argc -= 1; + argv += 1; + + // Find the root of the luprex tree. + luprexroot = find_luprex_root(get_exe_path()); + + // Load the DLL and gain access to its functions. + call_init_engine_wrapper(luprexroot, &engw); + engw.replay_cb_sent_outgoing = replay_cb_sent_outgoing; + engw.hook_dprint(dprint_callback); + + // If argv contains "replay ", do a replay, + // and then skip everything else. + if (argc >= 1) { + std::string cmd(argv[0]); + if ((cmd == "replay") || (cmd == "vreplay")) { + if (argc != 2) { + std::cerr << "usage: " << program << " replay " + << std::endl; + return 1; + } + return replay_logfile(argv[1], cmd == "vreplay"); + } + } + + // If argv contains "record ", start recording, + // and remove the "record " from argv. + std::string replaylogfn; + if (argc >= 1) { + std::string cmd = argv[0]; + if (cmd == "record") { + if (argc < 2) { + std::cerr << "The 'record' command must be followed by a filename" << std::endl; + return 1; + } + replaylogfn = argv[1]; + argc -= 2; + argv += 2; + } + } + + // Initialize state variables. + read_console_recently_ = false; + chbuf_.reset(new char[DRV_SHORTSTRING_SIZE]); + pollvec_.reset(new struct pollfd[POLLVEC_SIZE]); + + ssl_server_ctx_.reset(sslutil::new_context(SSL_VERIFY_NONE)); + ssl_client_secure_ctx_.reset(sslutil::new_context(SSL_VERIFY_PEER)); + ssl_client_insecure_ctx_.reset(sslutil::new_context(SSL_VERIFY_NONE)); + ssl_load_certificate_authorities(ssl_client_secure_ctx_.get()); + sslutil::ctx_load_dummy_cert(ssl_server_ctx_.get()); + + // Read the initial lua source code. + drvutil::ostringstream srcpak; + std::string srcpakerr = drvutil::package_lua_source(luprexroot, &srcpak); + if_error_print_and_exit(srcpakerr); + + // Initialize the engine. + std::string_view srcpakv = srcpak.view(); + engw.play_initialize(&engw, argc, argv, srcpakv.size(), srcpakv.data(), replaylogfn.c_str()); + if_error_print_and_exit(engw.error); + + // Set up listening ports. + handle_listen_ports(); + + // Main loop. + while (!engw.get_stop_driver(&engw)) { + handle_lua_source(); + handle_console_output(); + handle_new_outgoing_sockets(); + handle_socket_input_output(); + handle_console_input(); + handle_console_output(); + engw.play_update(&engw, drvutil::get_monotonic_clock()); + } + + // Cleanup + for (ChanInfo &chan : chans_) { + close_channel(chan, ""); + } + engw.release(&engw); + return 0; + } +}; + +int main(int argc, char **argv) { + os_initialize(argc, argv); + assert(OPENSSL_init_ssl(0, NULL) == 1); + sslutil::clear_all_errors(); + Driver driver; + return driver.drive(argc, argv); +} + diff --git a/luprex/cpp/drv/drvutil.cpp b/luprex/cpp/drv/drvutil.cpp new file mode 100644 index 00000000..310ff26f --- /dev/null +++ b/luprex/cpp/drv/drvutil.cpp @@ -0,0 +1,357 @@ + +#include "drvutil.hpp" + +#include +#include +#include +#include +#include +#include + +namespace drvutil { + + +inline static bool ascii_isspace(char c) { + return (c==' ')||(c=='\t')||(c=='\r')||(c=='\n')||(c=='\f')||(c=='\v'); +} + +std::string_view trim(std::string_view v) { + while ((!v.empty()) && (ascii_isspace(v.front()))) { + v.remove_prefix(1); + } + while ((!v.empty()) && (ascii_isspace(v.back()))) { + v.remove_suffix(1); + } + return v; +} + +static std::string_view read_to_line(std::string_view &source) { + size_t pos = source.find('\n'); + std::string_view result; + if (pos == std::string_view::npos) { + result = source; + source = std::string_view(); + } else { + result = source.substr(0, pos); + source = source.substr(pos + 1); + } + if ((!result.empty()) && (result.back() == '\r')) { + result.remove_suffix(1); + } + return result; +} + +std::vector split_view(std::string_view v, char sep) { + std::vector result; + while (true) { + size_t pos = v.find(sep); + if (pos == std::string_view::npos) break; + result.push_back(v.substr(0, pos)); + v = v.substr(pos + 1); + } + result.push_back(v); + return result; +} + +void split_target(std::string_view target, std::string &cert, std::string &host, std::string &port) { + std::vector split = split_view(target, ':'); + if (split.size() != 3) { + cert.clear(); host.clear(); port.clear(); + return; + } + if (split[0].empty() || split[1].empty() || split[2].empty()) { + cert.clear(); host.clear(); port.clear(); + return; + } + cert = std::string(split[0]); + host = std::string(split[1]); + port = std::string(split[2]); +} + +bool is_single_wchar_t(char32_t c) { + if ((c >= 0xD800) && (c <= 0xDFFF)) return false; + if ((c >= 0) && (c <= 0xFFFF)) return true; + return false; +} + +static int buffer_codepoint_utf8(char32_t scp, char *buffer) { + uint32_t cp = (uint32_t)scp; + unsigned char *c = (unsigned char *)buffer; + if (cp < 0) { + return 0; + } + else if (cp <= 0x7F) { + c[0] = cp; + return 1; + } + else if (cp <= 0x7FF) { + c[0] = (cp>>6)+192; + c[1] = (cp&63)+128; + return 2; + } + else if (cp <= 0xFFFF) { + if ((cp >= 0xD800) && (cp <= 0xDFFF)) { + return 0; + } + c[0] = (cp>>12)+224; + c[1] = ((cp>>6)&63)+128; + c[2] = (cp&63)+128; + return 3; + } + else if (cp <= 0x10FFFF) { + c[0] = (cp>>18)+240; + c[1] = ((cp>>12)&63)+128; + c[2] = ((cp>>6)&63)+128; + c[3] = (cp&63)+128; + return 4; + } else { + return 0; + } +} + +static int32_t read_codepoint_utf16(std::u16string_view &source) { + if (source.empty()) return -1; + + int32_t word0 = ((const uint16_t *)source.data())[0]; + source.remove_prefix(1); + + if (word0 < 0xD800) { + return word0; + } else if (word0 < 0xDC00) { + if (source.empty()) { + return -2; + } + int32_t word1 = ((const uint16_t *)source.data())[0]; + if ((word1 < 0xDC00)||(word1 > 0xDFFF)) { + return -2; + } + int32_t part1 = word0 & 0x3FF; + int32_t part2 = word1 & 0x3FF; + int32_t result = ((part1 << 10) | part2) + 0x10000; + source.remove_prefix(1); + return result; + } else if (word0 < 0xE000) { + return -2; + } else { + return word0; + } +} + +static int32_t read_codepoint_utf8(std::string_view &source) { + size_t size = source.size(); + if (size == 0) return -1; + + const unsigned char *bytes = (const unsigned char *)source.data(); + int codepoint; + size_t seqlen; + if ((bytes[0] & 0x80) == 0x00) { + // U+0000 to U+007F + codepoint = (bytes[0] & 0x7F); + seqlen = 1; + } else if ((bytes[0] & 0xE0) == 0xC0) { + // U+0080 to U+07FF + codepoint = (bytes[0] & 0x1F); + seqlen = 2; + } else if ((bytes[0] & 0xF0) == 0xE0) { + // U+0800 to U+FFFF + codepoint = (bytes[0] & 0x0F); + seqlen = 3; + } else if ((bytes[0] & 0xF8) == 0xF0) { + // U+10000 to U+10FFFF + codepoint = (bytes[0] & 0x07); + seqlen = 4; + } else { + // Bad character. return invalid CP. + return -2; + } + + if (seqlen > size) { + return -1; + } + + for (size_t i = 1; i < seqlen; ++i) { + if ((bytes[i] & 0xC0) != 0x80) { + // Bad character. return invalid CP. + return -2; + } + codepoint = (codepoint << 6) | (bytes[i] & 0x3F); + } + + if ((codepoint > 0x10FFFF) || + ((codepoint >= 0xD800) && (codepoint <= 0xDFFF)) || + ((codepoint <= 0x007F) && (seqlen != 1)) || + ((codepoint >= 0x0080) && (codepoint <= 0x07FF) && (seqlen != 2)) || + ((codepoint >= 0x0800) && (codepoint <= 0xFFFF) && (seqlen != 3)) || + ((codepoint >= 0x10000) && (codepoint <= 0x1FFFFF) && (seqlen != 4))) { + // Bad character. return invalid CP. + return -2; + } + + source.remove_prefix(seqlen); + return codepoint; +} + +std::string utf32_to_utf8(const std::u32string &s) { + std::string result(s.size() * 4, 0); + char *buffer = &result[0]; + int len = 0; + for (char32_t c : s) { + int clen = buffer_codepoint_utf8(c, buffer + len); + len += clen; + } + return result.substr(0, len); +} + +std::u32string utf8_to_utf32(std::string_view s, int *consumed) { + std::string_view rest = s; + std::u32string result(s.size(), 0); + int len = 0; + while (true) { + int32_t c = read_codepoint_utf8(rest); + if (c == -1) { + break; // EOF reached; + } else if (c < 0) { + rest.remove_prefix(1); + } else { + result[len++] = (char32_t)c; + } + } + if (consumed != nullptr) { + *consumed = s.size() - rest.size(); + } + return result.substr(0, len); +} + +std::u16string utf8_to_ucs2(std::string_view s, int *consumed) { + std::string_view rest = s; + std::u16string result(s.size(), 0); + int len = 0; + while (true) { + int32_t c = read_codepoint_utf8(rest); + if (c == -1) { + break; // EOF reached; + } else if (c < 0) { + rest.remove_prefix(1); + } else if ((c >= 0xD800) && (c <= 0xDFFF)) { + result[len++] = 0x2610; + } else if (c > 0xFFFF) { + result[len++] = 0x2610; + } else { + result[len++] = (char16_t)c; + } + } + if (consumed != nullptr) { + *consumed = s.size() - rest.size(); + } + return result.substr(0, len); +} + +std::string utf16_to_utf8(std::u16string_view s) { + std::string result(s.size() * 4, 0); + int len = 0; + while (true) { + int codepoint = read_codepoint_utf16(s); + if (codepoint == -1) break; + if (codepoint < 0) continue; + len += buffer_codepoint_utf8(codepoint, &result[len]); + } + return result.substr(0, len); +} + +static std::vector parse_control_lst(std::string_view ctrl) { + std::vector result; + while (!ctrl.empty()) { + std::string_view line = read_to_line(ctrl); + std::string_view trimmed = trim(line); + if ((trimmed.size() > 0) && (trimmed[0] != '#')) { + result.emplace_back(trimmed); + } + } + return result; +} + +// Read a source file into a string. +// +static std::string read_file(const std::filesystem::path &fn, std::string &err) { + std::ifstream t(fn); + if (t.fail()) { + err = std::string("Could not open ") + fn.u8string(); + return ""; + } + t.seekg(0, std::ios::end); + size_t size = t.tellg(); + std::string result(size, ' '); + t.seekg(0); + t.read(&result[0], size); + if ((t.fail()) || (size_t(t.tellg()) != size)) { + err = std::string("Could not read ") + fn.u8string(); + return ""; + } + err = ""; + return result; +} + +// This encoding can be read by StreamBuffer::read_uint32. +// +static void sbwrite_uint32(std::ostream *s, uint32_t v) { + s->write((const char *)&v, 4); +} + +// This encoding can be read by StreamBuffer::read_uint64. +// +static void sbwrite_uint64(std::ostream *s, uint64_t v) { + s->write((const char *)&v, 8); +} + +// This encoding can be read by StreamBuffer::read_string. +// +static void sbwrite_string(std::ostream *s, std::string_view sv) { + s->put('\xFF'); + sbwrite_uint64(s, sv.size()); + s->write(sv.data(), sv.size()); +} + +// This encoding can be read by StreamBuffer::read_string. +// +static bool sbwrite_file(std::ostream *s, const std::filesystem::path &fn) { + s->put('\xFF'); + uint64_t pos1 = s->tellp(); + sbwrite_uint64(s, 0); + uint64_t pos2 = s->tellp(); + std::ifstream t(fn); + if (t.fail()) { + return false; + } + *s << t.rdbuf(); + if (t.fail()) { + return false; + } + uint64_t pos3 = s->tellp(); + s->seekp(pos1); + sbwrite_uint64(s, pos3 - pos2); + s->seekp(pos3); + return true; +} + +std::string package_lua_source(const std::filesystem::path &base, std::ostream *s) { + std::string err; + std::filesystem::path cfn = base / "lua/control.lst"; + std::string ctrl = read_file(cfn, err); + if (!err.empty()) { + return err; + } + + std::vector names = parse_control_lst(ctrl); + sbwrite_uint32(s, names.size()); + for (int i = 0; i < int(names.size()); i++) { + sbwrite_string(s, names[i]); + std::filesystem::path lfn = base / "lua" / names[i]; + if (!sbwrite_file(s, lfn)) { + return std::string("Cannot read source file: ") + lfn.u8string(); + } + } + return ""; +} + + +} // namespace drv \ No newline at end of file diff --git a/luprex/cpp/drv/drvutil.hpp b/luprex/cpp/drv/drvutil.hpp new file mode 100644 index 00000000..c670c238 --- /dev/null +++ b/luprex/cpp/drv/drvutil.hpp @@ -0,0 +1,137 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// DRIVER_UTIL +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef DRVUTIL_HPP +#define DRVUTIL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace drvutil { + +// Read the lua source from disk into an ostringstream. +// +// To pass the lua source into the DLL, here is what you do: Construct an +// ostringstream. Use package_lua_source to package all the lua source into +// the ostringstream. Fetch the packaged source code using ostringstream::str. +// Pass the packaged source code into drv_set_lua_source. +// +// The DLL must then decode the source package. Here is how it does that: +// It creates a StreamBuffer from the packaged up source. Then it must +// call these StreamBuffer methods: +// +// - read the number of source files using read_uint32. +// - for each file, read the filename using read_string. +// - for each file, read the contents using read_string. +// +// If package_lua_source encounters an error reading the source code, then it +// returns an error message. In this case, the ostream contains garbage. If +// there is no error, returns the empty string. +// +std::string package_lua_source(const std::filesystem::path &base, std::ostream *oss); + +// Parse a target designation. +// +// A target consists of 'cert::host::port'. +// +void split_target(std::string_view target, std::string &cert, std::string &host, std::string &port); + +// Return true if the unicode codepoint can be converted to a single 16-bit wchar_t. +// +bool is_single_wchar_t(char32_t c); + +// Convert a codepoint string into a UTF8-string. +// If the codepoint string contains invalid codepoints, they're silently dropped. +// +std::string utf32_to_utf8(const std::u32string &cps); + +// Convert a UTF8 string to a UTF32 string. +// +// If the UTF8 string contains invalid sequences, they're silently dropped. +// Some of the bytes may not be consumed, if the source ends with an unfinished +// utf-8 sequence. Returns the Codepoint string and the number of bytes consumed. +// +std::u32string utf8_to_utf32(std::string_view source, int *consumed); + +// Convert a UTF8 string to a UCS-2 string. +// +// If the UTF8 string contains invalid sequences, they're silently dropped. +// Some of the bytes may not be consumed, if the source ends with an unfinished +// utf-8 sequence. Returns the UCS-2 string and the number of bytes consumed. +// Of course, UCS-2 can't represent all of unicode, so this is lossy. +// Any character that can't be represented is replaced with a box. +// +std::u16string utf8_to_ucs2(std::string_view source, int *consumed); + +// Convert a UTF16 string to a UTF8 string. +// +// If the UTF16 string contains invalid sequences, they're silently dropped. +// +std::string utf16_to_utf8(std::u16string_view source); + +// Get a system error message, in an OS-independent manner. +// +// These versions of strerror is thread-safe, and it never fails +// to put a message into the buffer. +// +void strerror_safe(int errnum, char result[256]); +std::string strerror_str(int errnum); + +// Get the amount of time elapsed since program start. +// +// This is guaranteed to be monotonically increasing. It is not +// guaranteed to be accurate. Error could gradually accumulate over +// time. +// +double get_monotonic_clock(); + +// drvutil::ostringstream +// +// This is a variant of ostringstream in which it is possible +// to get the contents without copying. To get the contents +// without copying, use oss.size() and oss.c_str() +// +class ostringstream : public std::ostringstream { + class rstringbuf : public std::basic_stringbuf { + public: + char *eback() const { return std::streambuf::eback(); } + char *pptr() const { return std::streambuf::pptr(); } + }; + rstringbuf rstringbuf_; +public: + ostringstream() { + std::basic_ostream::rdbuf(&rstringbuf_); + } + std::string_view view() const { + char *p = rstringbuf_.eback(); + size_t size = rstringbuf_.pptr() - p; + return std::string_view(p, size); + } + std::string str() const { + return rstringbuf_.str(); + } +}; + + +// Remove items from a vector that are marked for deletion. +// +template +void remove_marked_items(T &vec) { + auto iter = std::partition(vec.begin(), vec.end(), [] (const auto &x) { return !x.marked_for_deletion(); }); + vec.erase(iter, vec.end()); +} + + +} // namespace drvutil + +#endif // DRVUTIL_HPP diff --git a/luprex/cpp/drv/osdrvutil.cpp b/luprex/cpp/drv/osdrvutil.cpp new file mode 100644 index 00000000..4f7d5642 --- /dev/null +++ b/luprex/cpp/drv/osdrvutil.cpp @@ -0,0 +1,116 @@ +#include "osdrvutil.hpp" + +#if defined(__linux__) + #include +#elif defined(_WIN32) + #include + #include +#else + #error "Only support __linux__ or _WIN32" +#endif + +#include +#include +#include +#include + +// strerror has to be the most overcomplicated function imaginable. The simple +// version, 'strerror', is not thread-safe, and the improved versions are all +// incompatible from OS to OS. Even different versions of linux aren't +// compatible. A lot of conditional compilation is needed. + +#if defined(__linux__) + +inline static void strerror_helper(int status, int errnum, char errbuf[256]) { + if (status != 0) { + snprintf(errbuf, 256, "unknown errno %d", errnum); + } +} + +inline static void strerror_helper(const char *result, int errnum, char errbuf[256]) { + if (result != errbuf) { + snprintf(errbuf, 256, "%s", result); + } +} + +static void strerror_safe(int errnum, char errbuf[256]) { + auto rval = strerror_r(errnum, errbuf, 256); + strerror_helper(rval, errnum, errbuf); +} + +#elif defined(_WIN32) + +static void strerror_safe(int errnum, char errbuf[256]) { + int status = strerror_s(errbuf, 256, errnum); + if (status != 0) { + snprintf(errbuf, 256, "unknown errno %d", errnum); + } +} + +#endif + +// The monotonic clock is required to start at zero at initialization time, +// advance steadily, and never go backwards. It is okay, however, if it is a +// little inaccurate, or if it drifts a little over time. + +#if defined(__linux__) + + class MonoClock { + private: + struct timespec base_; + public: + MonoClock() { + int status = clock_gettime(CLOCK_MONOTONIC, &base_); + assert(status == 0); + } + double get() { + struct timespec t; + int status = clock_gettime(CLOCK_MONOTONIC, &t); + assert(status == 0); + double tv_sec = t.tv_sec - base_.tv_sec; + double tv_nsec = t.tv_nsec - base_.tv_nsec; + return tv_sec + (tv_nsec * 1.0E-9); + } + }; + +#elif defined(_WIN32) + + class MonoClock { + public: + double freq_; + LONGLONG base_; + inline LONGLONG qpc() { + LARGE_INTEGER x; + BOOL status = QueryPerformanceCounter(&x); + assert(status != 0); + return x.QuadPart; + } + MonoClock() { + LARGE_INTEGER x; + BOOL status = QueryPerformanceFrequency(&x); + assert(status != 0); + freq_ = 1.0 / double(x.QuadPart); + base_ = qpc(); + } + double get() { + return (qpc() - base_) * freq_; + } + }; + +#endif + + +namespace drvutil { + +static MonoClock monoclock; +double get_monotonic_clock() { + return monoclock.get(); +} + +std::string strerror_str(int errnum) { + char buf[256]; + strerror_safe(errnum, buf); + return buf; + +} +} // namespace drvutil \ No newline at end of file diff --git a/luprex/cpp/drv/osdrvutil.hpp b/luprex/cpp/drv/osdrvutil.hpp new file mode 100644 index 00000000..34af2bc8 --- /dev/null +++ b/luprex/cpp/drv/osdrvutil.hpp @@ -0,0 +1,23 @@ +#ifndef OSDRVUTIL_HPP +#define OSDRVUTIL_HPP + +#include + +namespace drvutil { + +// Get a system error message, in an OS-independent manner. +// +std::string strerror_str(int errnum); + +// Get the amount of time elapsed since program start. +// +// This is guaranteed to be monotonically increasing. It is not +// guaranteed to be accurate. Error could gradually accumulate over +// time. +// +double get_monotonic_clock(); + +} // namespace drvutil + +#endif // OSDRVUTIL_HPP + diff --git a/luprex/cpp/drv/readline.cpp b/luprex/cpp/drv/readline.cpp new file mode 100644 index 00000000..630ec70d --- /dev/null +++ b/luprex/cpp/drv/readline.cpp @@ -0,0 +1,111 @@ +#include "readline.hpp" + +#define MAXLINE 512 + +static std::u32string n_backspaces(int n) { + std::u32string result(3 * n, 0); + for (int i = 0; i < n; i++) { + result[i*3 + 0] = '\b'; + result[i*3 + 1] = ' '; + result[i*3 + 2] = '\b'; + } + return result; +} + +static int common_prefix_length(const std::u32string &a, const std::u32string &b) { + int minlen = std::min(a.size(), b.size()); + for (int i = 0; i < minlen; i++) { + if (a[i] != b[i]) return i; + } + return minlen; +} + +void ReadlineDevice::set_print_callback(print_callback cb) { + print_cb_ = cb; +} + +void ReadlineDevice::set_prompt(const std::u32string &prompt) { + desired_prompt_ = prompt; + echo_command(); +} + +void ReadlineDevice::erase_command() { + int ccsize = current_prompt_.size() + current_command_.size(); + if (ccsize > 0) { + print_cb_(n_backspaces(ccsize)); + current_prompt_.clear(); + current_command_.clear(); + } +} + +void ReadlineDevice::echo_command() { + // If the prompt has changed, erase everything and start over. + if (desired_prompt_ != current_prompt_) { + int ccsize = current_prompt_.size() + current_command_.size(); + print_cb_(n_backspaces(ccsize)); + print_cb_(desired_prompt_); + current_command_.clear(); + current_prompt_ = desired_prompt_; + } + + // Find out how much of the command matches. + int match = common_prefix_length(current_command_, desired_command_); + + // Echo backspaces to remove the non-matching part. + int remove = current_command_.size() - match; + if (remove > 0) { + print_cb_(n_backspaces(remove)); + current_command_ = current_command_.substr(0, match); + } + + // Echo the new part. + std::u32string newpart = desired_command_.substr(current_command_.size()); + if (!newpart.empty()) { + print_cb_(newpart); + current_command_ = desired_command_; + } +} + +std::u32string ReadlineDevice::putcode(char32_t c) { + if ((c == '\n') && (readline_lastc_ == '\r')) { + // Ignore newline immediately after carriage return. + // Otherwise, crlf produces two newlines. + return std::u32string(); + } else if ((c == '\r') || (c == '\n')) { + std::u32string white(1, ' '); + std::u32string newline(1, '\n'); + echo_command(); + print_cb_(white + newline); + std::u32string result = desired_command_ + newline; + desired_command_.clear(); + current_prompt_.clear(); + current_command_.clear(); + echo_command(); + return result; + } else if ((c == '\b') || (c == 127)) { + int len = desired_command_.size(); + if (len > 0) { + desired_command_ = desired_command_.substr(0, len-1); + } + echo_command(); + return std::u32string(); + } else if ((c >= 32)&&(c <= 0x10FFFF)) { + int len = desired_command_.size(); + if (len < MAXLINE) { + desired_command_ = desired_command_ + c; + } + echo_command(); + return std::u32string(); + } + readline_lastc_ = c; + return std::u32string(); +} + +void ReadlineDevice::print(const std::u32string &s) { + if (!s.empty()) { + erase_command(); + print_cb_(s); + echo_command(); + } +} + diff --git a/luprex/cpp/drv/readline.hpp b/luprex/cpp/drv/readline.hpp new file mode 100644 index 00000000..78cfe3e7 --- /dev/null +++ b/luprex/cpp/drv/readline.hpp @@ -0,0 +1,43 @@ + +#ifndef READLINE_HPP +#define READLINE_HPP + +#include +#include +#include "drvutil.hpp" + + +class ReadlineDevice { +public: + using print_callback = void (*)(const std::u32string &text); + +private: + print_callback print_cb_; + std::u32string desired_command_; + std::u32string current_command_; + std::u32string desired_prompt_; + std::u32string current_prompt_; + char32_t readline_lastc_; + + void erase_command(); + void echo_command(); + + +public: + // The callback must be set before using the readline device. + void set_print_callback(print_callback cb); + + // change the prompt. + void set_prompt(const std::u32string &prompt); + + // Use this to print anything on the console. + void print(const std::u32string &cps); + + // Whenever the user types a character, call 'putcode'. If the code is + // newline, this returns the line of text that was entered, including the + // newline. Otherwise returns empty string. Backspace is handled here. + std::u32string putcode(char32_t codepoint); +}; + + +#endif // READLINE_HPP diff --git a/luprex/cpp/drv/sslutil.cpp b/luprex/cpp/drv/sslutil.cpp new file mode 100644 index 00000000..dbae9be2 --- /dev/null +++ b/luprex/cpp/drv/sslutil.cpp @@ -0,0 +1,217 @@ +#include "drvutil.hpp" +#include "osdrvutil.hpp" +#include "sslutil.hpp" +#include +#include +#include +#include + +namespace sslutil { + +const char *dummy_cert = + "-----BEGIN CERTIFICATE-----\n" + "MIIDezCCAmOgAwIBAgIUajKmxrLMr9zBMlphrTJU5qKG8FgwDQYJKoZIhvcNAQEL\n" + "BQAwTDELMAkGA1UEBhMCVVMxFTATBgNVBAgMDFBlbm5zeWx2YW5pYTESMBAGA1UE\n" + "CgwJbG9jYWxob3N0MRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjIwMzIyMTczMzA4\n" + "WhgPMjEyMjAyMjYxNzMzMDhaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQIDAxQZW5u\n" + "c3lsdmFuaWExEjAQBgNVBAoMCWxvY2FsaG9zdDESMBAGA1UEAwwJbG9jYWxob3N0\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5OWIaKqYae4nPxvu5EP3\n" + "VilcjApYcMT4+2ypfQoB6PEep5lwguA929rNsTKnhGsEiQAZ0eZPEZN7VhUwf/hz\n" + "26jIyTT43ELkt6k97wwSZSXuT65RpSiemwEs6g2mMwzpgP6nv+yam4HjE9AKiHGN\n" + "YeTV72Nw1EN70t6IjIf4jsJRXqDJkUx5sSSD6j0WBTOhzozIDgZHTDwiLhatE66m\n" + "SNoD8oWC0PscbUgOJkFpbaCAS8RJmpsdgkTFae2rzL9cOFLGw6OgV/BV1J1s0ks8\n" + "+veoMMtIO6fese+OZ+DyQbuGaoaltZUXzY6QjD5l34m2mGplelT7BrpcqJTBHwmh\n" + "CwIDAQABo1MwUTAdBgNVHQ4EFgQUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wHwYDVR0j\n" + "BBgwFoAUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wDwYDVR0TAQH/BAUwAwEB/zANBgkq\n" + "hkiG9w0BAQsFAAOCAQEAqYX/ZGv0Qh/xdXppjnqojm8mH0giDW4tvwMqHcW3YRa3\n" + "9J2yYot+rHjU5g4n6HEmWDBE0eqLz9n3Y3fkFzT8RWZwBaST965CgsfGofyuA2hC\n" + "Ddn4Am3B5tTPmi8WWRZg8amhpGVD/mwkoVFIK0M337b1aZUJYPE+Kc9WetSL2KqB\n" + "EhqSQpkAWhVadzP85dq2T9EDjAvhlFTFlDEBx1GDUcc8M0KQ9NEvLT7LgoUcbMiT\n" + "PerlSZQTB0crchXTRSERgiwu80r7D6STn/RcPL9Fg5PkA94/d87jGbmV4sxSRsvM\n" + "z+DnJGjHrV1J/jHPrnVvVLpigBlGno3C5O/sRw3gcQ==\n" + "-----END CERTIFICATE-----\n"; + +const char *dummy_key = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDk5Yhoqphp7ic/\n" + "G+7kQ/dWKVyMClhwxPj7bKl9CgHo8R6nmXCC4D3b2s2xMqeEawSJABnR5k8Rk3tW\n" + "FTB/+HPbqMjJNPjcQuS3qT3vDBJlJe5PrlGlKJ6bASzqDaYzDOmA/qe/7JqbgeMT\n" + "0AqIcY1h5NXvY3DUQ3vS3oiMh/iOwlFeoMmRTHmxJIPqPRYFM6HOjMgOBkdMPCIu\n" + "Fq0TrqZI2gPyhYLQ+xxtSA4mQWltoIBLxEmamx2CRMVp7avMv1w4UsbDo6BX8FXU\n" + "nWzSSzz696gwy0g7p96x745n4PJBu4ZqhqW1lRfNjpCMPmXfibaYamV6VPsGulyo\n" + "lMEfCaELAgMBAAECggEBAJa1AiFX4U4tva1xqNKmZV1XklWqIhzts7lnDBkF08gZ\n" + "qcNT5Z5mIpR09eVropwvEidZ56Yp63l5D0XYYbyAS1gfQ0QnGot7h7fdOKgB3MK4\n" + "PLY94gfKPNN17KqWHg2SvNNv1+cn04v78xUCb0zy5tHDp5Acexdm70ohtupARElJ\n" + "LSHdS7ebsqZUFXbbM3BpPEsQLi3PrzNs1DrKkZ3rR6eMGrsDqExXx8/foi9aZKsd\n" + "BGM2/kcTJ5aY6NhSv5iqO1oK46sbMrjVW/bYNsOyl0eFjwTRahn+Zhp/JMewZYeu\n" + "715g6kzbZNwEzBLgrhNPF6E2ycEr/C6z5bE78g5QCkECgYEA8s07UUY25bjYiWWy\n" + "W38pT7d/OXBSyKnq16N6MjVahl29r7nezFiDeLhLC0QiwXu/+qyxVZkB95MMGZXS\n" + "AsaKFNis3AJ6eR4SYyhpSScYKNvlKIiW37TtR4FDcy7y5LL6tFpiDDIGH3LuyWNo\n" + "d76142MBpv5aStnLGYU3pcZj43sCgYEA8VbNM4nqgSCQcbnHYjvsgphEMNSaoVie\n" + "xob2uigXdV6Te0ayoUFBnVNKVsRhk+sswuTV4k1pK/On+USVl2tQ16tcaVMjTfSD\n" + "HLYTJLmt6s4DcywWj5dfkbDoe5PulGXNZE960qXmOC62Lf0VMRwJ5x4FBRvGTjKC\n" + "zvekI2/kO7ECgYEAhBGeclb/BXXGUvY+TgadMf9d9KBkZ0IFu8Xwcd8TnoLe6vbv\n" + "ebery75zE228egIWKwREcYsIxuH1cvVLhrb35N73J7UxaTAyUD1rB598RL1XqPSj\n" + "HIwNhReK2NxwwnWYaQHA02FiczjRKjooWPojdcwk2fEArDZLg1YzLrj7HIECgYEA\n" + "htdx1Y8ESFtyeShMv5UtoxYCW6oeL3H9XH0CE6bc3IYYLvOkULbOO2HTEkGtJ2Fp\n" + "5AbJfiS0U4tS2dI5Jp4eUDH9cxexjRfFvd/5ODbKdnver5X9kQMJsbQ/YPSZg66R\n" + "oK9Lt7Bbvh5TScSy93psCgba1SzckspkDdGNkwMsaTECgYEAnFWaxormLUpXQRLs\n" + "tKzMMHgVnHlsHiqXH432zmT2fpGZHYoWbsGuQjjrHGnSiu3QbDhnzM6y/T2GRs6z\n" + "zHteIo/tzIyxg4MvJGJ9qANA7HoiKBdQ7G/I/NLJIyWAjj+e7/hgzKFcf+dpjpDq\n" + "HcKc9a4WXhC7yu79e5BnKWltHXY=\n" + "-----END PRIVATE KEY-----\n"; + +std::string error_string() { + // Get the last code. + int code = 0; + while (true) { + int icode = ERR_get_error(); + if (icode == 0) break; + code = icode; + } + + // Fetch and clear errno. + int terrno = errno; + errno = 0; + + if (code != 0) { + const char *rc = ERR_reason_error_string(code); + if (rc != nullptr) { + return rc; + } else { + return drvutil::strerror_str(ERR_GET_REASON(code)); + } + } else if (terrno != 0) { + return drvutil::strerror_str(terrno); + } else { + return ""; + } +} + +// Make sure the path only contains printable ascii characters, +// and no control characters or unicode characters. +std::string path_to_plain_printable_ascii(const std::filesystem::path &path) { + auto s = path.native(); + std::string result(' ', s.size()); + for (int i = 0; i < int(s.size()); i++) { + auto c = s[i]; + if ((c < 32) || (c > 126)) return ""; + result[i] = c; + } + return result; +} + +void clear_all_errors() { + ERR_clear_error(); + errno = 0; +} + +SSL_CTX *new_context(int verify) { + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_verify(ctx, verify, nullptr); + return ctx; +} + +static int ctx_use_certificate_str(SSL_CTX *ctx, const char *str) { + UniqueBIO bio(BIO_new(BIO_s_mem())); + BIO_puts(bio.get(), str); + UniqueX509 certificate(PEM_read_bio_X509(bio.get(), NULL, NULL, NULL)); + return SSL_CTX_use_certificate(ctx, certificate.get()); +} + +static int ctx_use_privatekey_str(SSL_CTX *ctx, const char *str) { + UniqueBIO bio(BIO_new(BIO_s_mem())); + BIO_puts(bio.get(), str); + UniquePKEY pkey(PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL)); + return SSL_CTX_use_PrivateKey(ctx, pkey.get()); +} + +void ctx_load_dummy_cert(SSL_CTX *ctx) { + ERR_clear_error(); + if (ctx_use_certificate_str(ctx, dummy_cert) <= 0) { + ERR_print_errors_fp(stderr); + exit(1); + } + if (ctx_use_privatekey_str(ctx, dummy_key) <= 0) { + ERR_print_errors_fp(stderr); + exit(1); + } +} + +static int count_certificates(const char *fn) { + static char null_passwd; + ErrClearErrorOnExit ece; + UniqueBIO bio(BIO_new(BIO_s_file())); + assert(bio != nullptr); + if (BIO_read_filename(bio.get(), fn) <= 0) { + std::cerr << "Cannot open file: " << fn << std::endl; + exit(1); + } + int total = 0; + while (true) { + UniqueX509 x(PEM_read_bio_X509_AUX(bio.get(), nullptr, nullptr, &null_passwd)); + if (x == nullptr) break; + total += 1; + } + return total; +} + +static bool contains_privatekey(const char *fn) { + static char null_passwd; + ErrClearErrorOnExit ece; + UniqueBIO bio(BIO_new(BIO_s_file())); + assert(bio != nullptr); + if (BIO_read_filename(bio.get(), fn) <= 0) { + std::cerr << "Cannot open file: " << fn << std::endl; + exit(1); + } + UniquePKEY k(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, &null_passwd)); + return k != nullptr; +} + +void ctx_load_cert_from_directory(SSL_CTX *ctx, const std::string &dir) { + std::vector key_paths; + std::vector cert_paths; + + for (const auto & entry : std::filesystem::directory_iterator(dir)) { + std::string fn = path_to_plain_printable_ascii(entry.path()); + if (fn.empty()) { + std::cerr << "Ignoring file with non-ascii filename: " << entry.path() << std::endl; + } else { + if (count_certificates(fn.c_str()) >= 1) { + cert_paths.push_back(fn); + } + if (contains_privatekey(fn.c_str())) { + key_paths.push_back(fn); + } + } + } + + if (cert_paths.size() > 1) { + std::cerr << "Directory contains multiple certs: " << dir << std::endl; + exit(1); + } + if (key_paths.size() > 1) { + std::cerr << "Directory contains multiple keys: " << dir << std::endl; + exit(1); + } + if (cert_paths.empty()) { + std::cerr << "Directory doesn't contain a cert: " << dir << std::endl; + exit(1); + } + if (key_paths.empty()) { + std::cerr << "Directory doesn't contain a key: " << dir << std::endl; + exit(1); + } + + int status; + status = SSL_CTX_use_PrivateKey_file(ctx, key_paths[0].c_str(), SSL_FILETYPE_PEM); + assert(status == 1); + status = SSL_CTX_use_certificate_chain_file(ctx, cert_paths[0].c_str()); + assert(status == 1); +} + +} // namespace sslutil + diff --git a/luprex/cpp/drv/sslutil.hpp b/luprex/cpp/drv/sslutil.hpp new file mode 100644 index 00000000..aaf7b55f --- /dev/null +++ b/luprex/cpp/drv/sslutil.hpp @@ -0,0 +1,59 @@ +#ifndef SSLUTIL_HPP +#define SSLUTIL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace sslutil { + +struct SSL_Deleter { + void operator()(SSL *ssl) { SSL_free(ssl); } +}; + +struct CTX_Deleter { + void operator()(SSL_CTX *ctx) { SSL_CTX_free(ctx); } +}; + +struct BIO_Deleter { + void operator()(BIO *bio) { BIO_free(bio); } +}; + +struct X509_Deleter { + void operator()(X509 *x) { X509_free(x); } +}; + +struct PKEY_Deleter { + void operator()(EVP_PKEY *p) { EVP_PKEY_free(p); } +}; + +using UniqueSSL = std::unique_ptr; +using UniqueCTX = std::unique_ptr; +using UniqueBIO = std::unique_ptr; +using UniqueX509 = std::unique_ptr; +using UniquePKEY = std::unique_ptr; + +struct ErrClearErrorOnExit { + ~ErrClearErrorOnExit() { + ERR_clear_error(); + } +}; + +// Return the OpenSSL error as a string. +std::string error_string(); +void clear_all_errors(); +SSL_CTX *new_context(int verify); +void ctx_load_dummy_cert(SSL_CTX *ctx); +void ctx_load_cert_from_directory(SSL_CTX *ctx, const std::string &dir); + +} // namespace sslutil + +#endif // SSLUTIL_HPP + diff --git a/luprex/cpp/wrap/mkstub.py b/luprex/cpp/wrap/mkstub.py new file mode 100755 index 00000000..568aa722 --- /dev/null +++ b/luprex/cpp/wrap/mkstub.py @@ -0,0 +1,19 @@ +#!/usr/bin/python3 + +import sys + +base=sys.argv[1] +ubase=base.upper() +dash=base.replace("_", "-") + +with open(f"wrap-{dash}.hpp", "w") as f: + print(f"#ifndef WRAP_{ubase}_HPP", file=f) + print(f"#define WRAP_{ubase}_HPP", file=f) + print("", file=f) + print('#include "eng-malloc.hpp"', file=f) + print(f"#include <{base}>", file=f) + print("", file=f) + print("namespace eng {", file=f) + print("} // namespace eng", file=f) + print("", file=f) + print(f"#endif // WRAP_{ubase}_HPP", file=f) diff --git a/luprex/cpp/wrap/wrap-bytell-hash-map.hpp b/luprex/cpp/wrap/wrap-bytell-hash-map.hpp new file mode 100644 index 00000000..23bf3ed4 --- /dev/null +++ b/luprex/cpp/wrap/wrap-bytell-hash-map.hpp @@ -0,0 +1,14 @@ +#ifndef WRAP_BYTELL_HASH_MAP_HPP +#define WRAP_BYTELL_HASH_MAP_HPP + +#include "eng-malloc.hpp" +#include "bytell-hash-map.hpp" + +namespace eng { +template, class E=std::equal_to> +class bytell_hash_map : public ska::bytell_hash_map>>, public eng::opnew { + using ska::bytell_hash_map>>::bytell_hash_map; +}; +} // namespace eng + +#endif // WRAP_BYTELL_HASH_MAP_HPP diff --git a/luprex/cpp/wrap/wrap-deque.hpp b/luprex/cpp/wrap/wrap-deque.hpp new file mode 100644 index 00000000..6648864e --- /dev/null +++ b/luprex/cpp/wrap/wrap-deque.hpp @@ -0,0 +1,14 @@ +#ifndef WRAP_DEQUE_HPP +#define WRAP_DEQUE_HPP + +#include "eng-malloc.hpp" +#include + +namespace eng { +template +class deque : public std::deque>, public eng::opnew { + using std::deque>::deque; +}; +} // namespace eng + +#endif // WRAP_DEQUE_HPP diff --git a/luprex/cpp/wrap/wrap-map.hpp b/luprex/cpp/wrap/wrap-map.hpp new file mode 100644 index 00000000..0077eca5 --- /dev/null +++ b/luprex/cpp/wrap/wrap-map.hpp @@ -0,0 +1,14 @@ +#ifndef WRAP_MAP_HPP +#define WRAP_MAP_HPP + +#include "eng-malloc.hpp" +#include + +namespace eng { +template> +class map : public std::map>>, eng::opnew { + using std::map>>::map; +}; +} // namespace eng + +#endif // WRAP_MAP_HPP diff --git a/luprex/cpp/wrap/wrap-set.hpp b/luprex/cpp/wrap/wrap-set.hpp new file mode 100644 index 00000000..dba5a388 --- /dev/null +++ b/luprex/cpp/wrap/wrap-set.hpp @@ -0,0 +1,14 @@ +#ifndef WRAP_SET_HPP +#define WRAP_SET_HPP + +#include "eng-malloc.hpp" +#include + +namespace eng { +template> +class set : public std::set>, public eng::opnew { + using std::set>::set; +}; +} // namespace eng + +#endif // WRAP_SET_HPP diff --git a/luprex/cpp/wrap/wrap-sstream.hpp b/luprex/cpp/wrap/wrap-sstream.hpp new file mode 100644 index 00000000..eb60b0ee --- /dev/null +++ b/luprex/cpp/wrap/wrap-sstream.hpp @@ -0,0 +1,21 @@ +#ifndef WRAP_SSTREAM_HPP +#define WRAP_SSTREAM_HPP + +#include "eng-malloc.hpp" +#include "wrap-string.hpp" +#include +#include + +namespace eng { +template> +class basic_ostringstream : public std::basic_ostringstream>, public eng::opnew { + using underlying = std::basic_ostringstream>; + using underlying::basic_ostringstream; +}; +//template> +//using basic_stringbuf = std::basic_stringbuf>; +using ostringstream = basic_ostringstream; +//using stringbuf = basic_stringbuf; +} // namespace eng + +#endif // WRAP_SSTREAM_HPP diff --git a/luprex/cpp/wrap/wrap-string.hpp b/luprex/cpp/wrap/wrap-string.hpp new file mode 100644 index 00000000..df54cc62 --- /dev/null +++ b/luprex/cpp/wrap/wrap-string.hpp @@ -0,0 +1,13 @@ +#ifndef WRAP_STRING_HPP +#define WRAP_STRING_HPP + +#include "eng-malloc.hpp" +#include + +namespace eng { +template> +using basic_string = std::basic_string>; +using string = basic_string; +} // namespace eng + +#endif // WRAP_STRING_HPP diff --git a/luprex/cpp/wrap/wrap-unordered-map.hpp b/luprex/cpp/wrap/wrap-unordered-map.hpp new file mode 100644 index 00000000..5b187f41 --- /dev/null +++ b/luprex/cpp/wrap/wrap-unordered-map.hpp @@ -0,0 +1,14 @@ +#ifndef WRAP_UNORDERED_MAP_HPP +#define WRAP_UNORDERED_MAP_HPP + +#include "eng-malloc.hpp" +#include + +namespace eng { +template, class E=std::equal_to> +class unordered_map : public std::unordered_map>>, public eng::opnew { + using std::unordered_map>>::unordered_map; +}; +} // namespace eng + +#endif // WRAP_UNORDERED_MAP_HPP diff --git a/luprex/cpp/wrap/wrap-unordered-set.hpp b/luprex/cpp/wrap/wrap-unordered-set.hpp new file mode 100644 index 00000000..08640258 --- /dev/null +++ b/luprex/cpp/wrap/wrap-unordered-set.hpp @@ -0,0 +1,14 @@ +#ifndef WRAP_UNORDERED_SET_HPP +#define WRAP_UNORDERED_SET_HPP + +#include "eng-malloc.hpp" +#include + +namespace eng { +template, class E=std::equal_to> +class unordered_set : public std::unordered_set>, public eng::opnew { + using std::unordered_set>::unordered_set; +}; +} // namespace eng + +#endif // WRAP_UNORDERED_SET_HPP diff --git a/luprex/cpp/wrap/wrap-vector.hpp b/luprex/cpp/wrap/wrap-vector.hpp new file mode 100644 index 00000000..30a273d0 --- /dev/null +++ b/luprex/cpp/wrap/wrap-vector.hpp @@ -0,0 +1,14 @@ +#ifndef WRAP_VECTOR_HPP +#define WRAP_VECTOR_HPP + +#include "eng-malloc.hpp" +#include + +namespace eng { +template +class vector : public std::vector>, public eng::opnew { + using std::vector>::vector; +}; +} // namespace eng + +#endif // WRAP_VECTOR_HPP diff --git a/luprex/diffs b/luprex/diffs new file mode 100644 index 00000000..e69de29b diff --git a/luprex/ext/base-buffer.hpp b/luprex/ext/base-buffer.hpp new file mode 100644 index 00000000..ee3d22e3 --- /dev/null +++ b/luprex/ext/base-buffer.hpp @@ -0,0 +1,771 @@ +#pragma once + +///////////////////////////////////////////////////////////////// +// +// IMPORTANT: This is a header-only library that is included +// by the graphics engine as well. It cannot contain references +// to anything else in the engine. +// +///////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////// +// +// SimpleDynamic +// +// A struct that holds a dynamically typed value. +// This can hold a string, number, vector, or boolean. +// +// The type is stored in the 'type' field. +// +// If it's a STRING, the value is in the field s +// If it's a NUMBER, the value is in the field x +// If it's a BOOLEAN, it's true if (x==1.0) +// If it's a VECTOR, the value is in x,y,z +// +/////////////////////////////////////////////////////////////// + +enum class SimpleDynamicTag { + UNINITIALIZED, + AUTO, + STRING, + NUMBER, + BOOLEAN, + VECTOR, +}; + +template +struct SimpleDynamic { + using string = STRING; + SimpleDynamicTag type; + double x, y, z; + string s; + + SimpleDynamic() { + type = SimpleDynamicTag::UNINITIALIZED; + x=y=z=0; + } + + static const char *type_name_of(SimpleDynamicTag t) { + switch (t) { + case SimpleDynamicTag::UNINITIALIZED: return "uninitialized"; + case SimpleDynamicTag::BOOLEAN: return "boolean"; + case SimpleDynamicTag::NUMBER: return "number"; + case SimpleDynamicTag::STRING: return "string"; + case SimpleDynamicTag::VECTOR: return "vector"; + default: return "unknown"; + } + } + + const char *type_name() const { + return type_name_of(type); + } + + void set_uninitialized() { + type=SimpleDynamicTag::UNINITIALIZED; s.clear(); x=y=z=0; + } + + void set_auto() { + type=SimpleDynamicTag::AUTO; s.clear(); x=y=z=0; + } + + void set_string(std::string_view is) { + type=SimpleDynamicTag::STRING; s=is; x=y=z=0; + } + + void set_number(double n) { + type = SimpleDynamicTag::NUMBER; s.clear(); x=n; y=z=0; + } + + void set_boolean(bool b) { + type = SimpleDynamicTag::BOOLEAN; s.clear(); x=(b?1:0); y=z=0; + } + + void set_vector(double ix, double iy, double iz) { + type = SimpleDynamicTag::VECTOR; s.clear(); x=ix; y=iy; z=iz; + } + + void copy_value(const SimpleDynamic &other) { + type = other.type; + s=other.s; x=other.x; y=other.y; z=other.z; + } +}; + +/////////////////////////////////////////////////////////////// +// +// BaseWriter +// +// This base class provides the following methods: +// +// void write_uint8(uint64_t data) +// void write_uint16(uint64_t data) +// void write_uint32(uint64_t data) +// void write_uint64(uint64_t data) +// void write_int8(int64_t data) +// void write_int16(int64_t data) +// void write_int32(int64_t data) +// void write_int64(int64_t data) +// void write_char(char c) +// void write_float(float data) +// void write_double(double data) +// void write_length(size_t data) +// void write_string(std::string_view data) +// void write_simple_dynamic(const SimpleDynamic &sd); +// +// You should derive from BaseWriter using the CRTP pattern: +// +// class DerivedWriter : public BaseWriter +// +// You must provide two methods in the derived class: +// +// write_bytes(const char *n, size_t size) +// raise_truncated() +// +/////////////////////////////////////////////////////////////// + +template +class BaseWriter { +protected: + template + void write_value_core(T arg) { + static_cast(this)->write_bytes((const char *)&arg, sizeof(arg)); + } + + template + void write_int_core(XT arg) { + T reduced = arg; + if (XT(reduced) != arg) static_cast(this)->raise_truncated(); + write_value_core(reduced); + } + +public: + + void write_uint8(uint64_t data) { write_int_core(data); } + void write_uint16(uint64_t data) { write_int_core(data); } + void write_uint32(uint64_t data) { write_int_core(data); } + void write_uint64(uint64_t data) { write_int_core(data); } + void write_int8(int64_t data) { write_int_core(data); } + void write_int16(int64_t data) { write_int_core(data); } + void write_int32(int64_t data) { write_int_core(data); } + void write_int64(int64_t data) { write_int_core(data); } + + void write_bool(bool b) { write_uint8(b ? 1:0); } + void write_char(char c) { write_value_core(c); } + void write_float(float arg) { write_value_core(arg); } + void write_double(double arg) { write_value_core(arg); } + + void write_length(size_t len) { + if (len >= 255) { + write_uint8(0xFF); + write_uint64(len); + } else { + write_uint8(len); + } + } + + void write_string(std::string_view s) { + write_length(s.size()); + static_cast(this)->write_bytes(s.data(), s.size()); + } +}; + + +/////////////////////////////////////////////////////////////// +// +// BaseReader +// +// This base class provides the following methods: +// +// uint8_t read_uint8(); +// uint16_t read_uint16(); +// uint32_t read_uint32(); +// uint64_t read_uint64(); +// int8_t read_int8(); +// int16_t read_int16(); +// int32_t read_int32(); +// int64_t read_int64(); +// bool read_bool(); +// char read_char(); +// float read_float(); +// double read_double(); +// size_t read_length(); +// String read_string_limit(uint64_t size); +// String read_string(); +// SimpleDynamic read_simple_dynamic(); +// +// You should derive from BaseReader using the CRTP pattern: +// +// class DerivedReader : public BaseReader +// +// The derived class must provide: +// +// using read_string_type = std::string; // or compatible +// void read_bytes_into(char *n, size_t size) +// void handle_string_too_long(); +// +// Error Handling: +// +// It is up to the derived class whether it wants +// to report errors using exceptions or flags. +// +// If read_bytes_into discovers there's not enough bytes, +// there are two valid options: throw an exception, OR, +// set an error flag and fill the buffer with zeros. +// +// If read_string discovers that the string is longer than +// the allowed limit, it will call handle_string_too_long. +// This function may either throw an exception, or set an +// error flag. +// +/////////////////////////////////////////////////////////////// + +template +class BaseReader { +protected: + template + T read_value_core() { + T result; + Derived *dthis = static_cast(this); + dthis->read_bytes_into((char *)(&result), sizeof(result)); + return result; + } + +public: + + uint8_t read_uint8() { return read_value_core(); } + uint16_t read_uint16() { return read_value_core(); } + uint32_t read_uint32() { return read_value_core(); } + uint64_t read_uint64() { return read_value_core(); } + int8_t read_int8() { return read_value_core(); } + int16_t read_int16() { return read_value_core(); } + int32_t read_int32() { return read_value_core(); } + int64_t read_int64() { return read_value_core(); } + + bool read_bool() { return (bool)read_uint8(); } + char read_char() { return read_value_core(); } + float read_float() { return read_value_core(); } + double read_double() { return read_value_core(); } + + size_t read_length() { + uint64_t len = read_uint8(); + if (len == 255) { + len = read_uint64(); + } + return len; + } + + auto read_string_limit(uint64_t limit) { + size_t len = read_length(); + Derived *dthis = static_cast(this); + if (len > limit) { + dthis->raise_string_too_long(); + len = 0; + } + typename Derived::read_string_type result(len, ' '); + dthis->read_bytes_into(&(result[0]), len); + return result; + } + + auto read_string() { return read_string_limit(0x1000000); } // 16MB limit default +}; + +/////////////////////////////////////////////////////////////// +// +// Class BaseBuffer +// +// You must supply a CoreHandler which must define these +// methods: +// +// void *basebuffer_malloc(size_t size); +// void basebuffer_free(void *data); +// void raise_eof_on_read(); +// void raise_string_too_long(); +// void raise_integer_truncated(); +// +// You must also select a StringType. Typically this would +// be std::string. This only affects the return value of +// read_string. You can always use read_string_view to read +// strings into other string types. +// +/////////////////////////////////////////////////////////////// + +template +class BaseBuffer : public CoreHandler { +private: + // True if we own this buffer. + bool owned_; + + // True if we're not allowed to expand this buffer. + bool fixed_size_; + + // Start and end of the allocated block. + char *buf_lo_; + char *buf_hi_; + + // The write and read cursors. + char *write_cursor_; + char *read_cursor_; + + // Number of bytes read before buffer was last aligned. + int64_t pre_read_count_; + +private: + void init(bool fixed, bool owned, char *buf, int64_t size) { + CoreHandler::clear_error_flags(); + owned_ = owned; + fixed_size_ = fixed; + buf_lo_ = buf; + buf_hi_ = buf_lo_ + size; + read_cursor_ = buf_lo_; + write_cursor_ = buf_lo_; + pre_read_count_ = 0; + } + +public: + using string_type = StringType; + + // Construct an empty buffer. + // + BaseBuffer() { + init(false, true, 0, 0); + } + + // Construct an empty buffer, preallocate the specified amount of space. + // + BaseBuffer(int64_t size, bool fixed) { + assert(size >= 0); + init(fixed, true, (char *)CoreHandler::basebuffer_malloc(size), size); + } + + // Construct a streambuffer that reads from an external block of bytes. + // + BaseBuffer(std::string_view data) { + init(true, false, const_cast(data.data()), data.size()); + write_cursor_ = buf_hi_; + } + + // Modify an existing streambuffer to read from an external block of bytes. + // + void open(std::string_view data) { + if (owned_ && (buf_lo_ != 0)) CoreHandler::basebuffer_free(buf_lo_); + init(true, false, const_cast(data.data()), data.size()); + write_cursor_ = buf_hi_; + } + + // Destructor. Frees the buffer, if any. + // + ~BaseBuffer() { + if (owned_ && (buf_lo_ != 0)) CoreHandler::basebuffer_free(buf_lo_); + } + + // Return the total number of bytes ever read. + // + int64_t total_reads() const { + return (read_cursor_ - buf_lo_) + pre_read_count_; + } + + // Return the total number of bytes ever written. + // + int64_t total_writes() const { + return (write_cursor_ - buf_lo_) + pre_read_count_; + } + + // Return the total bytes in the buffer. + // + int64_t fill() const { + return write_cursor_ - read_cursor_; + } + + // Checks to see if the buffer is empty. + // + bool empty() const { + return write_cursor_ == read_cursor_; + } + + // Return the contents as a string_view. + // + std::string_view view() const { + return std::string_view(read_cursor_, write_cursor_ - read_cursor_); + } + + // Make the specified amount of space in the buffer for writing. + // + char *make_space(int64_t bytes) { + int64_t available = buf_hi_ - write_cursor_; + if (available < bytes) make_space_slow(bytes); + return write_cursor_; + } + + // Used after calling make_space then filling the space. + // + void wrote_space(int64_t bytes) { + int64_t available = buf_hi_ - write_cursor_; + assert(bytes >= 0); + assert(available >= bytes); + write_cursor_ += bytes; + } + + // Rewind the read cursor to a previous position. + // + void unread_to(int64_t rd_count) { + assert(rd_count >= pre_read_count_); + assert(rd_count <= total_reads()); + read_cursor_ = buf_lo_ + (rd_count - pre_read_count_); + } + + // Rewind the write cursor to a previous position. + // + void unwrite_to(int64_t wr_count) { + assert(wr_count >= total_reads()); + assert(wr_count <= total_writes()); + write_cursor_ = buf_lo_ + (wr_count - pre_read_count_); + } + + // Discard all data. Reset total read and write counts. + // May release the allocated buffer, if it is large. + // + void clear() { + if (!owned_) { + open(""); + } else { + if ((!fixed_size_) && (buf_lo_ != nullptr) && ((buf_hi_ - buf_lo_) > 100000)) { + CoreHandler::basebuffer_free(buf_lo_); + buf_lo_ = nullptr; + buf_hi_ = nullptr; + } + read_cursor_ = buf_lo_; + write_cursor_ = buf_lo_; + pre_read_count_ = 0; + } + } + + // Write block of bytes into the buffer. + // + void write_bytes(std::string_view s) { + int64_t len = s.size(); + make_space(len); + memcpy(write_cursor_, s.data(), len); + write_cursor_ += len; + } + + // Write integers. + // + void write_uint8(uint64_t data) { write_uint_core(data); } + void write_uint16(uint64_t data) { write_uint_core(data); } + void write_uint32(uint64_t data) { write_uint_core(data); } + void write_uint64(uint64_t data) { write_uint_core(data); } + void write_int8(int64_t data) { write_int_core(data); } + void write_int16(int64_t data) { write_int_core(data); } + void write_int32(int64_t data) { write_int_core(data); } + void write_int64(int64_t data) { write_int_core(data); } + + // Write other primitive types. + // + void write_bool(bool b) { write_uint8(b ? 1:0); } + void write_char(char c) { write_value_core(c); } + void write_float(float arg) { write_value_core(arg); } + void write_double(double arg) { write_value_core(arg); } + + // Write lengths. + // + // Lengths are usually short, so we have a special way of storing + // lengths that minimizes the number of bytes when the length is short. + // + void write_length(size_t len) { + if (len >= 255) { + write_uint8(0xFF); + write_uint64(len); + } else { + write_uint8(len); + } + } + + // Write a string. + // + void write_string(std::string_view s) { + write_length(s.size()); + write_bytes(s); + } + + // Write a SimpleDynamicTag. + // + void write_simple_dynamic_tag(SimpleDynamicTag tag) { + write_uint8(uint8_t(tag)); + } + + // Write a SimpleDynamic value. + // + // This works regardless of what kind of string is present in the + // SimpleDynamic. + // + template + void write_simple_dynamic(const SimpleDynamic &sd) { + write_simple_dynamic_tag(sd.type); + switch(sd.type) { + case SimpleDynamicTag::NUMBER: write_double(sd.x); break; + case SimpleDynamicTag::BOOLEAN: write_bool(sd.x == 1.0); break; + case SimpleDynamicTag::VECTOR: write_double(sd.x); write_double(sd.y); write_double(sd.z); break; + case SimpleDynamicTag::STRING: write_string(sd.s); break; + default: assert(false); + } + } + + // Read a block of bytes from the buffer. + // + // Caution: the pointer returned is a pointer to the stream's buffer. + // It is only valid until you mutate the buffer. If the bytes aren't + // there, calls 'raise_eof_on_read', and then returns nullptr. + // + const char *read_bytes(int64_t bytes) { + int64_t avail = write_cursor_ - read_cursor_; + if (avail < bytes) { + CoreHandler::raise_eof_on_read(); + return nullptr; + } + char *data = read_cursor_; + read_cursor_ += bytes; + return data; + } + + // Read integers. + // + uint8_t read_uint8() { return read_value_core(); } + uint16_t read_uint16() { return read_value_core(); } + uint32_t read_uint32() { return read_value_core(); } + uint64_t read_uint64() { return read_value_core(); } + int8_t read_int8() { return read_value_core(); } + int16_t read_int16() { return read_value_core(); } + int32_t read_int32() { return read_value_core(); } + int64_t read_int64() { return read_value_core(); } + + // Read other primitive types. + // + bool read_bool() { return (bool)read_uint8(); } + char read_char() { return read_value_core(); } + float read_float() { return read_value_core(); } + double read_double() { return read_value_core(); } + + // Read a length. + // + size_t read_length() { + uint64_t len = read_uint8(); + if (len == 255) { + len = read_uint64(); + } + return len; + } + + // Read a string as a string_view. + // + // If the string in the buffer is longer than the limit, + // calls 'raise_string_too_long' and returns an empty string. + // + // If the buffer doesn't contain a complete string, calls + // 'raise_eof_on_read' and returns an empty string. + // + std::string_view read_string_view_limit(uint64_t limit) { + size_t length = read_length(); + if (length > limit) { + CoreHandler::raise_string_too_long(); + return std::string_view(); + } + int64_t avail = write_cursor_ - read_cursor_; + if (avail < int64_t(length)) { + CoreHandler::raise_eof_on_read(); + return std::string_view(); + } + std::string_view result(read_cursor_, length); + read_cursor_ += length; + return result; + } + + // Read a string as a string_view. + // + std::string_view read_string_view() { + return read_string_view_limit(0x1000000); + } + + // Read a string. + // + string_type read_string_limit(uint64_t limit) { + size_t len = read_length(); + if (len > limit) { + CoreHandler::raise_string_too_long(); + return string_type(); + } + int64_t avail = write_cursor_ - read_cursor_; + if (avail < int64_t(len)) { + CoreHandler::raise_eof_on_read(); + return string_type(); + } + string_type result(len, ' '); + memcpy(&result[0], read_cursor_, len); + read_cursor_ += len; + return result; + } + + // Read a string. + // + string_type read_string() { + return read_string_limit(0x1000000); + } + + // Read a SimpleDynamicTag + // + SimpleDynamicTag read_simple_dynamic_tag() { + return SimpleDynamicTag(read_uint8()); + } + + // Read a SimpleDynamic + // + template + void read_simple_dynamic(SimpleDynamic *result) { + SimpleDynamicTag type = read_simple_dynamic_tag(); + switch (type) { + case SimpleDynamicTag::NUMBER: result->set_number(read_double()); break; + case SimpleDynamicTag::BOOLEAN: result->set_boolean(read_bool()); break; + case SimpleDynamicTag::VECTOR: { + double x=read_double(); + double y=read_double(); + double z=read_double(); + result->set_vector(x,y,z); + break; + } + case SimpleDynamicTag::STRING: result->set_string(read_string()); break; + default: result->set_uninitialized(); break; + } + } + + // Attempt to do a "readline". If there is no newline in + // the buffer, returns empty string. If there is a newline, + // returns a block of text that ends in newline. + // + string_type readline() { + char *p = read_cursor_; + while ((p < write_cursor_) && (*p != '\n')) p++; + if (p == write_cursor_) { + return string_type(); + } else { + p++; + string_type result(read_cursor_, p - read_cursor_); + read_cursor_ = p; + return result; + } + } + + // Overwrite values previously written to the buffer. + // + // See the comment at the top of this file for an explanation. + // + void overwrite_int8(int64_t write_count_after, int64_t v) { overwrite_int_core(write_count_after, v); } + void overwrite_int16(int64_t write_count_after, int64_t v) { overwrite_int_core(write_count_after, v); } + void overwrite_int32(int64_t write_count_after, int64_t v) { overwrite_int_core(write_count_after, v); } + void overwrite_int64(int64_t write_count_after, int64_t v) { overwrite_int_core(write_count_after, v); } + void overwrite_uint8(int64_t write_count_after, uint64_t v) { overwrite_uint_core(write_count_after, v); } + void overwrite_uint16(int64_t write_count_after, uint64_t v) { overwrite_uint_core(write_count_after, v); } + void overwrite_uint32(int64_t write_count_after, uint64_t v) { overwrite_uint_core(write_count_after, v); } + void overwrite_uint64(int64_t write_count_after, uint64_t v) { overwrite_uint_core(write_count_after, v); } + + // This is for unit testing. + // + bool layout_is(int64_t a, int64_t b, int64_t c) { + if (read_cursor_ - buf_lo_ != a) return false; + if (write_cursor_ - read_cursor_ != b) return false; + if (buf_hi_ - write_cursor_ != c) return false; + return true; + } + +private: + void make_space_slow(int64_t bytes) { + assert(owned_ && "We don't own this buffer, can't grow it"); + + // Decide whether the current buffer is big enough. + int64_t data_size = (write_cursor_ - read_cursor_); + int64_t existing_size = (buf_hi_ - buf_lo_); + int64_t desired_size = 8192 + ((data_size + bytes) * 2); + + // Update some simple things. + pre_read_count_ += (read_cursor_ - buf_lo_); + + // Move the data to the beginning of the buffer, or to + // the beginning of a new buffer. + if (fixed_size_) { + assert((data_size + bytes <= existing_size) && "Not enough space in fixed-size buffer"); + if (data_size > 0) memcpy(buf_lo_, read_cursor_, data_size); + } else if (existing_size >= desired_size) { + if (data_size > 0) memcpy(buf_lo_, read_cursor_, data_size); + } else { + char *nbuf = (char *)CoreHandler::basebuffer_malloc(desired_size); + if (data_size > 0) memcpy(nbuf, read_cursor_, data_size); + if (buf_lo_ != nullptr) CoreHandler::basebuffer_free(buf_lo_); + buf_lo_ = nbuf; + buf_hi_ = nbuf + desired_size; + } + + // Update the pointers to the data region. + read_cursor_ = buf_lo_; + write_cursor_ = buf_lo_ + data_size; + } + + template + void write_value_core(T arg) { + make_space(sizeof(arg)); + memcpy(write_cursor_, &arg, sizeof(arg)); + write_cursor_ += sizeof(arg); + } + + template + void write_int_core(int64_t arg) { + T reduced = arg; + if (int64_t(reduced) != arg) CoreHandler::raise_integer_truncated(); + write_value_core(reduced); + } + + template + void write_uint_core(uint64_t arg) { + T reduced = arg; + if (uint64_t(reduced) != arg) CoreHandler::raise_integer_truncated(); + write_value_core(reduced); + } + + template + T read_value_core() { + T result; + int64_t avail = write_cursor_ - read_cursor_; + if (avail < int64_t(sizeof(result))) { + CoreHandler::raise_eof_on_read(); + return 0; + } + memcpy(&result, read_cursor_, sizeof(result)); + read_cursor_ += sizeof(result); + return result; + } + + template + void overwrite_int_core(int64_t write_count_after, int64_t vv) { + T v = vv; + assert(int64_t(v) == vv); + int64_t write_count_before = write_count_after - sizeof(v); + assert(write_count_before >= total_reads()); + assert(write_count_after <= total_writes()); + void *target = buf_lo_ + (write_count_before - pre_read_count_); + memcpy(target, &v, sizeof(v)); + } + + template + void overwrite_uint_core(int64_t write_count_after, uint64_t vv) { + T v = vv; + assert(uint64_t(v) == vv); + int64_t write_count_before = write_count_after - sizeof(v); + assert(write_count_before >= total_reads()); + assert(write_count_after <= total_writes()); + void *target = buf_lo_ + (write_count_before - pre_read_count_); + memcpy(target, &v, sizeof(v)); + } +}; \ No newline at end of file diff --git a/luprex/ext/cv2pdb.exe b/luprex/ext/cv2pdb.exe new file mode 100644 index 00000000..12594140 --- /dev/null +++ b/luprex/ext/cv2pdb.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b1f26a96ad8b6401469ef245eb4070485c4feafe875c77ce9bc505f57388653 +size 368128 diff --git a/luprex/ext/dlmalloc.c b/luprex/ext/dlmalloc.c new file mode 100644 index 00000000..4fc64718 --- /dev/null +++ b/luprex/ext/dlmalloc.c @@ -0,0 +1,6251 @@ +/* + This is a version (aka dlmalloc) of malloc/free/realloc written by + Doug Lea and released to the public domain, as explained at + http://creativecommons.org/publicdomain/zero/1.0/ Send questions, + comments, complaints, performance data, etc to dl@cs.oswego.edu + +* Version 2.8.5 Sun May 22 10:26:02 2011 Doug Lea (dl at gee) + + Note: There may be an updated version of this malloc obtainable at + ftp://gee.cs.oswego.edu/pub/misc/malloc.c + Check before installing! + +* Quickstart + + This library is all in one file to simplify the most common usage: + ftp it, compile it (-O3), and link it into another program. All of + the compile-time options default to reasonable values for use on + most platforms. You might later want to step through various + compile-time and dynamic tuning options. + + For convenience, an include file for code using this malloc is at: + ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.5.h + You don't really need this .h file unless you call functions not + defined in your system include files. The .h file contains only the + excerpts from this file needed for using this malloc on ANSI C/C++ + systems, so long as you haven't changed compile-time options about + naming and tuning parameters. If you do, then you can create your + own malloc.h that does include all settings by cutting at the point + indicated below. Note that you may already by default be using a C + library containing a malloc that is based on some version of this + malloc (for example in linux). You might still want to use the one + in this file to customize settings or to avoid overheads associated + with library versions. + +* Vital statistics: + + Supported pointer/size_t representation: 4 or 8 bytes + size_t MUST be an unsigned type of the same width as + pointers. (If you are using an ancient system that declares + size_t as a signed type, or need it to be a different width + than pointers, you can use a previous release of this malloc + (e.g. 2.7.2) supporting these.) + + Alignment: 8 bytes (default) + This suffices for nearly all current machines and C compilers. + However, you can define MALLOC_ALIGNMENT to be wider than this + if necessary (up to 128bytes), at the expense of using more space. + + Minimum overhead per allocated chunk: 4 or 8 bytes (if 4byte sizes) + 8 or 16 bytes (if 8byte sizes) + Each malloced chunk has a hidden word of overhead holding size + and status information, and additional cross-check word + if FOOTERS is defined. + + Minimum allocated size: 4-byte ptrs: 16 bytes (including overhead) + 8-byte ptrs: 32 bytes (including overhead) + + Even a request for zero bytes (i.e., malloc(0)) returns a + pointer to something of the minimum allocatable size. + The maximum overhead wastage (i.e., number of extra bytes + allocated than were requested in malloc) is less than or equal + to the minimum size, except for requests >= mmap_threshold that + are serviced via mmap(), where the worst case wastage is about + 32 bytes plus the remainder from a system page (the minimal + mmap unit); typically 4096 or 8192 bytes. + + Security: static-safe; optionally more or less + The "security" of malloc refers to the ability of malicious + code to accentuate the effects of errors (for example, freeing + space that is not currently malloc'ed or overwriting past the + ends of chunks) in code that calls malloc. This malloc + guarantees not to modify any memory locations below the base of + heap, i.e., static variables, even in the presence of usage + errors. The routines additionally detect most improper frees + and reallocs. All this holds as long as the static bookkeeping + for malloc itself is not corrupted by some other means. This + is only one aspect of security -- these checks do not, and + cannot, detect all possible programming errors. + + If FOOTERS is defined nonzero, then each allocated chunk + carries an additional check word to verify that it was malloced + from its space. These check words are the same within each + execution of a program using malloc, but differ across + executions, so externally crafted fake chunks cannot be + freed. This improves security by rejecting frees/reallocs that + could corrupt heap memory, in addition to the checks preventing + writes to statics that are always on. This may further improve + security at the expense of time and space overhead. (Note that + FOOTERS may also be worth using with MSPACES.) + + By default detected errors cause the program to abort (calling + "abort()"). You can override this to instead proceed past + errors by defining PROCEED_ON_ERROR. In this case, a bad free + has no effect, and a malloc that encounters a bad address + caused by user overwrites will ignore the bad address by + dropping pointers and indices to all known memory. This may + be appropriate for programs that should continue if at all + possible in the face of programming errors, although they may + run out of memory because dropped memory is never reclaimed. + + If you don't like either of these options, you can define + CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything + else. And if if you are sure that your program using malloc has + no errors or vulnerabilities, you can define INSECURE to 1, + which might (or might not) provide a small performance improvement. + + It is also possible to limit the maximum total allocatable + space, using malloc_set_footprint_limit. This is not + designed as a security feature in itself (calls to set limits + are not screened or privileged), but may be useful as one + aspect of a secure implementation. + + Thread-safety: NOT thread-safe unless USE_LOCKS defined non-zero + When USE_LOCKS is defined, each public call to malloc, free, + etc is surrounded with a lock. By default, this uses a plain + pthread mutex, win32 critical section, or a spin-lock if if + available for the platform and not disabled by setting + USE_SPIN_LOCKS=0. However, if USE_RECURSIVE_LOCKS is defined, + recursive versions are used instead (which are not required for + base functionality but may be needed in layered extensions). + Using a global lock is not especially fast, and can be a major + bottleneck. It is designed only to provide minimal protection + in concurrent environments, and to provide a basis for + extensions. If you are using malloc in a concurrent program, + consider instead using nedmalloc + (http://www.nedprod.com/programs/portable/nedmalloc/) or + ptmalloc (See http://www.malloc.de), which are derived from + versions of this malloc. + + System requirements: Any combination of MORECORE and/or MMAP/MUNMAP + This malloc can use unix sbrk or any emulation (invoked using + the CALL_MORECORE macro) and/or mmap/munmap or any emulation + (invoked using CALL_MMAP/CALL_MUNMAP) to get and release system + memory. On most unix systems, it tends to work best if both + MORECORE and MMAP are enabled. On Win32, it uses emulations + based on VirtualAlloc. It also uses common C library functions + like memset. + + Compliance: I believe it is compliant with the Single Unix Specification + (See http://www.unix.org). Also SVID/XPG, ANSI C, and probably + others as well. + +* Overview of algorithms + + This is not the fastest, most space-conserving, most portable, or + most tunable malloc ever written. However it is among the fastest + while also being among the most space-conserving, portable and + tunable. Consistent balance across these factors results in a good + general-purpose allocator for malloc-intensive programs. + + In most ways, this malloc is a best-fit allocator. Generally, it + chooses the best-fitting existing chunk for a request, with ties + broken in approximately least-recently-used order. (This strategy + normally maintains low fragmentation.) However, for requests less + than 256bytes, it deviates from best-fit when there is not an + exactly fitting available chunk by preferring to use space adjacent + to that used for the previous small request, as well as by breaking + ties in approximately most-recently-used order. (These enhance + locality of series of small allocations.) And for very large requests + (>= 256Kb by default), it relies on system memory mapping + facilities, if supported. (This helps avoid carrying around and + possibly fragmenting memory used only for large chunks.) + + All operations (except malloc_stats and mallinfo) have execution + times that are bounded by a constant factor of the number of bits in + a size_t, not counting any clearing in calloc or copying in realloc, + or actions surrounding MORECORE and MMAP that have times + proportional to the number of non-contiguous regions returned by + system allocation routines, which is often just 1. In real-time + applications, you can optionally suppress segment traversals using + NO_SEGMENT_TRAVERSAL, which assures bounded execution even when + system allocators return non-contiguous spaces, at the typical + expense of carrying around more memory and increased fragmentation. + + The implementation is not very modular and seriously overuses + macros. Perhaps someday all C compilers will do as good a job + inlining modular code as can now be done by brute-force expansion, + but now, enough of them seem not to. + + Some compilers issue a lot of warnings about code that is + dead/unreachable only on some platforms, and also about intentional + uses of negation on unsigned types. All known cases of each can be + ignored. + + For a longer but out of date high-level description, see + http://gee.cs.oswego.edu/dl/html/malloc.html + +* MSPACES + If MSPACES is defined, then in addition to malloc, free, etc., + this file also defines mspace_malloc, mspace_free, etc. These + are versions of malloc routines that take an "mspace" argument + obtained using create_mspace, to control all internal bookkeeping. + If ONLY_MSPACES is defined, only these versions are compiled. + So if you would like to use this allocator for only some allocations, + and your system malloc for others, you can compile with + ONLY_MSPACES and then do something like... + static mspace mymspace = create_mspace(0,0); // for example + #define mymalloc(bytes) mspace_malloc(mymspace, bytes) + + (Note: If you only need one instance of an mspace, you can instead + use "USE_DL_PREFIX" to relabel the global malloc.) + + You can similarly create thread-local allocators by storing + mspaces as thread-locals. For example: + static __thread mspace tlms = 0; + void* tlmalloc(size_t bytes) { + if (tlms == 0) tlms = create_mspace(0, 0); + return mspace_malloc(tlms, bytes); + } + void tlfree(void* mem) { mspace_free(tlms, mem); } + + Unless FOOTERS is defined, each mspace is completely independent. + You cannot allocate from one and free to another (although + conformance is only weakly checked, so usage errors are not always + caught). If FOOTERS is defined, then each chunk carries around a tag + indicating its originating mspace, and frees are directed to their + originating spaces. Normally, this requires use of locks. + + ------------------------- Compile-time options --------------------------- + +Be careful in setting #define values for numerical constants of type +size_t. On some systems, literal values are not automatically extended +to size_t precision unless they are explicitly casted. You can also +use the symbolic values MAX_SIZE_T, SIZE_T_ONE, etc below. + +WIN32 default: defined if _WIN32 defined + Defining WIN32 sets up defaults for MS environment and compilers. + Otherwise defaults are for unix. Beware that there seem to be some + cases where this malloc might not be a pure drop-in replacement for + Win32 malloc: Random-looking failures from Win32 GDI API's (eg; + SetDIBits()) may be due to bugs in some video driver implementations + when pixel buffers are malloc()ed, and the region spans more than + one VirtualAlloc()ed region. Because dlmalloc uses a small (64Kb) + default granularity, pixel buffers may straddle virtual allocation + regions more often than when using the Microsoft allocator. You can + avoid this by using VirtualAlloc() and VirtualFree() for all pixel + buffers rather than using malloc(). If this is not possible, + recompile this malloc with a larger DEFAULT_GRANULARITY. Note: + in cases where MSC and gcc (cygwin) are known to differ on WIN32, + conditions use _MSC_VER to distinguish them. + +DLMALLOC_EXPORT default: extern + Defines how public APIs are declared. If you want to export via a + Windows DLL, you might define this as + #define DLMALLOC_EXPORT extern __declspace(dllexport) + If you want a POSIX ELF shared object, you might use + #define DLMALLOC_EXPORT extern __attribute__((visibility("default"))) + +MALLOC_ALIGNMENT default: (size_t)8 + Controls the minimum alignment for malloc'ed chunks. It must be a + power of two and at least 8, even on machines for which smaller + alignments would suffice. It may be defined as larger than this + though. Note however that code and data structures are optimized for + the case of 8-byte alignment. + +MSPACES default: 0 (false) + If true, compile in support for independent allocation spaces. + This is only supported if HAVE_MMAP is true. + +ONLY_MSPACES default: 0 (false) + If true, only compile in mspace versions, not regular versions. + +USE_LOCKS default: 0 (false) + Causes each call to each public routine to be surrounded with + pthread or WIN32 mutex lock/unlock. (If set true, this can be + overridden on a per-mspace basis for mspace versions.) If set to a + non-zero value other than 1, locks are used, but their + implementation is left out, so lock functions must be supplied manually, + as described below. + +USE_SPIN_LOCKS default: 1 iff USE_LOCKS and spin locks available + If true, uses custom spin locks for locking. This is currently + supported only gcc >= 4.1, older gccs on x86 platforms, and recent + MS compilers. Otherwise, posix locks or win32 critical sections are + used. + +USE_RECURSIVE_LOCKS default: not defined + If defined nonzero, uses recursive (aka reentrant) locks, otherwise + uses plain mutexes. This is not required for malloc proper, but may + be needed for layered allocators such as nedmalloc. + +FOOTERS default: 0 + If true, provide extra checking and dispatching by placing + information in the footers of allocated chunks. This adds + space and time overhead. + +INSECURE default: 0 + If true, omit checks for usage errors and heap space overwrites. + +USE_DL_PREFIX default: NOT defined + Causes compiler to prefix all public routines with the string 'dl'. + This can be useful when you only want to use this malloc in one part + of a program, using your regular system malloc elsewhere. + +MALLOC_INSPECT_ALL default: NOT defined + If defined, compiles malloc_inspect_all and mspace_inspect_all, that + perform traversal of all heap space. Unless access to these + functions is otherwise restricted, you probably do not want to + include them in secure implementations. + +ABORT default: defined as abort() + Defines how to abort on failed checks. On most systems, a failed + check cannot die with an "assert" or even print an informative + message, because the underlying print routines in turn call malloc, + which will fail again. Generally, the best policy is to simply call + abort(). It's not very useful to do more than this because many + errors due to overwriting will show up as address faults (null, odd + addresses etc) rather than malloc-triggered checks, so will also + abort. Also, most compilers know that abort() does not return, so + can better optimize code conditionally calling it. + +PROCEED_ON_ERROR default: defined as 0 (false) + Controls whether detected bad addresses cause them to bypassed + rather than aborting. If set, detected bad arguments to free and + realloc are ignored. And all bookkeeping information is zeroed out + upon a detected overwrite of freed heap space, thus losing the + ability to ever return it from malloc again, but enabling the + application to proceed. If PROCEED_ON_ERROR is defined, the + static variable malloc_corruption_error_count is compiled in + and can be examined to see if errors have occurred. This option + generates slower code than the default abort policy. + +DEBUG default: NOT defined + The DEBUG setting is mainly intended for people trying to modify + this code or diagnose problems when porting to new platforms. + However, it may also be able to better isolate user errors than just + using runtime checks. The assertions in the check routines spell + out in more detail the assumptions and invariants underlying the + algorithms. The checking is fairly extensive, and will slow down + execution noticeably. Calling malloc_stats or mallinfo with DEBUG + set will attempt to check every non-mmapped allocated and free chunk + in the course of computing the summaries. + +ABORT_ON_ASSERT_FAILURE default: defined as 1 (true) + Debugging assertion failures can be nearly impossible if your + version of the assert macro causes malloc to be called, which will + lead to a cascade of further failures, blowing the runtime stack. + ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(), + which will usually make debugging easier. + +MALLOC_FAILURE_ACTION default: sets errno to ENOMEM, or no-op on win32 + The action to take before "return 0" when malloc fails to be able to + return memory because there is none available. + +HAVE_MORECORE default: 1 (true) unless win32 or ONLY_MSPACES + True if this system supports sbrk or an emulation of it. + +MORECORE default: sbrk + The name of the sbrk-style system routine to call to obtain more + memory. See below for guidance on writing custom MORECORE + functions. The type of the argument to sbrk/MORECORE varies across + systems. It cannot be size_t, because it supports negative + arguments, so it is normally the signed type of the same width as + size_t (sometimes declared as "intptr_t"). It doesn't much matter + though. Internally, we only call it with arguments less than half + the max value of a size_t, which should work across all reasonable + possibilities, although sometimes generating compiler warnings. + +MORECORE_CONTIGUOUS default: 1 (true) if HAVE_MORECORE + If true, take advantage of fact that consecutive calls to MORECORE + with positive arguments always return contiguous increasing + addresses. This is true of unix sbrk. It does not hurt too much to + set it true anyway, since malloc copes with non-contiguities. + Setting it false when definitely non-contiguous saves time + and possibly wasted space it would take to discover this though. + +MORECORE_CANNOT_TRIM default: NOT defined + True if MORECORE cannot release space back to the system when given + negative arguments. This is generally necessary only if you are + using a hand-crafted MORECORE function that cannot handle negative + arguments. + +NO_SEGMENT_TRAVERSAL default: 0 + If non-zero, suppresses traversals of memory segments + returned by either MORECORE or CALL_MMAP. This disables + merging of segments that are contiguous, and selectively + releasing them to the OS if unused, but bounds execution times. + +HAVE_MMAP default: 1 (true) + True if this system supports mmap or an emulation of it. If so, and + HAVE_MORECORE is not true, MMAP is used for all system + allocation. If set and HAVE_MORECORE is true as well, MMAP is + primarily used to directly allocate very large blocks. It is also + used as a backup strategy in cases where MORECORE fails to provide + space from system. Note: A single call to MUNMAP is assumed to be + able to unmap memory that may have be allocated using multiple calls + to MMAP, so long as they are adjacent. + +HAVE_MREMAP default: 1 on linux, else 0 + If true realloc() uses mremap() to re-allocate large blocks and + extend or shrink allocation spaces. + +MMAP_CLEARS default: 1 except on WINCE. + True if mmap clears memory so calloc doesn't need to. This is true + for standard unix mmap using /dev/zero and on WIN32 except for WINCE. + +USE_BUILTIN_FFS default: 0 (i.e., not used) + Causes malloc to use the builtin ffs() function to compute indices. + Some compilers may recognize and intrinsify ffs to be faster than the + supplied C version. Also, the case of x86 using gcc is special-cased + to an asm instruction, so is already as fast as it can be, and so + this setting has no effect. Similarly for Win32 under recent MS compilers. + (On most x86s, the asm version is only slightly faster than the C version.) + +malloc_getpagesize default: derive from system includes, or 4096. + The system page size. To the extent possible, this malloc manages + memory from the system in page-size units. This may be (and + usually is) a function rather than a constant. This is ignored + if WIN32, where page size is determined using getSystemInfo during + initialization. + +USE_DEV_RANDOM default: 0 (i.e., not used) + Causes malloc to use /dev/random to initialize secure magic seed for + stamping footers. Otherwise, the current time is used. + +NO_MALLINFO default: 0 + If defined, don't compile "mallinfo". This can be a simple way + of dealing with mismatches between system declarations and + those in this file. + +MALLINFO_FIELD_TYPE default: size_t + The type of the fields in the mallinfo struct. This was originally + defined as "int" in SVID etc, but is more usefully defined as + size_t. The value is used only if HAVE_USR_INCLUDE_MALLOC_H is not set + +NO_MALLOC_STATS default: 0 + If defined, don't compile "malloc_stats". This avoids calls to + fprintf and bringing in stdio dependencies you might not want. + +REALLOC_ZERO_BYTES_FREES default: not defined + This should be set if a call to realloc with zero bytes should + be the same as a call to free. Some people think it should. Otherwise, + since this malloc returns a unique pointer for malloc(0), so does + realloc(p, 0). + +LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H +LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H, LACKS_ERRNO_H +LACKS_STDLIB_H LACKS_SCHED_H LACKS_TIME_H default: NOT defined unless on WIN32 + Define these if your system does not have these header files. + You might need to manually insert some of the declarations they provide. + +DEFAULT_GRANULARITY default: page size if MORECORE_CONTIGUOUS, + system_info.dwAllocationGranularity in WIN32, + otherwise 64K. + Also settable using mallopt(M_GRANULARITY, x) + The unit for allocating and deallocating memory from the system. On + most systems with contiguous MORECORE, there is no reason to + make this more than a page. However, systems with MMAP tend to + either require or encourage larger granularities. You can increase + this value to prevent system allocation functions to be called so + often, especially if they are slow. The value must be at least one + page and must be a power of two. Setting to 0 causes initialization + to either page size or win32 region size. (Note: In previous + versions of malloc, the equivalent of this option was called + "TOP_PAD") + +DEFAULT_TRIM_THRESHOLD default: 2MB + Also settable using mallopt(M_TRIM_THRESHOLD, x) + The maximum amount of unused top-most memory to keep before + releasing via malloc_trim in free(). Automatic trimming is mainly + useful in long-lived programs using contiguous MORECORE. Because + trimming via sbrk can be slow on some systems, and can sometimes be + wasteful (in cases where programs immediately afterward allocate + more large chunks) the value should be high enough so that your + overall system performance would improve by releasing this much + memory. As a rough guide, you might set to a value close to the + average size of a process (program) running on your system. + Releasing this much memory would allow such a process to run in + memory. Generally, it is worth tuning trim thresholds when a + program undergoes phases where several large chunks are allocated + and released in ways that can reuse each other's storage, perhaps + mixed with phases where there are no such chunks at all. The trim + value must be greater than page size to have any useful effect. To + disable trimming completely, you can set to MAX_SIZE_T. Note that the trick + some people use of mallocing a huge space and then freeing it at + program startup, in an attempt to reserve system memory, doesn't + have the intended effect under automatic trimming, since that memory + will immediately be returned to the system. + +DEFAULT_MMAP_THRESHOLD default: 256K + Also settable using mallopt(M_MMAP_THRESHOLD, x) + The request size threshold for using MMAP to directly service a + request. Requests of at least this size that cannot be allocated + using already-existing space will be serviced via mmap. (If enough + normal freed space already exists it is used instead.) Using mmap + segregates relatively large chunks of memory so that they can be + individually obtained and released from the host system. A request + serviced through mmap is never reused by any other request (at least + not directly; the system may just so happen to remap successive + requests to the same locations). Segregating space in this way has + the benefits that: Mmapped space can always be individually released + back to the system, which helps keep the system level memory demands + of a long-lived program low. Also, mapped memory doesn't become + `locked' between other chunks, as can happen with normally allocated + chunks, which means that even trimming via malloc_trim would not + release them. However, it has the disadvantage that the space + cannot be reclaimed, consolidated, and then used to service later + requests, as happens with normal chunks. The advantages of mmap + nearly always outweigh disadvantages for "large" chunks, but the + value of "large" may vary across systems. The default is an + empirically derived value that works well in most systems. You can + disable mmap by setting to MAX_SIZE_T. + +MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP + The number of consolidated frees between checks to release + unused segments when freeing. When using non-contiguous segments, + especially with multiple mspaces, checking only for topmost space + doesn't always suffice to trigger trimming. To compensate for this, + free() will, with a period of MAX_RELEASE_CHECK_RATE (or the + current number of segments, if greater) try to release unused + segments to the OS when freeing chunks that result in + consolidation. The best value for this parameter is a compromise + between slowing down frees with relatively costly checks that + rarely trigger versus holding on to unused memory. To effectively + disable, set to MAX_SIZE_T. This may lead to a very slight speed + improvement at the expense of carrying around more memory. +*/ + +/* Version identifier to allow people to support multiple versions */ +#ifndef DLMALLOC_VERSION +#define DLMALLOC_VERSION 20805 +#endif /* DLMALLOC_VERSION */ + +#ifndef DLMALLOC_EXPORT +#define DLMALLOC_EXPORT extern +#endif + +#ifndef WIN32 +#ifdef _WIN32 +#define WIN32 1 +#endif /* _WIN32 */ +#ifdef _WIN32_WCE +#define LACKS_FCNTL_H +#define WIN32 1 +#endif /* _WIN32_WCE */ +#endif /* WIN32 */ +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#define HAVE_MMAP 1 +#define HAVE_MORECORE 0 +#define LACKS_UNISTD_H +#define LACKS_SYS_PARAM_H +#define LACKS_SYS_MMAN_H +#define LACKS_STRING_H +#define LACKS_STRINGS_H +#define LACKS_SYS_TYPES_H +#define LACKS_ERRNO_H +#define LACKS_SCHED_H +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION +#endif /* MALLOC_FAILURE_ACTION */ +#ifndef MMAP_CLEARS +#ifdef _WIN32_WCE /* WINCE reportedly does not clear */ +#define MMAP_CLEARS 0 +#else +#define MMAP_CLEARS 1 +#endif /* _WIN32_WCE */ +#endif /*MMAP_CLEARS */ +#endif /* WIN32 */ + +#if defined(DARWIN) || defined(_DARWIN) +/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */ +#ifndef HAVE_MORECORE +#define HAVE_MORECORE 0 +#define HAVE_MMAP 1 +/* OSX allocators provide 16 byte alignment */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)16U) +#endif +#endif /* HAVE_MORECORE */ +#endif /* DARWIN */ + +#ifndef LACKS_SYS_TYPES_H +#include /* For size_t */ +#endif /* LACKS_SYS_TYPES_H */ + +/* The maximum possible size_t value has all bits set */ +#define MAX_SIZE_T (~(size_t)0) + +#ifndef USE_LOCKS /* ensure true if spin or recursive locks set */ +#define USE_LOCKS ((defined(USE_SPIN_LOCKS) && USE_SPIN_LOCKS != 0) || \ + (defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0)) +#endif /* USE_LOCKS */ + +#if USE_LOCKS /* Spin locks for gcc >= 4.1, older gcc on x86, MSC >= 1310 */ +#if ((defined(__GNUC__) && \ + ((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) || \ + defined(__i386__) || defined(__x86_64__))) || \ + (defined(_MSC_VER) && _MSC_VER>=1310)) +#ifndef USE_SPIN_LOCKS +#define USE_SPIN_LOCKS 1 +#endif /* USE_SPIN_LOCKS */ +#elif USE_SPIN_LOCKS +#error "USE_SPIN_LOCKS defined without implementation" +#endif /* ... locks available... */ +#elif !defined(USE_SPIN_LOCKS) +#define USE_SPIN_LOCKS 0 +#endif /* USE_LOCKS */ + +#ifndef ONLY_MSPACES +#define ONLY_MSPACES 0 +#endif /* ONLY_MSPACES */ +#ifndef MSPACES +#if ONLY_MSPACES +#define MSPACES 1 +#else /* ONLY_MSPACES */ +#define MSPACES 0 +#endif /* ONLY_MSPACES */ +#endif /* MSPACES */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)8U) +#endif /* MALLOC_ALIGNMENT */ +#ifndef FOOTERS +#define FOOTERS 0 +#endif /* FOOTERS */ +#ifndef ABORT +#define ABORT abort() +#endif /* ABORT */ +#ifndef ABORT_ON_ASSERT_FAILURE +#define ABORT_ON_ASSERT_FAILURE 1 +#endif /* ABORT_ON_ASSERT_FAILURE */ +#ifndef PROCEED_ON_ERROR +#define PROCEED_ON_ERROR 0 +#endif /* PROCEED_ON_ERROR */ + +#ifndef INSECURE +#define INSECURE 0 +#endif /* INSECURE */ +#ifndef MALLOC_INSPECT_ALL +#define MALLOC_INSPECT_ALL 0 +#endif /* MALLOC_INSPECT_ALL */ +#ifndef HAVE_MMAP +#define HAVE_MMAP 1 +#endif /* HAVE_MMAP */ +#ifndef MMAP_CLEARS +#define MMAP_CLEARS 1 +#endif /* MMAP_CLEARS */ +#ifndef HAVE_MREMAP +#ifdef linux +#define HAVE_MREMAP 1 +#define _GNU_SOURCE /* Turns on mremap() definition */ +#else /* linux */ +#define HAVE_MREMAP 0 +#endif /* linux */ +#endif /* HAVE_MREMAP */ +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION errno = ENOMEM; +#endif /* MALLOC_FAILURE_ACTION */ +#ifndef HAVE_MORECORE +#if ONLY_MSPACES +#define HAVE_MORECORE 0 +#else /* ONLY_MSPACES */ +#define HAVE_MORECORE 1 +#endif /* ONLY_MSPACES */ +#endif /* HAVE_MORECORE */ +#if !HAVE_MORECORE +#define MORECORE_CONTIGUOUS 0 +#else /* !HAVE_MORECORE */ +#define MORECORE_DEFAULT sbrk +#ifndef MORECORE_CONTIGUOUS +#define MORECORE_CONTIGUOUS 1 +#endif /* MORECORE_CONTIGUOUS */ +#endif /* HAVE_MORECORE */ +#ifndef DEFAULT_GRANULARITY +#if (MORECORE_CONTIGUOUS || defined(WIN32)) +#define DEFAULT_GRANULARITY (0) /* 0 means to compute in init_mparams */ +#else /* MORECORE_CONTIGUOUS */ +#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U) +#endif /* MORECORE_CONTIGUOUS */ +#endif /* DEFAULT_GRANULARITY */ +#ifndef DEFAULT_TRIM_THRESHOLD +#ifndef MORECORE_CANNOT_TRIM +#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U) +#else /* MORECORE_CANNOT_TRIM */ +#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T +#endif /* MORECORE_CANNOT_TRIM */ +#endif /* DEFAULT_TRIM_THRESHOLD */ +#ifndef DEFAULT_MMAP_THRESHOLD +#if HAVE_MMAP +#define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U) +#else /* HAVE_MMAP */ +#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* DEFAULT_MMAP_THRESHOLD */ +#ifndef MAX_RELEASE_CHECK_RATE +#if HAVE_MMAP +#define MAX_RELEASE_CHECK_RATE 4095 +#else +#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* MAX_RELEASE_CHECK_RATE */ +#ifndef USE_BUILTIN_FFS +#define USE_BUILTIN_FFS 0 +#endif /* USE_BUILTIN_FFS */ +#ifndef USE_DEV_RANDOM +#define USE_DEV_RANDOM 0 +#endif /* USE_DEV_RANDOM */ +#ifndef NO_MALLINFO +#define NO_MALLINFO 0 +#endif /* NO_MALLINFO */ +#ifndef MALLINFO_FIELD_TYPE +#define MALLINFO_FIELD_TYPE size_t +#endif /* MALLINFO_FIELD_TYPE */ +#ifndef NO_MALLOC_STATS +#define NO_MALLOC_STATS 0 +#endif /* NO_MALLOC_STATS */ +#ifndef NO_SEGMENT_TRAVERSAL +#define NO_SEGMENT_TRAVERSAL 0 +#endif /* NO_SEGMENT_TRAVERSAL */ + +/* + mallopt tuning options. SVID/XPG defines four standard parameter + numbers for mallopt, normally defined in malloc.h. None of these + are used in this malloc, so setting them has no effect. But this + malloc does support the following options. +*/ + +#define M_TRIM_THRESHOLD (-1) +#define M_GRANULARITY (-2) +#define M_MMAP_THRESHOLD (-3) + +/* ------------------------ Mallinfo declarations ------------------------ */ + +#if !NO_MALLINFO +/* + This version of malloc supports the standard SVID/XPG mallinfo + routine that returns a struct containing usage properties and + statistics. It should work on any system that has a + /usr/include/malloc.h defining struct mallinfo. The main + declaration needed is the mallinfo struct that is returned (by-copy) + by mallinfo(). The malloinfo struct contains a bunch of fields that + are not even meaningful in this version of malloc. These fields are + are instead filled by mallinfo() with other numbers that might be of + interest. + + HAVE_USR_INCLUDE_MALLOC_H should be set if you have a + /usr/include/malloc.h file that includes a declaration of struct + mallinfo. If so, it is included; else a compliant version is + declared below. These must be precisely the same for mallinfo() to + work. The original SVID version of this struct, defined on most + systems with mallinfo, declares all fields as ints. But some others + define as unsigned long. If your system defines the fields using a + type of different width than listed here, you MUST #include your + system version and #define HAVE_USR_INCLUDE_MALLOC_H. +*/ + +/* #define HAVE_USR_INCLUDE_MALLOC_H */ + +#ifdef HAVE_USR_INCLUDE_MALLOC_H +#include "/usr/include/malloc.h" +#else /* HAVE_USR_INCLUDE_MALLOC_H */ +#ifndef STRUCT_MALLINFO_DECLARED +/* HP-UX (and others?) redefines mallinfo unless _STRUCT_MALLINFO is defined */ +#define _STRUCT_MALLINFO +#define STRUCT_MALLINFO_DECLARED 1 +struct mallinfo { + MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ + MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ + MALLINFO_FIELD_TYPE smblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ + MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ + MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ + MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ + MALLINFO_FIELD_TYPE fordblks; /* total free space */ + MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ +}; +#endif /* STRUCT_MALLINFO_DECLARED */ +#endif /* HAVE_USR_INCLUDE_MALLOC_H */ +#endif /* NO_MALLINFO */ + +/* + Try to persuade compilers to inline. The most critical functions for + inlining are defined as macros, so these aren't used for them. +*/ + +#ifndef FORCEINLINE + #if defined(__GNUC__) +#define FORCEINLINE __inline __attribute__ ((always_inline)) + #elif defined(_MSC_VER) + #define FORCEINLINE __forceinline + #endif +#endif +#ifndef NOINLINE + #if defined(__GNUC__) + #define NOINLINE __attribute__ ((noinline)) + #elif defined(_MSC_VER) + #define NOINLINE __declspec(noinline) + #else + #define NOINLINE + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#ifndef FORCEINLINE + #define FORCEINLINE inline +#endif +#endif /* __cplusplus */ +#ifndef FORCEINLINE + #define FORCEINLINE +#endif + +#if !ONLY_MSPACES + +/* ------------------- Declarations of public routines ------------------- */ + +#ifndef USE_DL_PREFIX +#define dlcalloc calloc +#define dlfree free +#define dlmalloc malloc +#define dlmemalign memalign +#define dlposix_memalign posix_memalign +#define dlrealloc realloc +#define dlrealloc_in_place realloc_in_place +#define dlvalloc valloc +#define dlpvalloc pvalloc +#define dlmallinfo mallinfo +#define dlmallopt mallopt +#define dlmalloc_trim malloc_trim +#define dlmalloc_stats malloc_stats +#define dlmalloc_usable_size malloc_usable_size +#define dlmalloc_footprint malloc_footprint +#define dlmalloc_max_footprint malloc_max_footprint +#define dlmalloc_footprint_limit malloc_footprint_limit +#define dlmalloc_set_footprint_limit malloc_set_footprint_limit +#define dlmalloc_inspect_all malloc_inspect_all +#define dlindependent_calloc independent_calloc +#define dlindependent_comalloc independent_comalloc +#define dlbulk_free bulk_free +#endif /* USE_DL_PREFIX */ + +/* + malloc(size_t n) + Returns a pointer to a newly allocated chunk of at least n bytes, or + null if no space is available, in which case errno is set to ENOMEM + on ANSI C systems. + + If n is zero, malloc returns a minimum-sized chunk. (The minimum + size is 16 bytes on most 32bit systems, and 32 bytes on 64bit + systems.) Note that size_t is an unsigned type, so calls with + arguments that would be negative if signed are interpreted as + requests for huge amounts of space, which will often fail. The + maximum supported value of n differs across systems, but is in all + cases less than the maximum representable value of a size_t. +*/ +DLMALLOC_EXPORT void* dlmalloc(size_t); + +/* + free(void* p) + Releases the chunk of memory pointed to by p, that had been previously + allocated using malloc or a related routine such as realloc. + It has no effect if p is null. If p was not malloced or already + freed, free(p) will by default cause the current program to abort. +*/ +DLMALLOC_EXPORT void dlfree(void*); + +/* + calloc(size_t n_elements, size_t element_size); + Returns a pointer to n_elements * element_size bytes, with all locations + set to zero. +*/ +DLMALLOC_EXPORT void* dlcalloc(size_t, size_t); + +/* + realloc(void* p, size_t n) + Returns a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. + + The returned pointer may or may not be the same as p. The algorithm + prefers extending p in most cases when possible, otherwise it + employs the equivalent of a malloc-copy-free sequence. + + If p is null, realloc is equivalent to malloc. + + If space is not available, realloc returns null, errno is set (if on + ANSI) and p is NOT freed. + + if n is for fewer bytes than already held by p, the newly unused + space is lopped off and freed if possible. realloc with a size + argument of zero (re)allocates a minimum-sized chunk. + + The old unix realloc convention of allowing the last-free'd chunk + to be used as an argument to realloc is not supported. +*/ +DLMALLOC_EXPORT void* dlrealloc(void*, size_t); + +/* + realloc_in_place(void* p, size_t n) + Resizes the space allocated for p to size n, only if this can be + done without moving p (i.e., only if there is adjacent space + available if n is greater than p's current allocated size, or n is + less than or equal to p's size). This may be used instead of plain + realloc if an alternative allocation strategy is needed upon failure + to expand space; for example, reallocation of a buffer that must be + memory-aligned or cleared. You can use realloc_in_place to trigger + these alternatives only when needed. + + Returns p if successful; otherwise null. +*/ +DLMALLOC_EXPORT void* dlrealloc_in_place(void*, size_t); + +/* + memalign(size_t alignment, size_t n); + Returns a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument. + + The alignment argument should be a power of two. If the argument is + not a power of two, the nearest greater power is used. + 8-byte alignment is guaranteed by normal malloc calls, so don't + bother calling memalign with an argument of 8 or less. + + Overreliance on memalign is a sure way to fragment space. +*/ +DLMALLOC_EXPORT void* dlmemalign(size_t, size_t); + +/* + int posix_memalign(void** pp, size_t alignment, size_t n); + Allocates a chunk of n bytes, aligned in accord with the alignment + argument. Differs from memalign only in that it (1) assigns the + allocated memory to *pp rather than returning it, (2) fails and + returns EINVAL if the alignment is not a power of two (3) fails and + returns ENOMEM if memory cannot be allocated. +*/ +DLMALLOC_EXPORT int dlposix_memalign(void**, size_t, size_t); + +/* + valloc(size_t n); + Equivalent to memalign(pagesize, n), where pagesize is the page + size of the system. If the pagesize is unknown, 4096 is used. +*/ +DLMALLOC_EXPORT void* dlvalloc(size_t); + +/* + mallopt(int parameter_number, int parameter_value) + Sets tunable parameters The format is to provide a + (parameter-number, parameter-value) pair. mallopt then sets the + corresponding parameter to the argument value if it can (i.e., so + long as the value is meaningful), and returns 1 if successful else + 0. To workaround the fact that mallopt is specified to use int, + not size_t parameters, the value -1 is specially treated as the + maximum unsigned size_t value. + + SVID/XPG/ANSI defines four standard param numbers for mallopt, + normally defined in malloc.h. None of these are use in this malloc, + so setting them has no effect. But this malloc also supports other + options in mallopt. See below for details. Briefly, supported + parameters are as follows (listed defaults are for "typical" + configurations). + + Symbol param # default allowed param values + M_TRIM_THRESHOLD -1 2*1024*1024 any (-1 disables) + M_GRANULARITY -2 page size any power of 2 >= page size + M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) +*/ +DLMALLOC_EXPORT int dlmallopt(int, int); + +/* + malloc_footprint(); + Returns the number of bytes obtained from the system. The total + number of bytes allocated by malloc, realloc etc., is less than this + value. Unlike mallinfo, this function returns only a precomputed + result, so can be called frequently to monitor memory consumption. + Even if locks are otherwise defined, this function does not use them, + so results might not be up to date. +*/ +DLMALLOC_EXPORT size_t dlmalloc_footprint(void); + +/* + malloc_max_footprint(); + Returns the maximum number of bytes obtained from the system. This + value will be greater than current footprint if deallocated space + has been reclaimed by the system. The peak number of bytes allocated + by malloc, realloc etc., is less than this value. Unlike mallinfo, + this function returns only a precomputed result, so can be called + frequently to monitor memory consumption. Even if locks are + otherwise defined, this function does not use them, so results might + not be up to date. +*/ +DLMALLOC_EXPORT size_t dlmalloc_max_footprint(void); + +/* + malloc_footprint_limit(); + Returns the number of bytes that the heap is allowed to obtain from + the system, returning the last value returned by + malloc_set_footprint_limit, or the maximum size_t value if + never set. The returned value reflects a permission. There is no + guarantee that this number of bytes can actually be obtained from + the system. +*/ +DLMALLOC_EXPORT size_t dlmalloc_footprint_limit(); + +/* + malloc_set_footprint_limit(); + Sets the maximum number of bytes to obtain from the system, causing + failure returns from malloc and related functions upon attempts to + exceed this value. The argument value may be subject to page + rounding to an enforceable limit; this actual value is returned. + Using an argument of the maximum possible size_t effectively + disables checks. If the argument is less than or equal to the + current malloc_footprint, then all future allocations that require + additional system memory will fail. However, invocation cannot + retroactively deallocate existing used memory. +*/ +DLMALLOC_EXPORT size_t dlmalloc_set_footprint_limit(size_t bytes); + +#if MALLOC_INSPECT_ALL +/* + malloc_inspect_all(void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg); + Traverses the heap and calls the given handler for each managed + region, skipping all bytes that are (or may be) used for bookkeeping + purposes. Traversal does not include include chunks that have been + directly memory mapped. Each reported region begins at the start + address, and continues up to but not including the end address. The + first used_bytes of the region contain allocated data. If + used_bytes is zero, the region is unallocated. The handler is + invoked with the given callback argument. If locks are defined, they + are held during the entire traversal. It is a bad idea to invoke + other malloc functions from within the handler. + + For example, to count the number of in-use chunks with size greater + than 1000, you could write: + static int count = 0; + void count_chunks(void* start, void* end, size_t used, void* arg) { + if (used >= 1000) ++count; + } + then: + malloc_inspect_all(count_chunks, NULL); + + malloc_inspect_all is compiled only if MALLOC_INSPECT_ALL is defined. +*/ +DLMALLOC_EXPORT void dlmalloc_inspect_all(void(*handler)(void*, void *, size_t, void*), + void* arg); + +#endif /* MALLOC_INSPECT_ALL */ + +#if !NO_MALLINFO +/* + mallinfo() + Returns (by copy) a struct containing various summary statistics: + + arena: current total non-mmapped bytes allocated from system + ordblks: the number of free chunks + smblks: always zero. + hblks: current number of mmapped regions + hblkhd: total bytes held in mmapped regions + usmblks: the maximum total allocated space. This will be greater + than current total if trimming has occurred. + fsmblks: always zero + uordblks: current total allocated space (normal or mmapped) + fordblks: total free space + keepcost: the maximum number of bytes that could ideally be released + back to system via malloc_trim. ("ideally" means that + it ignores page restrictions etc.) + + Because these fields are ints, but internal bookkeeping may + be kept as longs, the reported values may wrap around zero and + thus be inaccurate. +*/ +DLMALLOC_EXPORT struct mallinfo dlmallinfo(void); +#endif /* NO_MALLINFO */ + +/* + independent_calloc(size_t n_elements, size_t element_size, void* chunks[]); + + independent_calloc is similar to calloc, but instead of returning a + single cleared space, it returns an array of pointers to n_elements + independent elements that can hold contents of size elem_size, each + of which starts out cleared, and can be independently freed, + realloc'ed etc. The elements are guaranteed to be adjacently + allocated (this is not guaranteed to occur with multiple callocs or + mallocs), which may also improve cache locality in some + applications. + + The "chunks" argument is optional (i.e., may be null, which is + probably the most typical usage). If it is null, the returned array + is itself dynamically allocated and should also be freed when it is + no longer needed. Otherwise, the chunks array must be of at least + n_elements in length. It is filled in with the pointers to the + chunks. + + In either case, independent_calloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and "chunks" + is null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be freed when it is no longer needed. This can be + done all at once using bulk_free. + + independent_calloc simplifies and speeds up implementations of many + kinds of pools. It may also be useful when constructing large data + structures that initially have a fixed number of fixed-sized nodes, + but the number is not known at compile time, and some of the nodes + may later need to be freed. For example: + + struct Node { int item; struct Node* next; }; + + struct Node* build_list() { + struct Node** pool; + int n = read_number_of_nodes_needed(); + if (n <= 0) return 0; + pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); + if (pool == 0) die(); + // organize into a linked list... + struct Node* first = pool[0]; + for (i = 0; i < n-1; ++i) + pool[i]->next = pool[i+1]; + free(pool); // Can now free the array (or not, if it is needed later) + return first; + } +*/ +DLMALLOC_EXPORT void** dlindependent_calloc(size_t, size_t, void**); + +/* + independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]); + + independent_comalloc allocates, all at once, a set of n_elements + chunks with sizes indicated in the "sizes" array. It returns + an array of pointers to these elements, each of which can be + independently freed, realloc'ed etc. The elements are guaranteed to + be adjacently allocated (this is not guaranteed to occur with + multiple callocs or mallocs), which may also improve cache locality + in some applications. + + The "chunks" argument is optional (i.e., may be null). If it is null + the returned array is itself dynamically allocated and should also + be freed when it is no longer needed. Otherwise, the chunks array + must be of at least n_elements in length. It is filled in with the + pointers to the chunks. + + In either case, independent_comalloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and chunks is + null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be freed when it is no longer needed. This can be + done all at once using bulk_free. + + independent_comallac differs from independent_calloc in that each + element may have a different size, and also that it does not + automatically clear elements. + + independent_comalloc can be used to speed up allocation in cases + where several structs or objects must always be allocated at the + same time. For example: + + struct Head { ... } + struct Foot { ... } + + void send_message(char* msg) { + int msglen = strlen(msg); + size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; + void* chunks[3]; + if (independent_comalloc(3, sizes, chunks) == 0) + die(); + struct Head* head = (struct Head*)(chunks[0]); + char* body = (char*)(chunks[1]); + struct Foot* foot = (struct Foot*)(chunks[2]); + // ... + } + + In general though, independent_comalloc is worth using only for + larger values of n_elements. For small values, you probably won't + detect enough difference from series of malloc calls to bother. + + Overuse of independent_comalloc can increase overall memory usage, + since it cannot reuse existing noncontiguous small chunks that + might be available for some of the elements. +*/ +DLMALLOC_EXPORT void** dlindependent_comalloc(size_t, size_t*, void**); + +/* + bulk_free(void* array[], size_t n_elements) + Frees and clears (sets to null) each non-null pointer in the given + array. This is likely to be faster than freeing them one-by-one. + If footers are used, pointers that have been allocated in different + mspaces are not freed or cleared, and the count of all such pointers + is returned. For large arrays of pointers with poor locality, it + may be worthwhile to sort this array before calling bulk_free. +*/ +DLMALLOC_EXPORT size_t dlbulk_free(void**, size_t n_elements); + +/* + pvalloc(size_t n); + Equivalent to valloc(minimum-page-that-holds(n)), that is, + round up n to nearest pagesize. + */ +DLMALLOC_EXPORT void* dlpvalloc(size_t); + +/* + malloc_trim(size_t pad); + + If possible, gives memory back to the system (via negative arguments + to sbrk) if there is unused memory at the `high' end of the malloc + pool or in unused MMAP segments. You can call this after freeing + large blocks of memory to potentially reduce the system-level memory + requirements of a program. However, it cannot guarantee to reduce + memory. Under some allocation patterns, some large free blocks of + memory will be locked between two used chunks, so they cannot be + given back to the system. + + The `pad' argument to malloc_trim represents the amount of free + trailing space to leave untrimmed. If this argument is zero, only + the minimum amount of memory to maintain internal data structures + will be left. Non-zero arguments can be supplied to maintain enough + trailing space to service future expected allocations without having + to re-obtain memory from the system. + + Malloc_trim returns 1 if it actually released any memory, else 0. +*/ +DLMALLOC_EXPORT int dlmalloc_trim(size_t); + +/* + malloc_stats(); + Prints on stderr the amount of space obtained from the system (both + via sbrk and mmap), the maximum amount (which may be more than + current if malloc_trim and/or munmap got called), and the current + number of bytes allocated via malloc (or realloc, etc) but not yet + freed. Note that this is the number of bytes allocated, not the + number requested. It will be larger than the number requested + because of alignment and bookkeeping overhead. Because it includes + alignment wastage as being in use, this figure may be greater than + zero even when no user-level chunks are allocated. + + The reported current and maximum system memory can be inaccurate if + a program makes other calls to system memory allocation functions + (normally sbrk) outside of malloc. + + malloc_stats prints only the most commonly interesting statistics. + More information can be obtained by calling mallinfo. +*/ +DLMALLOC_EXPORT void dlmalloc_stats(void); + +#endif /* ONLY_MSPACES */ + +/* + malloc_usable_size(void* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. malloc_usable_size can be more useful in + debugging and assertions, for example: + + p = malloc(n); + assert(malloc_usable_size(p) >= 256); +*/ +size_t dlmalloc_usable_size(void*); + +#if MSPACES + +/* + mspace is an opaque type representing an independent + region of space that supports mspace_malloc, etc. +*/ +typedef void* mspace; + +/* + create_mspace creates and returns a new independent space with the + given initial capacity, or, if 0, the default granularity size. It + returns null if there is no system memory available to create the + space. If argument locked is non-zero, the space uses a separate + lock to control access. The capacity of the space will grow + dynamically as needed to service mspace_malloc requests. You can + control the sizes of incremental increases of this space by + compiling with a different DEFAULT_GRANULARITY or dynamically + setting with mallopt(M_GRANULARITY, value). +*/ +DLMALLOC_EXPORT mspace create_mspace(size_t capacity, int locked); + +/* + destroy_mspace destroys the given space, and attempts to return all + of its memory back to the system, returning the total number of + bytes freed. After destruction, the results of access to all memory + used by the space become undefined. +*/ +DLMALLOC_EXPORT size_t destroy_mspace(mspace msp); + +/* + create_mspace_with_base uses the memory supplied as the initial base + of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this + space is used for bookkeeping, so the capacity must be at least this + large. (Otherwise 0 is returned.) When this initial space is + exhausted, additional memory will be obtained from the system. + Destroying this space will deallocate all additionally allocated + space (if possible) but not the initial base. +*/ +DLMALLOC_EXPORT mspace create_mspace_with_base(void* base, size_t capacity, int locked); + +/* + mspace_track_large_chunks controls whether requests for large chunks + are allocated in their own untracked mmapped regions, separate from + others in this mspace. By default large chunks are not tracked, + which reduces fragmentation. However, such chunks are not + necessarily released to the system upon destroy_mspace. Enabling + tracking by setting to true may increase fragmentation, but avoids + leakage when relying on destroy_mspace to release all memory + allocated using this space. The function returns the previous + setting. +*/ +DLMALLOC_EXPORT int mspace_track_large_chunks(mspace msp, int enable); + + +/* + mspace_malloc behaves as malloc, but operates within + the given space. +*/ +DLMALLOC_EXPORT void* mspace_malloc(mspace msp, size_t bytes); + +/* + mspace_free behaves as free, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_free is not actually needed. + free may be called instead of mspace_free because freed chunks from + any space are handled by their originating spaces. +*/ +DLMALLOC_EXPORT void mspace_free(mspace msp, void* mem); + +/* + mspace_realloc behaves as realloc, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_realloc is not actually + needed. realloc may be called instead of mspace_realloc because + realloced chunks from any space are handled by their originating + spaces. +*/ +DLMALLOC_EXPORT void* mspace_realloc(mspace msp, void* mem, size_t newsize); + +/* + mspace_calloc behaves as calloc, but operates within + the given space. +*/ +DLMALLOC_EXPORT void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size); + +/* + mspace_memalign behaves as memalign, but operates within + the given space. +*/ +DLMALLOC_EXPORT void* mspace_memalign(mspace msp, size_t alignment, size_t bytes); + +/* + mspace_independent_calloc behaves as independent_calloc, but + operates within the given space. +*/ +DLMALLOC_EXPORT void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]); + +/* + mspace_independent_comalloc behaves as independent_comalloc, but + operates within the given space. +*/ +DLMALLOC_EXPORT void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]); + +/* + mspace_footprint() returns the number of bytes obtained from the + system for this space. +*/ +DLMALLOC_EXPORT size_t mspace_footprint(mspace msp); + +/* + mspace_max_footprint() returns the peak number of bytes obtained from the + system for this space. +*/ +DLMALLOC_EXPORT size_t mspace_max_footprint(mspace msp); + + +#if !NO_MALLINFO +/* + mspace_mallinfo behaves as mallinfo, but reports properties of + the given space. +*/ +DLMALLOC_EXPORT struct mallinfo mspace_mallinfo(mspace msp); +#endif /* NO_MALLINFO */ + +/* + malloc_usable_size(void* p) behaves the same as malloc_usable_size; +*/ +DLMALLOC_EXPORT size_t mspace_usable_size(void* mem); + +/* + mspace_malloc_stats behaves as malloc_stats, but reports + properties of the given space. +*/ +DLMALLOC_EXPORT void mspace_malloc_stats(mspace msp); + +/* + mspace_trim behaves as malloc_trim, but + operates within the given space. +*/ +DLMALLOC_EXPORT int mspace_trim(mspace msp, size_t pad); + +/* + An alias for mallopt. +*/ +DLMALLOC_EXPORT int mspace_mallopt(int, int); + +#endif /* MSPACES */ + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif /* __cplusplus */ + +/* + ======================================================================== + To make a fully customizable malloc.h header file, cut everything + above this line, put into file malloc.h, edit to suit, and #include it + on the next line, as well as in programs that use this malloc. + ======================================================================== +*/ + +/* #include "malloc.h" */ + +/*------------------------------ internal #includes ---------------------- */ + +#ifdef _MSC_VER +#pragma warning( disable : 4146 ) /* no "unsigned" warnings */ +#endif /* _MSC_VER */ +#if !NO_MALLOC_STATS +#include /* for printing in malloc_stats */ +#endif /* NO_MALLOC_STATS */ +#ifndef LACKS_ERRNO_H +#include /* for MALLOC_FAILURE_ACTION */ +#endif /* LACKS_ERRNO_H */ +#ifdef DEBUG +#if ABORT_ON_ASSERT_FAILURE +#undef assert +#define assert(x) if(!(x)) ABORT +#else /* ABORT_ON_ASSERT_FAILURE */ +#include +#endif /* ABORT_ON_ASSERT_FAILURE */ +#else /* DEBUG */ +#ifndef assert +#define assert(x) +#endif +#define DEBUG 0 +#endif /* DEBUG */ +#if !defined(WIN32) && !defined(LACKS_TIME_H) +#include /* for magic initialization */ +#endif /* WIN32 */ +#ifndef LACKS_STDLIB_H +#include /* for abort() */ +#endif /* LACKS_STDLIB_H */ +#ifndef LACKS_STRING_H +#include /* for memset etc */ +#endif /* LACKS_STRING_H */ +#if USE_BUILTIN_FFS +#ifndef LACKS_STRINGS_H +#include /* for ffs */ +#endif /* LACKS_STRINGS_H */ +#endif /* USE_BUILTIN_FFS */ +#if HAVE_MMAP +#ifndef LACKS_SYS_MMAN_H +/* On some versions of linux, mremap decl in mman.h needs __USE_GNU set */ +#if (defined(linux) && !defined(__USE_GNU)) +#define __USE_GNU 1 +#include /* for mmap */ +#undef __USE_GNU +#else +#include /* for mmap */ +#endif /* linux */ +#endif /* LACKS_SYS_MMAN_H */ +#ifndef LACKS_FCNTL_H +#include +#endif /* LACKS_FCNTL_H */ +#endif /* HAVE_MMAP */ +#ifndef LACKS_UNISTD_H +#include /* for sbrk, sysconf */ +#else /* LACKS_UNISTD_H */ +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) +extern void* sbrk(ptrdiff_t); +#endif /* FreeBSD etc */ +#endif /* LACKS_UNISTD_H */ + +/* Declarations for locking */ +#if USE_LOCKS +#ifndef WIN32 +#if defined (__SVR4) && defined (__sun) /* solaris */ +#include +#elif !defined(LACKS_SCHED_H) +#include +#endif /* solaris or LACKS_SCHED_H */ +#if (defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0) || !USE_SPIN_LOCKS +#include +#endif /* USE_RECURSIVE_LOCKS ... */ +#elif defined(_MSC_VER) +#ifndef _M_AMD64 +/* These are already defined on AMD64 builds */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +LONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp); +LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value); +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _M_AMD64 */ +#pragma intrinsic (_InterlockedCompareExchange) +#pragma intrinsic (_InterlockedExchange) +#define interlockedcompareexchange _InterlockedCompareExchange +#define interlockedexchange _InterlockedExchange +#elif defined(WIN32) && defined(__GNUC__) +#define interlockedcompareexchange(a, b, c) __sync_val_compare_and_swap(a, c, b) +#define interlockedexchange __sync_lock_test_and_set +#endif /* Win32 */ +#endif /* USE_LOCKS */ + +/* Declarations for bit scanning on win32 */ +#if defined(_MSC_VER) && _MSC_VER>=1300 +#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +unsigned char _BitScanForward(unsigned long *index, unsigned long mask); +unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#define BitScanForward _BitScanForward +#define BitScanReverse _BitScanReverse +#pragma intrinsic(_BitScanForward) +#pragma intrinsic(_BitScanReverse) +#endif /* BitScanForward */ +#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */ + +#ifndef WIN32 +#ifndef malloc_getpagesize +# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ +# ifndef _SC_PAGE_SIZE +# define _SC_PAGE_SIZE _SC_PAGESIZE +# endif +# endif +# ifdef _SC_PAGE_SIZE +# define malloc_getpagesize sysconf(_SC_PAGE_SIZE) +# else +# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) + extern size_t getpagesize(); +# define malloc_getpagesize getpagesize() +# else +# ifdef WIN32 /* use supplied emulation of getpagesize */ +# define malloc_getpagesize getpagesize() +# else +# ifndef LACKS_SYS_PARAM_H +# include +# endif +# ifdef EXEC_PAGESIZE +# define malloc_getpagesize EXEC_PAGESIZE +# else +# ifdef NBPG +# ifndef CLSIZE +# define malloc_getpagesize NBPG +# else +# define malloc_getpagesize (NBPG * CLSIZE) +# endif +# else +# ifdef NBPC +# define malloc_getpagesize NBPC +# else +# ifdef PAGESIZE +# define malloc_getpagesize PAGESIZE +# else /* just guess */ +# define malloc_getpagesize ((size_t)4096U) +# endif +# endif +# endif +# endif +# endif +# endif +# endif +#endif +#endif + +/* ------------------- size_t and alignment properties -------------------- */ + +/* The byte and bit size of a size_t */ +#define SIZE_T_SIZE (sizeof(size_t)) +#define SIZE_T_BITSIZE (sizeof(size_t) << 3) + +/* Some constants coerced to size_t */ +/* Annoying but necessary to avoid errors on some platforms */ +#define SIZE_T_ZERO ((size_t)0) +#define SIZE_T_ONE ((size_t)1) +#define SIZE_T_TWO ((size_t)2) +#define SIZE_T_FOUR ((size_t)4) +#define TWO_SIZE_T_SIZES (SIZE_T_SIZE<<1) +#define FOUR_SIZE_T_SIZES (SIZE_T_SIZE<<2) +#define SIX_SIZE_T_SIZES (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES) +#define HALF_MAX_SIZE_T (MAX_SIZE_T / 2U) + +/* The bit mask value corresponding to MALLOC_ALIGNMENT */ +#define CHUNK_ALIGN_MASK (MALLOC_ALIGNMENT - SIZE_T_ONE) + +/* True if address a has acceptable alignment */ +#define is_aligned(A) (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0) + +/* the number of bytes to offset an address to align it */ +#define align_offset(A)\ + ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\ + ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK)) + +/* -------------------------- MMAP preliminaries ------------------------- */ + +/* + If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and + checks to fail so compiler optimizer can delete code rather than + using so many "#if"s. +*/ + + +/* MORECORE and MMAP must return MFAIL on failure */ +#define MFAIL ((void*)(MAX_SIZE_T)) +#define CMFAIL ((char*)(MFAIL)) /* defined for convenience */ + +#if HAVE_MMAP + +#ifndef WIN32 +#define MUNMAP_DEFAULT(a, s) munmap((a), (s)) +#define MMAP_PROT (PROT_READ|PROT_WRITE) +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +#define MAP_ANONYMOUS MAP_ANON +#endif /* MAP_ANON */ +#ifdef MAP_ANONYMOUS +#define MMAP_FLAGS (MAP_PRIVATE|MAP_ANONYMOUS) +#define MMAP_DEFAULT(s) mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0) +#else /* MAP_ANONYMOUS */ +/* + Nearly all versions of mmap support MAP_ANONYMOUS, so the following + is unlikely to be needed, but is supplied just in case. +*/ +#define MMAP_FLAGS (MAP_PRIVATE) +static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */ +#define MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \ + (dev_zero_fd = open("/dev/zero", O_RDWR), \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) +#endif /* MAP_ANONYMOUS */ + +#define DIRECT_MMAP_DEFAULT(s) MMAP_DEFAULT(s) + +#else /* WIN32 */ + +/* Win32 MMAP via VirtualAlloc */ +static FORCEINLINE void* win32mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */ +static FORCEINLINE void* win32direct_mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, + PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* This function supports releasing coalesed segments */ +static FORCEINLINE int win32munmap(void* ptr, size_t size) { + MEMORY_BASIC_INFORMATION minfo; + char* cptr = (char*)ptr; + while (size) { + if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0) + return -1; + if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr || + minfo.State != MEM_COMMIT || minfo.RegionSize > size) + return -1; + if (VirtualFree(cptr, 0, MEM_RELEASE) == 0) + return -1; + cptr += minfo.RegionSize; + size -= minfo.RegionSize; + } + return 0; +} + +#define MMAP_DEFAULT(s) win32mmap(s) +#define MUNMAP_DEFAULT(a, s) win32munmap((a), (s)) +#define DIRECT_MMAP_DEFAULT(s) win32direct_mmap(s) +#endif /* WIN32 */ +#endif /* HAVE_MMAP */ + +#if HAVE_MREMAP +#ifndef WIN32 +#define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv)) +#endif /* WIN32 */ +#endif /* HAVE_MREMAP */ + +/** + * Define CALL_MORECORE + */ +#if HAVE_MORECORE + #ifdef MORECORE + #define CALL_MORECORE(S) MORECORE(S) + #else /* MORECORE */ + #define CALL_MORECORE(S) MORECORE_DEFAULT(S) + #endif /* MORECORE */ +#else /* HAVE_MORECORE */ + #define CALL_MORECORE(S) MFAIL +#endif /* HAVE_MORECORE */ + +/** + * Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP + */ +#if HAVE_MMAP + #define USE_MMAP_BIT (SIZE_T_ONE) + + #ifdef MMAP + #define CALL_MMAP(s) MMAP(s) + #else /* MMAP */ + #define CALL_MMAP(s) MMAP_DEFAULT(s) + #endif /* MMAP */ + #ifdef MUNMAP + #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) + #else /* MUNMAP */ + #define CALL_MUNMAP(a, s) MUNMAP_DEFAULT((a), (s)) + #endif /* MUNMAP */ + #ifdef DIRECT_MMAP + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) + #else /* DIRECT_MMAP */ + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s) + #endif /* DIRECT_MMAP */ +#else /* HAVE_MMAP */ + #define USE_MMAP_BIT (SIZE_T_ZERO) + + #define MMAP(s) MFAIL + #define MUNMAP(a, s) (-1) + #define DIRECT_MMAP(s) MFAIL + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) + #define CALL_MMAP(s) MMAP(s) + #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) +#endif /* HAVE_MMAP */ + +/** + * Define CALL_MREMAP + */ +#if HAVE_MMAP && HAVE_MREMAP + #ifdef MREMAP + #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv)) + #else /* MREMAP */ + #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv)) + #endif /* MREMAP */ +#else /* HAVE_MMAP && HAVE_MREMAP */ + #define CALL_MREMAP(addr, osz, nsz, mv) MFAIL +#endif /* HAVE_MMAP && HAVE_MREMAP */ + +/* mstate bit set if continguous morecore disabled or failed */ +#define USE_NONCONTIGUOUS_BIT (4U) + +/* segment bit set in create_mspace_with_base */ +#define EXTERN_BIT (8U) + + +/* --------------------------- Lock preliminaries ------------------------ */ + +/* + When locks are defined, there is one global lock, plus + one per-mspace lock. + + The global lock_ensures that mparams.magic and other unique + mparams values are initialized only once. It also protects + sequences of calls to MORECORE. In many cases sys_alloc requires + two calls, that should not be interleaved with calls by other + threads. This does not protect against direct calls to MORECORE + by other threads not using this lock, so there is still code to + cope the best we can on interference. + + Per-mspace locks surround calls to malloc, free, etc. + By default, locks are simple non-reentrant mutexes. + + Because lock-protected regions generally have bounded times, it is + OK to use the supplied simple spinlocks. Spinlocks are likely to + improve performance for lightly contended applications, but worsen + performance under heavy contention. + + If USE_LOCKS is > 1, the definitions of lock routines here are + bypassed, in which case you will need to define the type MLOCK_T, + and at least INITIAL_LOCK, DESTROY_LOCK, ACQUIRE_LOCK, RELEASE_LOCK + and TRY_LOCK. You must also declare a + static MLOCK_T malloc_global_mutex = { initialization values };. + +*/ + +#if !USE_LOCKS +#define USE_LOCK_BIT (0U) +#define INITIAL_LOCK(l) (0) +#define DESTROY_LOCK(l) (0) +#define ACQUIRE_MALLOC_GLOBAL_LOCK() +#define RELEASE_MALLOC_GLOBAL_LOCK() + +#else +#if USE_LOCKS > 1 +/* ----------------------- User-defined locks ------------------------ */ +/* Define your own lock implementation here */ +/* #define INITIAL_LOCK(lk) ... */ +/* #define DESTROY_LOCK(lk) ... */ +/* #define ACQUIRE_LOCK(lk) ... */ +/* #define RELEASE_LOCK(lk) ... */ +/* #define TRY_LOCK(lk) ... */ +/* static MLOCK_T malloc_global_mutex = ... */ + +#elif USE_SPIN_LOCKS + +/* First, define CAS_LOCK and CLEAR_LOCK on ints */ +/* Note CAS_LOCK defined to return 0 on success */ + +#if defined(__GNUC__)&& (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) +#define CAS_LOCK(sl) __sync_lock_test_and_set(sl, 1) +#define CLEAR_LOCK(sl) __sync_lock_release(sl) + +#elif (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) +/* Custom spin locks for older gcc on x86 */ +static FORCEINLINE int x86_cas_lock(int *sl) { + int ret; + int val = 1; + int cmp = 0; + __asm__ __volatile__ ("lock; cmpxchgl %1, %2" + : "=a" (ret) + : "r" (val), "m" (*(sl)), "0"(cmp) + : "memory", "cc"); + return ret; +} + +static FORCEINLINE void x86_clear_lock(int* sl) { + assert(*sl != 0); + int prev = 0; + int ret; + __asm__ __volatile__ ("lock; xchgl %0, %1" + : "=r" (ret) + : "m" (*(sl)), "0"(prev) + : "memory"); +} + +#define CAS_LOCK(sl) x86_cas_lock(sl) +#define CLEAR_LOCK(sl) x86_clear_lock(sl) + +#else /* Win32 MSC */ +#define CAS_LOCK(sl) interlockedexchange(sl, 1) +#define CLEAR_LOCK(sl) interlockedexchange (sl, 0) + +#endif /* ... gcc spins locks ... */ + +/* How to yield for a spin lock */ +#define SPINS_PER_YIELD 63 +#if defined(_MSC_VER) +#define SLEEP_EX_DURATION 50 /* delay for yield/sleep */ +#define SPIN_LOCK_YIELD SleepEx(SLEEP_EX_DURATION, FALSE) +#elif defined (__SVR4) && defined (__sun) /* solaris */ +#define SPIN_LOCK_YIELD thr_yield(); +#elif !defined(LACKS_SCHED_H) +#define SPIN_LOCK_YIELD sched_yield(); +#else +#define SPIN_LOCK_YIELD +#endif /* ... yield ... */ + +#if !defined(USE_RECURSIVE_LOCKS) || USE_RECURSIVE_LOCKS == 0 +/* Plain spin locks use single word (embedded in malloc_states) */ +static int spin_acquire_lock(int *sl) { + int spins = 0; + while (*(volatile int *)sl != 0 || CAS_LOCK(sl)) { + if ((++spins & SPINS_PER_YIELD) == 0) { + SPIN_LOCK_YIELD; + } + } + return 0; +} + +#define MLOCK_T int +#define TRY_LOCK(sl) !CAS_LOCK(sl) +#define RELEASE_LOCK(sl) CLEAR_LOCK(sl) +#define ACQUIRE_LOCK(sl) (CAS_LOCK(sl)? spin_acquire_lock(sl) : 0) +#define INITIAL_LOCK(sl) (*sl = 0) +#define DESTROY_LOCK(sl) (0) +static MLOCK_T malloc_global_mutex = 0; + +#else /* USE_RECURSIVE_LOCKS */ +/* types for lock owners */ +#ifdef WIN32 +#define THREAD_ID_T DWORD +#define CURRENT_THREAD GetCurrentThreadId() +#define EQ_OWNER(X,Y) ((X) == (Y)) +#else +/* + Note: the following assume that pthread_t is a type that can be + initialized to (casted) zero. If this is not the case, you will need to + somehow redefine these or not use spin locks. +*/ +#define THREAD_ID_T pthread_t +#define CURRENT_THREAD pthread_self() +#define EQ_OWNER(X,Y) pthread_equal(X, Y) +#endif + +struct malloc_recursive_lock { + int sl; + unsigned int c; + THREAD_ID_T threadid; +}; + +#define MLOCK_T struct malloc_recursive_lock +static MLOCK_T malloc_global_mutex = { 0, 0, (THREAD_ID_T)0}; + +static FORCEINLINE void recursive_release_lock(MLOCK_T *lk) { + assert(lk->sl != 0); + if (--lk->c == 0) { + CLEAR_LOCK(&lk->sl); + } +} + +static FORCEINLINE int recursive_acquire_lock(MLOCK_T *lk) { + THREAD_ID_T mythreadid = CURRENT_THREAD; + int spins = 0; + for (;;) { + if (*((volatile int *)(&lk->sl)) == 0) { + if (!CAS_LOCK(&lk->sl)) { + lk->threadid = mythreadid; + lk->c = 1; + return 0; + } + } + else if (EQ_OWNER(lk->threadid, mythreadid)) { + ++lk->c; + return 0; + } + if ((++spins & SPINS_PER_YIELD) == 0) { + SPIN_LOCK_YIELD; + } + } +} + +static FORCEINLINE int recursive_try_lock(MLOCK_T *lk) { + THREAD_ID_T mythreadid = CURRENT_THREAD; + if (*((volatile int *)(&lk->sl)) == 0) { + if (!CAS_LOCK(&lk->sl)) { + lk->threadid = mythreadid; + lk->c = 1; + return 1; + } + } + else if (EQ_OWNER(lk->threadid, mythreadid)) { + ++lk->c; + return 1; + } + return 0; +} + +#define RELEASE_LOCK(lk) recursive_release_lock(lk) +#define TRY_LOCK(lk) recursive_try_lock(lk) +#define ACQUIRE_LOCK(lk) recursive_acquire_lock(lk) +#define INITIAL_LOCK(lk) ((lk)->threadid = (THREAD_ID_T)0, (lk)->sl = 0, (lk)->c = 0) +#define DESTROY_LOCK(lk) (0) +#endif /* USE_RECURSIVE_LOCKS */ + +#elif defined(WIN32) /* Win32 critical sections */ +#define MLOCK_T CRITICAL_SECTION +#define ACQUIRE_LOCK(lk) (EnterCriticalSection(lk), 0) +#define RELEASE_LOCK(lk) LeaveCriticalSection(lk) +#define TRY_LOCK(lk) TryEnterCriticalSection(lk) +#define INITIAL_LOCK(lk) (!InitializeCriticalSectionAndSpinCount((lk), 0x80000000|4000)) +#define DESTROY_LOCK(lk) (DeleteCriticalSection(lk), 0) +#define NEED_GLOBAL_LOCK_INIT + +static MLOCK_T malloc_global_mutex; +static volatile long malloc_global_mutex_status; + +/* Use spin loop to initialize global lock */ +static void init_malloc_global_mutex() { + for (;;) { + long stat = malloc_global_mutex_status; + if (stat > 0) + return; + /* transition to < 0 while initializing, then to > 0) */ + if (stat == 0 && + interlockedcompareexchange(&malloc_global_mutex_status, -1, 0) == 0) { + InitializeCriticalSection(&malloc_global_mutex); + interlockedexchange(&malloc_global_mutex_status,1); + return; + } + SleepEx(0, FALSE); + } +} + +#else /* pthreads-based locks */ +#define MLOCK_T pthread_mutex_t +#define ACQUIRE_LOCK(lk) pthread_mutex_lock(lk) +#define RELEASE_LOCK(lk) pthread_mutex_unlock(lk) +#define TRY_LOCK(lk) (!pthread_mutex_trylock(lk)) +#define INITIAL_LOCK(lk) pthread_init_lock(lk) +#define DESTROY_LOCK(lk) pthread_mutex_destroy(lk) + +#if defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0 && defined(linux) && !defined(PTHREAD_MUTEX_RECURSIVE) +/* Cope with old-style linux recursive lock initialization by adding */ +/* skipped internal declaration from pthread.h */ +extern int pthread_mutexattr_setkind_np __P ((pthread_mutexattr_t *__attr, + int __kind)); +#define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP +#define pthread_mutexattr_settype(x,y) pthread_mutexattr_setkind_np(x,y) +#endif /* USE_RECURSIVE_LOCKS ... */ + +static MLOCK_T malloc_global_mutex = PTHREAD_MUTEX_INITIALIZER; + +static int pthread_init_lock (MLOCK_T *lk) { + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr)) return 1; +#if defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0 + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return 1; +#endif + if (pthread_mutex_init(lk, &attr)) return 1; + if (pthread_mutexattr_destroy(&attr)) return 1; + return 0; +} + +#endif /* ... lock types ... */ + +/* Common code for all lock types */ +#define USE_LOCK_BIT (2U) + +#ifndef ACQUIRE_MALLOC_GLOBAL_LOCK +#define ACQUIRE_MALLOC_GLOBAL_LOCK() ACQUIRE_LOCK(&malloc_global_mutex); +#endif + +#ifndef RELEASE_MALLOC_GLOBAL_LOCK +#define RELEASE_MALLOC_GLOBAL_LOCK() RELEASE_LOCK(&malloc_global_mutex); +#endif + +#endif /* USE_LOCKS */ + +/* ----------------------- Chunk representations ------------------------ */ + +/* + (The following includes lightly edited explanations by Colin Plumb.) + + The malloc_chunk declaration below is misleading (but accurate and + necessary). It declares a "view" into memory allowing access to + necessary fields at known offsets from a given base. + + Chunks of memory are maintained using a `boundary tag' method as + originally described by Knuth. (See the paper by Paul Wilson + ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such + techniques.) Sizes of free chunks are stored both in the front of + each chunk and at the end. This makes consolidating fragmented + chunks into bigger chunks fast. The head fields also hold bits + representing whether chunks are free or in use. + + Here are some pictures to make it clearer. They are "exploded" to + show that the state of a chunk can be thought of as extending from + the high 31 bits of the head field of its header through the + prev_foot and PINUSE_BIT bit of the following chunk header. + + A chunk that's in use looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk (if P = 0) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 1| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + +- -+ + | | + +- -+ + | : + +- size - sizeof(size_t) available payload bytes -+ + : | + chunk-> +- -+ + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1| + | Size of next chunk (may or may not be in use) | +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + And if it's free, it looks like this: + + chunk-> +- -+ + | User payload (must be in use, or we would have merged!) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 0| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Prev pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- size - sizeof(struct chunk) unused bytes -+ + : | + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0| + | Size of next chunk (must be in use, or we would have merged)| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- User payload -+ + : | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0| + +-+ + Note that since we always merge adjacent free chunks, the chunks + adjacent to a free chunk must be in use. + + Given a pointer to a chunk (which can be derived trivially from the + payload pointer) we can, in O(1) time, find out whether the adjacent + chunks are free, and if so, unlink them from the lists that they + are on and merge them with the current chunk. + + Chunks always begin on even word boundaries, so the mem portion + (which is returned to the user) is also on an even word boundary, and + thus at least double-word aligned. + + The P (PINUSE_BIT) bit, stored in the unused low-order bit of the + chunk size (which is always a multiple of two words), is an in-use + bit for the *previous* chunk. If that bit is *clear*, then the + word before the current chunk size contains the previous chunk + size, and can be used to find the front of the previous chunk. + The very first chunk allocated always has this bit set, preventing + access to non-existent (or non-owned) memory. If pinuse is set for + any given chunk, then you CANNOT determine the size of the + previous chunk, and might even get a memory addressing fault when + trying to do so. + + The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of + the chunk size redundantly records whether the current chunk is + inuse (unless the chunk is mmapped). This redundancy enables usage + checks within free and realloc, and reduces indirection when freeing + and consolidating chunks. + + Each freshly allocated chunk must have both cinuse and pinuse set. + That is, each allocated chunk borders either a previously allocated + and still in-use chunk, or the base of its memory arena. This is + ensured by making all allocations from the `lowest' part of any + found chunk. Further, no free chunk physically borders another one, + so each free chunk is known to be preceded and followed by either + inuse chunks or the ends of memory. + + Note that the `foot' of the current chunk is actually represented + as the prev_foot of the NEXT chunk. This makes it easier to + deal with alignments etc but can be very confusing when trying + to extend or adapt this code. + + The exceptions to all this are + + 1. The special chunk `top' is the top-most available chunk (i.e., + the one bordering the end of available memory). It is treated + specially. Top is never included in any bin, is used only if + no other chunk is available, and is released back to the + system if it is very large (see M_TRIM_THRESHOLD). In effect, + the top chunk is treated as larger (and thus less well + fitting) than any other available chunk. The top chunk + doesn't update its trailing size field since there is no next + contiguous chunk that would have to index off it. However, + space is still allocated for it (TOP_FOOT_SIZE) to enable + separation or merging when space is extended. + + 3. Chunks allocated via mmap, have both cinuse and pinuse bits + cleared in their head fields. Because they are allocated + one-by-one, each must carry its own prev_foot field, which is + also used to hold the offset this chunk has within its mmapped + region, which is needed to preserve alignment. Each mmapped + chunk is trailed by the first two fields of a fake next-chunk + for sake of usage checks. + +*/ + +struct malloc_chunk { + size_t prev_foot; /* Size of previous chunk (if free). */ + size_t head; /* Size and inuse bits. */ + struct malloc_chunk* fd; /* double links -- used only if free. */ + struct malloc_chunk* bk; +}; + +typedef struct malloc_chunk mchunk; +typedef struct malloc_chunk* mchunkptr; +typedef struct malloc_chunk* sbinptr; /* The type of bins of chunks */ +typedef unsigned int bindex_t; /* Described below */ +typedef unsigned int binmap_t; /* Described below */ +typedef unsigned int flag_t; /* The type of various bit flag sets */ + +/* ------------------- Chunks sizes and alignments ----------------------- */ + +#define MCHUNK_SIZE (sizeof(mchunk)) + +#if FOOTERS +#define CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +#else /* FOOTERS */ +#define CHUNK_OVERHEAD (SIZE_T_SIZE) +#endif /* FOOTERS */ + +/* MMapped chunks need a second word of overhead ... */ +#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +/* ... and additional padding for fake next-chunk at foot */ +#define MMAP_FOOT_PAD (FOUR_SIZE_T_SIZES) + +/* The smallest size we can malloc is an aligned minimal chunk */ +#define MIN_CHUNK_SIZE\ + ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* conversion from malloc headers to user pointers, and back */ +#define chunk2mem(p) ((void*)((char*)(p) + TWO_SIZE_T_SIZES)) +#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES)) +/* chunk associated with aligned address A */ +#define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A))) + +/* Bounds on request (not chunk) sizes. */ +#define MAX_REQUEST ((-MIN_CHUNK_SIZE) << 2) +#define MIN_REQUEST (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE) + +/* pad request bytes into a usable size */ +#define pad_request(req) \ + (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* pad request, checking for minimum (but not maximum) */ +#define request2size(req) \ + (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req)) + + +/* ------------------ Operations on head and foot fields ----------------- */ + +/* + The head field of a chunk is or'ed with PINUSE_BIT when previous + adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in + use, unless mmapped, in which case both bits are cleared. + + FLAG4_BIT is not used by this malloc, but might be useful in extensions. +*/ + +#define PINUSE_BIT (SIZE_T_ONE) +#define CINUSE_BIT (SIZE_T_TWO) +#define FLAG4_BIT (SIZE_T_FOUR) +#define INUSE_BITS (PINUSE_BIT|CINUSE_BIT) +#define FLAG_BITS (PINUSE_BIT|CINUSE_BIT|FLAG4_BIT) + +/* Head value for fenceposts */ +#define FENCEPOST_HEAD (INUSE_BITS|SIZE_T_SIZE) + +/* extraction of fields from head words */ +#define cinuse(p) ((p)->head & CINUSE_BIT) +#define pinuse(p) ((p)->head & PINUSE_BIT) +#define flag4inuse(p) ((p)->head & FLAG4_BIT) +#define is_inuse(p) (((p)->head & INUSE_BITS) != PINUSE_BIT) +#define is_mmapped(p) (((p)->head & INUSE_BITS) == 0) + +#define chunksize(p) ((p)->head & ~(FLAG_BITS)) + +#define clear_pinuse(p) ((p)->head &= ~PINUSE_BIT) +#define set_flag4(p) ((p)->head |= FLAG4_BIT) +#define clear_flag4(p) ((p)->head &= ~FLAG4_BIT) + +/* Treat space at ptr +/- offset as a chunk */ +#define chunk_plus_offset(p, s) ((mchunkptr)(((char*)(p)) + (s))) +#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s))) + +/* Ptr to next or previous physical malloc_chunk. */ +#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~FLAG_BITS))) +#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) )) + +/* extract next chunk's pinuse bit */ +#define next_pinuse(p) ((next_chunk(p)->head) & PINUSE_BIT) + +/* Get/set size at footer */ +#define get_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot) +#define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s)) + +/* Set size, pinuse bit, and foot */ +#define set_size_and_pinuse_of_free_chunk(p, s)\ + ((p)->head = (s|PINUSE_BIT), set_foot(p, s)) + +/* Set size, pinuse bit, foot, and clear next pinuse */ +#define set_free_with_pinuse(p, s, n)\ + (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s)) + +/* Get the internal overhead associated with chunk p */ +#define overhead_for(p)\ + (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD) + +/* Return true if malloced space is not necessarily cleared */ +#if MMAP_CLEARS +#define calloc_must_clear(p) (!is_mmapped(p)) +#else /* MMAP_CLEARS */ +#define calloc_must_clear(p) (1) +#endif /* MMAP_CLEARS */ + +/* ---------------------- Overlaid data structures ----------------------- */ + +/* + When chunks are not in use, they are treated as nodes of either + lists or trees. + + "Small" chunks are stored in circular doubly-linked lists, and look + like this: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space (may be 0 bytes long) . + . . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Larger chunks are kept in a form of bitwise digital trees (aka + tries) keyed on chunksizes. Because malloc_tree_chunks are only for + free chunks greater than 256 bytes, their size doesn't impose any + constraints on user chunk sizes. Each node looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to left child (child[0]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to right child (child[1]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to parent | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | bin index of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Each tree holding treenodes is a tree of unique chunk sizes. Chunks + of the same size are arranged in a circularly-linked list, with only + the oldest chunk (the next to be used, in our FIFO ordering) + actually in the tree. (Tree members are distinguished by a non-null + parent pointer.) If a chunk with the same size an an existing node + is inserted, it is linked off the existing node using pointers that + work in the same way as fd/bk pointers of small chunks. + + Each tree contains a power of 2 sized range of chunk sizes (the + smallest is 0x100 <= x < 0x180), which is is divided in half at each + tree level, with the chunks in the smaller half of the range (0x100 + <= x < 0x140 for the top nose) in the left subtree and the larger + half (0x140 <= x < 0x180) in the right subtree. This is, of course, + done by inspecting individual bits. + + Using these rules, each node's left subtree contains all smaller + sizes than its right subtree. However, the node at the root of each + subtree has no particular ordering relationship to either. (The + dividing line between the subtree sizes is based on trie relation.) + If we remove the last chunk of a given size from the interior of the + tree, we need to replace it with a leaf node. The tree ordering + rules permit a node to be replaced by any leaf below it. + + The smallest chunk in a tree (a common operation in a best-fit + allocator) can be found by walking a path to the leftmost leaf in + the tree. Unlike a usual binary tree, where we follow left child + pointers until we reach a null, here we follow the right child + pointer any time the left one is null, until we reach a leaf with + both child pointers null. The smallest chunk in the tree will be + somewhere along that path. + + The worst case number of steps to add, find, or remove a node is + bounded by the number of bits differentiating chunks within + bins. Under current bin calculations, this ranges from 6 up to 21 + (for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case + is of course much better. +*/ + +struct malloc_tree_chunk { + /* The first four fields must be compatible with malloc_chunk */ + size_t prev_foot; + size_t head; + struct malloc_tree_chunk* fd; + struct malloc_tree_chunk* bk; + + struct malloc_tree_chunk* child[2]; + struct malloc_tree_chunk* parent; + bindex_t index; +}; + +typedef struct malloc_tree_chunk tchunk; +typedef struct malloc_tree_chunk* tchunkptr; +typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */ + +/* A little helper macro for trees */ +#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1]) + +/* ----------------------------- Segments -------------------------------- */ + +/* + Each malloc space may include non-contiguous segments, held in a + list headed by an embedded malloc_segment record representing the + top-most space. Segments also include flags holding properties of + the space. Large chunks that are directly allocated by mmap are not + included in this list. They are instead independently created and + destroyed without otherwise keeping track of them. + + Segment management mainly comes into play for spaces allocated by + MMAP. Any call to MMAP might or might not return memory that is + adjacent to an existing segment. MORECORE normally contiguously + extends the current space, so this space is almost always adjacent, + which is simpler and faster to deal with. (This is why MORECORE is + used preferentially to MMAP when both are available -- see + sys_alloc.) When allocating using MMAP, we don't use any of the + hinting mechanisms (inconsistently) supported in various + implementations of unix mmap, or distinguish reserving from + committing memory. Instead, we just ask for space, and exploit + contiguity when we get it. It is probably possible to do + better than this on some systems, but no general scheme seems + to be significantly better. + + Management entails a simpler variant of the consolidation scheme + used for chunks to reduce fragmentation -- new adjacent memory is + normally prepended or appended to an existing segment. However, + there are limitations compared to chunk consolidation that mostly + reflect the fact that segment processing is relatively infrequent + (occurring only when getting memory from system) and that we + don't expect to have huge numbers of segments: + + * Segments are not indexed, so traversal requires linear scans. (It + would be possible to index these, but is not worth the extra + overhead and complexity for most programs on most platforms.) + * New segments are only appended to old ones when holding top-most + memory; if they cannot be prepended to others, they are held in + different segments. + + Except for the top-most segment of an mstate, each segment record + is kept at the tail of its segment. Segments are added by pushing + segment records onto the list headed by &mstate.seg for the + containing mstate. + + Segment flags control allocation/merge/deallocation policies: + * If EXTERN_BIT set, then we did not allocate this segment, + and so should not try to deallocate or merge with others. + (This currently holds only for the initial segment passed + into create_mspace_with_base.) + * If USE_MMAP_BIT set, the segment may be merged with + other surrounding mmapped segments and trimmed/de-allocated + using munmap. + * If neither bit is set, then the segment was obtained using + MORECORE so can be merged with surrounding MORECORE'd segments + and deallocated/trimmed using MORECORE with negative arguments. +*/ + +struct malloc_segment { + char* base; /* base address */ + size_t size; /* allocated size */ + struct malloc_segment* next; /* ptr to next segment */ + flag_t sflags; /* mmap and extern flag */ +}; + +#define is_mmapped_segment(S) ((S)->sflags & USE_MMAP_BIT) +#define is_extern_segment(S) ((S)->sflags & EXTERN_BIT) + +typedef struct malloc_segment msegment; +typedef struct malloc_segment* msegmentptr; + +/* ---------------------------- malloc_state ----------------------------- */ + +/* + A malloc_state holds all of the bookkeeping for a space. + The main fields are: + + Top + The topmost chunk of the currently active segment. Its size is + cached in topsize. The actual size of topmost space is + topsize+TOP_FOOT_SIZE, which includes space reserved for adding + fenceposts and segment records if necessary when getting more + space from the system. The size at which to autotrim top is + cached from mparams in trim_check, except that it is disabled if + an autotrim fails. + + Designated victim (dv) + This is the preferred chunk for servicing small requests that + don't have exact fits. It is normally the chunk split off most + recently to service another small request. Its size is cached in + dvsize. The link fields of this chunk are not maintained since it + is not kept in a bin. + + SmallBins + An array of bin headers for free chunks. These bins hold chunks + with sizes less than MIN_LARGE_SIZE bytes. Each bin contains + chunks of all the same size, spaced 8 bytes apart. To simplify + use in double-linked lists, each bin header acts as a malloc_chunk + pointing to the real first node, if it exists (else pointing to + itself). This avoids special-casing for headers. But to avoid + waste, we allocate only the fd/bk pointers of bins, and then use + repositioning tricks to treat these as the fields of a chunk. + + TreeBins + Treebins are pointers to the roots of trees holding a range of + sizes. There are 2 equally spaced treebins for each power of two + from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything + larger. + + Bin maps + There is one bit map for small bins ("smallmap") and one for + treebins ("treemap). Each bin sets its bit when non-empty, and + clears the bit when empty. Bit operations are then used to avoid + bin-by-bin searching -- nearly all "search" is done without ever + looking at bins that won't be selected. The bit maps + conservatively use 32 bits per map word, even if on 64bit system. + For a good description of some of the bit-based techniques used + here, see Henry S. Warren Jr's book "Hacker's Delight" (and + supplement at http://hackersdelight.org/). Many of these are + intended to reduce the branchiness of paths through malloc etc, as + well as to reduce the number of memory locations read or written. + + Segments + A list of segments headed by an embedded malloc_segment record + representing the initial space. + + Address check support + The least_addr field is the least address ever obtained from + MORECORE or MMAP. Attempted frees and reallocs of any address less + than this are trapped (unless INSECURE is defined). + + Magic tag + A cross-check field that should always hold same value as mparams.magic. + + Max allowed footprint + The maximum allowed bytes to allocate from system (zero means no limit) + + Flags + Bits recording whether to use MMAP, locks, or contiguous MORECORE + + Statistics + Each space keeps track of current and maximum system memory + obtained via MORECORE or MMAP. + + Trim support + Fields holding the amount of unused topmost memory that should trigger + trimming, and a counter to force periodic scanning to release unused + non-topmost segments. + + Locking + If USE_LOCKS is defined, the "mutex" lock is acquired and released + around every public call using this mspace. + + Extension support + A void* pointer and a size_t field that can be used to help implement + extensions to this malloc. +*/ + +/* Bin types, widths and sizes */ +#define NSMALLBINS (32U) +#define NTREEBINS (32U) +#define SMALLBIN_SHIFT (3U) +#define SMALLBIN_WIDTH (SIZE_T_ONE << SMALLBIN_SHIFT) +#define TREEBIN_SHIFT (8U) +#define MIN_LARGE_SIZE (SIZE_T_ONE << TREEBIN_SHIFT) +#define MAX_SMALL_SIZE (MIN_LARGE_SIZE - SIZE_T_ONE) +#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD) + +struct malloc_state { + binmap_t smallmap; + binmap_t treemap; + size_t dvsize; + size_t topsize; + char* least_addr; + mchunkptr dv; + mchunkptr top; + size_t trim_check; + size_t release_checks; + size_t magic; + mchunkptr smallbins[(NSMALLBINS+1)*2]; + tbinptr treebins[NTREEBINS]; + size_t footprint; + size_t max_footprint; + size_t footprint_limit; /* zero means no limit */ + flag_t mflags; +#if USE_LOCKS + MLOCK_T mutex; /* locate lock among fields that rarely change */ +#endif /* USE_LOCKS */ + msegment seg; + void* extp; /* Unused but available for extensions */ + size_t exts; +}; + +typedef struct malloc_state* mstate; + +/* ------------- Global malloc_state and malloc_params ------------------- */ + +/* + malloc_params holds global properties, including those that can be + dynamically set using mallopt. There is a single instance, mparams, + initialized in init_mparams. Note that the non-zeroness of "magic" + also serves as an initialization flag. +*/ + +struct malloc_params { + size_t magic; + size_t page_size; + size_t granularity; + size_t mmap_threshold; + size_t trim_threshold; + flag_t default_mflags; +}; + +static struct malloc_params mparams; + +/* Ensure mparams initialized */ +#define ensure_initialization() (void)(mparams.magic != 0 || init_mparams()) + +#if !ONLY_MSPACES + +/* The global malloc_state used for all non-"mspace" calls */ +static struct malloc_state _gm_; +#define gm (&_gm_) +#define is_global(M) ((M) == &_gm_) + +#endif /* !ONLY_MSPACES */ + +#define is_initialized(M) ((M)->top != 0) + +/* -------------------------- system alloc setup ------------------------- */ + +/* Operations on mflags */ + +#define use_lock(M) ((M)->mflags & USE_LOCK_BIT) +#define enable_lock(M) ((M)->mflags |= USE_LOCK_BIT) +#if USE_LOCKS +#define disable_lock(M) ((M)->mflags &= ~USE_LOCK_BIT) +#else +#define disable_lock(M) +#endif + +#define use_mmap(M) ((M)->mflags & USE_MMAP_BIT) +#define enable_mmap(M) ((M)->mflags |= USE_MMAP_BIT) +#if HAVE_MMAP +#define disable_mmap(M) ((M)->mflags &= ~USE_MMAP_BIT) +#else +#define disable_mmap(M) +#endif + +#define use_noncontiguous(M) ((M)->mflags & USE_NONCONTIGUOUS_BIT) +#define disable_contiguous(M) ((M)->mflags |= USE_NONCONTIGUOUS_BIT) + +#define set_lock(M,L)\ + ((M)->mflags = (L)?\ + ((M)->mflags | USE_LOCK_BIT) :\ + ((M)->mflags & ~USE_LOCK_BIT)) + +/* page-align a size */ +#define page_align(S)\ + (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE)) + +/* granularity-align a size */ +#define granularity_align(S)\ + (((S) + (mparams.granularity - SIZE_T_ONE))\ + & ~(mparams.granularity - SIZE_T_ONE)) + + +/* For mmap, use granularity alignment on windows, else page-align */ +#ifdef WIN32 +#define mmap_align(S) granularity_align(S) +#else +#define mmap_align(S) page_align(S) +#endif + +/* For sys_alloc, enough padding to ensure can malloc request on success */ +#define SYS_ALLOC_PADDING (TOP_FOOT_SIZE + MALLOC_ALIGNMENT) + +#define is_page_aligned(S)\ + (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0) +#define is_granularity_aligned(S)\ + (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0) + +/* True if segment S holds address A */ +#define segment_holds(S, A)\ + ((char*)(A) >= S->base && (char*)(A) < S->base + S->size) + +/* Return segment holding given address */ +static msegmentptr segment_holding(mstate m, char* addr) { + msegmentptr sp = &m->seg; + for (;;) { + if (addr >= sp->base && addr < sp->base + sp->size) + return sp; + if ((sp = sp->next) == 0) + return 0; + } +} + +/* Return true if segment contains a segment link */ +static int has_segment_link(mstate m, msegmentptr ss) { + msegmentptr sp = &m->seg; + for (;;) { + if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size) + return 1; + if ((sp = sp->next) == 0) + return 0; + } +} + +#ifndef MORECORE_CANNOT_TRIM +#define should_trim(M,s) ((s) > (M)->trim_check) +#else /* MORECORE_CANNOT_TRIM */ +#define should_trim(M,s) (0) +#endif /* MORECORE_CANNOT_TRIM */ + +/* + TOP_FOOT_SIZE is padding at the end of a segment, including space + that may be needed to place segment records and fenceposts when new + noncontiguous segments are added. +*/ +#define TOP_FOOT_SIZE\ + (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE) + + +/* ------------------------------- Hooks -------------------------------- */ + +/* + PREACTION should be defined to return 0 on success, and nonzero on + failure. If you are not using locking, you can redefine these to do + anything you like. +*/ + +#if USE_LOCKS +#define PREACTION(M) ((use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0) +#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); } +#else /* USE_LOCKS */ + +#ifndef PREACTION +#define PREACTION(M) (0) +#endif /* PREACTION */ + +#ifndef POSTACTION +#define POSTACTION(M) +#endif /* POSTACTION */ + +#endif /* USE_LOCKS */ + +/* + CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses. + USAGE_ERROR_ACTION is triggered on detected bad frees and + reallocs. The argument p is an address that might have triggered the + fault. It is ignored by the two predefined actions, but might be + useful in custom actions that try to help diagnose errors. +*/ + +#if PROCEED_ON_ERROR + +/* A count of the number of corruption errors causing resets */ +int malloc_corruption_error_count; + +/* default corruption action */ +static void reset_on_error(mstate m); + +#define CORRUPTION_ERROR_ACTION(m) reset_on_error(m) +#define USAGE_ERROR_ACTION(m, p) + +#else /* PROCEED_ON_ERROR */ + +#ifndef CORRUPTION_ERROR_ACTION +#define CORRUPTION_ERROR_ACTION(m) ABORT +#endif /* CORRUPTION_ERROR_ACTION */ + +#ifndef USAGE_ERROR_ACTION +#define USAGE_ERROR_ACTION(m,p) ABORT +#endif /* USAGE_ERROR_ACTION */ + +#endif /* PROCEED_ON_ERROR */ + + +/* -------------------------- Debugging setup ---------------------------- */ + +#if ! DEBUG + +#define check_free_chunk(M,P) +#define check_inuse_chunk(M,P) +#define check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) +#define check_malloc_state(M) +#define check_top_chunk(M,P) + +#else /* DEBUG */ +#define check_free_chunk(M,P) do_check_free_chunk(M,P) +#define check_inuse_chunk(M,P) do_check_inuse_chunk(M,P) +#define check_top_chunk(M,P) do_check_top_chunk(M,P) +#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) do_check_mmapped_chunk(M,P) +#define check_malloc_state(M) do_check_malloc_state(M) + +static void do_check_any_chunk(mstate m, mchunkptr p); +static void do_check_top_chunk(mstate m, mchunkptr p); +static void do_check_mmapped_chunk(mstate m, mchunkptr p); +static void do_check_inuse_chunk(mstate m, mchunkptr p); +static void do_check_free_chunk(mstate m, mchunkptr p); +static void do_check_malloced_chunk(mstate m, void* mem, size_t s); +static void do_check_tree(mstate m, tchunkptr t); +static void do_check_treebin(mstate m, bindex_t i); +static void do_check_smallbin(mstate m, bindex_t i); +static void do_check_malloc_state(mstate m); +static int bin_find(mstate m, mchunkptr x); +static size_t traverse_and_check(mstate m); +#endif /* DEBUG */ + +/* ---------------------------- Indexing Bins ---------------------------- */ + +#define is_small(s) (((s) >> SMALLBIN_SHIFT) < NSMALLBINS) +#define small_index(s) (bindex_t)((s) >> SMALLBIN_SHIFT) +#define small_index2size(i) ((i) << SMALLBIN_SHIFT) +#define MIN_SMALL_INDEX (small_index(MIN_CHUNK_SIZE)) + +/* addressing by index. See above about smallbin repositioning */ +#define smallbin_at(M, i) ((sbinptr)((char*)&((M)->smallbins[(i)<<1]))) +#define treebin_at(M,i) (&((M)->treebins[i])) + +/* assign tree index for size S to variable I. Use x86 asm if possible */ +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_tree_index(S, I)\ +{\ + unsigned int X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K = (unsigned) sizeof(X)*__CHAR_BIT__ - 1 - (unsigned) __builtin_clz(X); \ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K = _bit_scan_reverse (X); \ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K;\ + _BitScanReverse((DWORD *) &K, (DWORD) X);\ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#else /* GNUC */ +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int Y = (unsigned int)X;\ + unsigned int N = ((Y - 0x100) >> 16) & 8;\ + unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\ + N += K;\ + N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\ + K = 14 - N + ((Y <<= K) >> 15);\ + I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\ + }\ +} +#endif /* GNUC */ + +/* Bit representing maximum resolved size in a treebin at i */ +#define bit_for_tree_index(i) \ + (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2) + +/* Shift placing maximum resolved bit in a treebin at i as sign bit */ +#define leftshift_for_tree_index(i) \ + ((i == NTREEBINS-1)? 0 : \ + ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2))) + +/* The size of the smallest chunk held in bin with index i */ +#define minsize_for_tree_index(i) \ + ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) | \ + (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1))) + + +/* ------------------------ Operations on bin maps ----------------------- */ + +/* bit corresponding to given index */ +#define idx2bit(i) ((binmap_t)(1) << (i)) + +/* Mark/Clear bits with given index */ +#define mark_smallmap(M,i) ((M)->smallmap |= idx2bit(i)) +#define clear_smallmap(M,i) ((M)->smallmap &= ~idx2bit(i)) +#define smallmap_is_marked(M,i) ((M)->smallmap & idx2bit(i)) + +#define mark_treemap(M,i) ((M)->treemap |= idx2bit(i)) +#define clear_treemap(M,i) ((M)->treemap &= ~idx2bit(i)) +#define treemap_is_marked(M,i) ((M)->treemap & idx2bit(i)) + +/* isolate the least set bit of a bitmap */ +#define least_bit(x) ((x) & -(x)) + +/* mask with all bits to left of least bit of x on */ +#define left_bits(x) ((x<<1) | -(x<<1)) + +/* mask with all bits to left of or equal to least bit of x on */ +#define same_or_left_bits(x) ((x) | -(x)) + +/* index corresponding to given bit. Use x86 asm if possible */ + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + J = __builtin_ctz(X); \ + I = (bindex_t)J;\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + J = _bit_scan_forward (X); \ + I = (bindex_t)J;\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + _BitScanForward((DWORD *) &J, X);\ + I = (bindex_t)J;\ +} + +#elif USE_BUILTIN_FFS +#define compute_bit2idx(X, I) I = ffs(X)-1 + +#else +#define compute_bit2idx(X, I)\ +{\ + unsigned int Y = X - 1;\ + unsigned int K = Y >> (16-4) & 16;\ + unsigned int N = K; Y >>= K;\ + N += K = Y >> (8-3) & 8; Y >>= K;\ + N += K = Y >> (4-2) & 4; Y >>= K;\ + N += K = Y >> (2-1) & 2; Y >>= K;\ + N += K = Y >> (1-0) & 1; Y >>= K;\ + I = (bindex_t)(N + Y);\ +} +#endif /* GNUC */ + + +/* ----------------------- Runtime Check Support ------------------------- */ + +/* + For security, the main invariant is that malloc/free/etc never + writes to a static address other than malloc_state, unless static + malloc_state itself has been corrupted, which cannot occur via + malloc (because of these checks). In essence this means that we + believe all pointers, sizes, maps etc held in malloc_state, but + check all of those linked or offsetted from other embedded data + structures. These checks are interspersed with main code in a way + that tends to minimize their run-time cost. + + When FOOTERS is defined, in addition to range checking, we also + verify footer fields of inuse chunks, which can be used guarantee + that the mstate controlling malloc/free is intact. This is a + streamlined version of the approach described by William Robertson + et al in "Run-time Detection of Heap-based Overflows" LISA'03 + http://www.usenix.org/events/lisa03/tech/robertson.html The footer + of an inuse chunk holds the xor of its mstate and a random seed, + that is checked upon calls to free() and realloc(). This is + (probabalistically) unguessable from outside the program, but can be + computed by any code successfully malloc'ing any chunk, so does not + itself provide protection against code that has already broken + security through some other means. Unlike Robertson et al, we + always dynamically check addresses of all offset chunks (previous, + next, etc). This turns out to be cheaper than relying on hashes. +*/ + +#if !INSECURE +/* Check if address a is at least as high as any from MORECORE or MMAP */ +#define ok_address(M, a) ((char*)(a) >= (M)->least_addr) +/* Check if address of next chunk n is higher than base chunk p */ +#define ok_next(p, n) ((char*)(p) < (char*)(n)) +/* Check if p has inuse status */ +#define ok_inuse(p) is_inuse(p) +/* Check if p has its pinuse bit on */ +#define ok_pinuse(p) pinuse(p) + +#else /* !INSECURE */ +#define ok_address(M, a) (1) +#define ok_next(b, n) (1) +#define ok_inuse(p) (1) +#define ok_pinuse(p) (1) +#endif /* !INSECURE */ + +#if (FOOTERS && !INSECURE) +/* Check if (alleged) mstate m has expected magic field */ +#define ok_magic(M) ((M)->magic == mparams.magic) +#else /* (FOOTERS && !INSECURE) */ +#define ok_magic(M) (1) +#endif /* (FOOTERS && !INSECURE) */ + +/* In gcc, use __builtin_expect to minimize impact of checks */ +#if !INSECURE +#if defined(__GNUC__) && __GNUC__ >= 3 +#define RTCHECK(e) __builtin_expect(e, 1) +#else /* GNUC */ +#define RTCHECK(e) (e) +#endif /* GNUC */ +#else /* !INSECURE */ +#define RTCHECK(e) (1) +#endif /* !INSECURE */ + +/* macros to set up inuse chunks with or without footers */ + +#if !FOOTERS + +#define mark_inuse_foot(M,p,s) + +/* Macros for setting head/foot of non-mmapped chunks */ + +/* Set cinuse bit and pinuse bit of next chunk */ +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set cinuse and pinuse of this chunk and pinuse of next chunk */ +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set size, cinuse and pinuse bit of this chunk */ +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT)) + +#else /* FOOTERS */ + +/* Set foot of inuse chunk to be xor of mstate and seed */ +#define mark_inuse_foot(M,p,s)\ + (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic)) + +#define get_mstate_for(p)\ + ((mstate)(((mchunkptr)((char*)(p) +\ + (chunksize(p))))->prev_foot ^ mparams.magic)) + +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \ + mark_inuse_foot(M,p,s)) + +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\ + mark_inuse_foot(M,p,s)) + +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + mark_inuse_foot(M, p, s)) + +#endif /* !FOOTERS */ + +/* ---------------------------- setting mparams -------------------------- */ + +/* Initialize mparams */ +static int init_mparams(void) { +#ifdef NEED_GLOBAL_LOCK_INIT + if (malloc_global_mutex_status <= 0) + init_malloc_global_mutex(); +#endif + + ACQUIRE_MALLOC_GLOBAL_LOCK(); + if (mparams.magic == 0) { + size_t magic; + size_t psize; + size_t gsize; + +#ifndef WIN32 + psize = malloc_getpagesize; + gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize); +#else /* WIN32 */ + { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + psize = system_info.dwPageSize; + gsize = ((DEFAULT_GRANULARITY != 0)? + DEFAULT_GRANULARITY : system_info.dwAllocationGranularity); + } +#endif /* WIN32 */ + + /* Sanity-check configuration: + size_t must be unsigned and as wide as pointer type. + ints must be at least 4 bytes. + alignment must be at least 8. + Alignment, min chunk size, and page size must all be powers of 2. + */ + if ((sizeof(size_t) != sizeof(char*)) || + (MAX_SIZE_T < MIN_CHUNK_SIZE) || + (sizeof(int) < 4) || + (MALLOC_ALIGNMENT < (size_t)8U) || + ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) || + ((MCHUNK_SIZE & (MCHUNK_SIZE-SIZE_T_ONE)) != 0) || + ((gsize & (gsize-SIZE_T_ONE)) != 0) || + ((psize & (psize-SIZE_T_ONE)) != 0)) + ABORT; + + mparams.granularity = gsize; + mparams.page_size = psize; + mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD; + mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD; +#if MORECORE_CONTIGUOUS + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT; +#else /* MORECORE_CONTIGUOUS */ + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT; +#endif /* MORECORE_CONTIGUOUS */ + +#if !ONLY_MSPACES + /* Set up lock for main malloc area */ + gm->mflags = mparams.default_mflags; + (void)INITIAL_LOCK(&gm->mutex); +#endif + + { +#if USE_DEV_RANDOM + int fd; + unsigned char buf[sizeof(size_t)]; + /* Try to use /dev/urandom, else fall back on using time */ + if ((fd = open("/dev/urandom", O_RDONLY)) >= 0 && + read(fd, buf, sizeof(buf)) == sizeof(buf)) { + magic = *((size_t *) buf); + close(fd); + } + else +#endif /* USE_DEV_RANDOM */ +#ifdef WIN32 + magic = (size_t)(GetTickCount() ^ (size_t)0x55555555U); +#elif defined(LACKS_TIME_H) + magic = (size_t)&magic ^ (size_t)0x55555555U; +#else + magic = (size_t)(time(0) ^ (size_t)0x55555555U); +#endif + magic |= (size_t)8U; /* ensure nonzero */ + magic &= ~(size_t)7U; /* improve chances of fault for bad values */ + /* Until memory modes commonly available, use volatile-write */ + (*(volatile size_t *)(&(mparams.magic))) = magic; + } + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + return 1; +} + +/* support for mallopt */ +static int change_mparam(int param_number, int value) { + size_t val; + ensure_initialization(); + val = (value == -1)? MAX_SIZE_T : (size_t)value; + switch(param_number) { + case M_TRIM_THRESHOLD: + mparams.trim_threshold = val; + return 1; + case M_GRANULARITY: + if (val >= mparams.page_size && ((val & (val-1)) == 0)) { + mparams.granularity = val; + return 1; + } + else + return 0; + case M_MMAP_THRESHOLD: + mparams.mmap_threshold = val; + return 1; + default: + return 0; + } +} + +#if DEBUG +/* ------------------------- Debugging Support --------------------------- */ + +/* Check properties of any chunk, whether free, inuse, mmapped etc */ +static void do_check_any_chunk(mstate m, mchunkptr p) { + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); +} + +/* Check properties of top chunk */ +static void do_check_top_chunk(mstate m, mchunkptr p) { + msegmentptr sp = segment_holding(m, (char*)p); + size_t sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */ + assert(sp != 0); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(sz == m->topsize); + assert(sz > 0); + assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE); + assert(pinuse(p)); + assert(!pinuse(chunk_plus_offset(p, sz))); +} + +/* Check properties of (inuse) mmapped chunks */ +static void do_check_mmapped_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + size_t len = (sz + (p->prev_foot) + MMAP_FOOT_PAD); + assert(is_mmapped(p)); + assert(use_mmap(m)); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(!is_small(sz)); + assert((len & (mparams.page_size-SIZE_T_ONE)) == 0); + assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD); + assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0); +} + +/* Check properties of inuse chunks */ +static void do_check_inuse_chunk(mstate m, mchunkptr p) { + do_check_any_chunk(m, p); + assert(is_inuse(p)); + assert(next_pinuse(p)); + /* If not pinuse and not mmapped, previous chunk has OK offset */ + assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p); + if (is_mmapped(p)) + do_check_mmapped_chunk(m, p); +} + +/* Check properties of free chunks */ +static void do_check_free_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + mchunkptr next = chunk_plus_offset(p, sz); + do_check_any_chunk(m, p); + assert(!is_inuse(p)); + assert(!next_pinuse(p)); + assert (!is_mmapped(p)); + if (p != m->dv && p != m->top) { + if (sz >= MIN_CHUNK_SIZE) { + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(is_aligned(chunk2mem(p))); + assert(next->prev_foot == sz); + assert(pinuse(p)); + assert (next == m->top || is_inuse(next)); + assert(p->fd->bk == p); + assert(p->bk->fd == p); + } + else /* markers are always of size SIZE_T_SIZE */ + assert(sz == SIZE_T_SIZE); + } +} + +/* Check properties of malloced chunks at the point they are malloced */ +static void do_check_malloced_chunk(mstate m, void* mem, size_t s) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + size_t sz = p->head & ~INUSE_BITS; + do_check_inuse_chunk(m, p); + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(sz >= MIN_CHUNK_SIZE); + assert(sz >= s); + /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */ + assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE)); + } +} + +/* Check a tree and its subtrees. */ +static void do_check_tree(mstate m, tchunkptr t) { + tchunkptr head = 0; + tchunkptr u = t; + bindex_t tindex = t->index; + size_t tsize = chunksize(t); + bindex_t idx; + compute_tree_index(tsize, idx); + assert(tindex == idx); + assert(tsize >= MIN_LARGE_SIZE); + assert(tsize >= minsize_for_tree_index(idx)); + assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1)))); + + do { /* traverse through chain of same-sized nodes */ + do_check_any_chunk(m, ((mchunkptr)u)); + assert(u->index == tindex); + assert(chunksize(u) == tsize); + assert(!is_inuse(u)); + assert(!next_pinuse(u)); + assert(u->fd->bk == u); + assert(u->bk->fd == u); + if (u->parent == 0) { + assert(u->child[0] == 0); + assert(u->child[1] == 0); + } + else { + assert(head == 0); /* only one node on chain has parent */ + head = u; + assert(u->parent != u); + assert (u->parent->child[0] == u || + u->parent->child[1] == u || + *((tbinptr*)(u->parent)) == u); + if (u->child[0] != 0) { + assert(u->child[0]->parent == u); + assert(u->child[0] != u); + do_check_tree(m, u->child[0]); + } + if (u->child[1] != 0) { + assert(u->child[1]->parent == u); + assert(u->child[1] != u); + do_check_tree(m, u->child[1]); + } + if (u->child[0] != 0 && u->child[1] != 0) { + assert(chunksize(u->child[0]) < chunksize(u->child[1])); + } + } + u = u->fd; + } while (u != t); + assert(head != 0); +} + +/* Check all the chunks in a treebin. */ +static void do_check_treebin(mstate m, bindex_t i) { + tbinptr* tb = treebin_at(m, i); + tchunkptr t = *tb; + int empty = (m->treemap & (1U << i)) == 0; + if (t == 0) + assert(empty); + if (!empty) + do_check_tree(m, t); +} + +/* Check all the chunks in a smallbin. */ +static void do_check_smallbin(mstate m, bindex_t i) { + sbinptr b = smallbin_at(m, i); + mchunkptr p = b->bk; + unsigned int empty = (m->smallmap & (1U << i)) == 0; + if (p == b) + assert(empty); + if (!empty) { + for (; p != b; p = p->bk) { + size_t size = chunksize(p); + mchunkptr q; + /* each chunk claims to be free */ + do_check_free_chunk(m, p); + /* chunk belongs in bin */ + assert(small_index(size) == i); + assert(p->bk == b || chunksize(p->bk) == chunksize(p)); + /* chunk is followed by an inuse chunk */ + q = next_chunk(p); + if (q->head != FENCEPOST_HEAD) + do_check_inuse_chunk(m, q); + } + } +} + +/* Find x in a bin. Used in other check functions. */ +static int bin_find(mstate m, mchunkptr x) { + size_t size = chunksize(x); + if (is_small(size)) { + bindex_t sidx = small_index(size); + sbinptr b = smallbin_at(m, sidx); + if (smallmap_is_marked(m, sidx)) { + mchunkptr p = b; + do { + if (p == x) + return 1; + } while ((p = p->fd) != b); + } + } + else { + bindex_t tidx; + compute_tree_index(size, tidx); + if (treemap_is_marked(m, tidx)) { + tchunkptr t = *treebin_at(m, tidx); + size_t sizebits = size << leftshift_for_tree_index(tidx); + while (t != 0 && chunksize(t) != size) { + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + sizebits <<= 1; + } + if (t != 0) { + tchunkptr u = t; + do { + if (u == (tchunkptr)x) + return 1; + } while ((u = u->fd) != t); + } + } + } + return 0; +} + +/* Traverse each chunk and check it; return total */ +static size_t traverse_and_check(mstate m) { + size_t sum = 0; + if (is_initialized(m)) { + msegmentptr s = &m->seg; + sum += m->topsize + TOP_FOOT_SIZE; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + mchunkptr lastq = 0; + assert(pinuse(q)); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + sum += chunksize(q); + if (is_inuse(q)) { + assert(!bin_find(m, q)); + do_check_inuse_chunk(m, q); + } + else { + assert(q == m->dv || bin_find(m, q)); + assert(lastq == 0 || is_inuse(lastq)); /* Not 2 consecutive free */ + do_check_free_chunk(m, q); + } + lastq = q; + q = next_chunk(q); + } + s = s->next; + } + } + return sum; +} + + +/* Check all properties of malloc_state. */ +static void do_check_malloc_state(mstate m) { + bindex_t i; + size_t total; + /* check bins */ + for (i = 0; i < NSMALLBINS; ++i) + do_check_smallbin(m, i); + for (i = 0; i < NTREEBINS; ++i) + do_check_treebin(m, i); + + if (m->dvsize != 0) { /* check dv chunk */ + do_check_any_chunk(m, m->dv); + assert(m->dvsize == chunksize(m->dv)); + assert(m->dvsize >= MIN_CHUNK_SIZE); + assert(bin_find(m, m->dv) == 0); + } + + if (m->top != 0) { /* check top chunk */ + do_check_top_chunk(m, m->top); + /*assert(m->topsize == chunksize(m->top)); redundant */ + assert(m->topsize > 0); + assert(bin_find(m, m->top) == 0); + } + + total = traverse_and_check(m); + assert(total <= m->footprint); + assert(m->footprint <= m->max_footprint); +} +#endif /* DEBUG */ + +/* ----------------------------- statistics ------------------------------ */ + +#if !NO_MALLINFO +static struct mallinfo internal_mallinfo(mstate m) { + struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + ensure_initialization(); + if (!PREACTION(m)) { + check_malloc_state(m); + if (is_initialized(m)) { + size_t nfree = SIZE_T_ONE; /* top always free */ + size_t mfree = m->topsize + TOP_FOOT_SIZE; + size_t sum = mfree; + msegmentptr s = &m->seg; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + size_t sz = chunksize(q); + sum += sz; + if (!is_inuse(q)) { + mfree += sz; + ++nfree; + } + q = next_chunk(q); + } + s = s->next; + } + + nm.arena = sum; + nm.ordblks = nfree; + nm.hblkhd = m->footprint - sum; + nm.usmblks = m->max_footprint; + nm.uordblks = m->footprint - mfree; + nm.fordblks = mfree; + nm.keepcost = m->topsize; + } + + POSTACTION(m); + } + return nm; +} +#endif /* !NO_MALLINFO */ + +#if !NO_MALLOC_STATS +static void internal_malloc_stats(mstate m) { + ensure_initialization(); + if (!PREACTION(m)) { + size_t maxfp = 0; + size_t fp = 0; + size_t used = 0; + check_malloc_state(m); + if (is_initialized(m)) { + msegmentptr s = &m->seg; + maxfp = m->max_footprint; + fp = m->footprint; + used = fp - (m->topsize + TOP_FOOT_SIZE); + + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + if (!is_inuse(q)) + used -= chunksize(q); + q = next_chunk(q); + } + s = s->next; + } + } + POSTACTION(m); /* drop lock */ + fprintf(stderr, "max system bytes = %10lu\n", (unsigned long)(maxfp)); + fprintf(stderr, "system bytes = %10lu\n", (unsigned long)(fp)); + fprintf(stderr, "in use bytes = %10lu\n", (unsigned long)(used)); + } +} +#endif /* NO_MALLOC_STATS */ + +/* ----------------------- Operations on smallbins ----------------------- */ + +/* + Various forms of linking and unlinking are defined as macros. Even + the ones for trees, which are very long but have very short typical + paths. This is ugly but reduces reliance on inlining support of + compilers. +*/ + +/* Link a free chunk into a smallbin */ +#define insert_small_chunk(M, P, S) {\ + bindex_t I = small_index(S);\ + mchunkptr B = smallbin_at(M, I);\ + mchunkptr F = B;\ + assert(S >= MIN_CHUNK_SIZE);\ + if (!smallmap_is_marked(M, I))\ + mark_smallmap(M, I);\ + else if (RTCHECK(ok_address(M, B->fd)))\ + F = B->fd;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + B->fd = P;\ + F->bk = P;\ + P->fd = F;\ + P->bk = B;\ +} + +/* Unlink a chunk from a smallbin */ +#define unlink_small_chunk(M, P, S) {\ + mchunkptr F = P->fd;\ + mchunkptr B = P->bk;\ + bindex_t I = small_index(S);\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (RTCHECK(F == smallbin_at(M,I) || (ok_address(M, F) && F->bk == P))) { \ + if (B == F) {\ + clear_smallmap(M, I);\ + }\ + else if (RTCHECK(B == smallbin_at(M,I) ||\ + (ok_address(M, B) && B->fd == P))) {\ + F->bk = B;\ + B->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + +/* Unlink the first chunk from a smallbin */ +#define unlink_first_small_chunk(M, B, P, I) {\ + mchunkptr F = P->fd;\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (B == F) {\ + clear_smallmap(M, I);\ + }\ + else if (RTCHECK(ok_address(M, F) && F->bk == P)) {\ + F->bk = B;\ + B->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + +/* Replace dv node, binning the old one */ +/* Used only when dvsize known to be small */ +#define replace_dv(M, P, S) {\ + size_t DVS = M->dvsize;\ + assert(is_small(DVS));\ + if (DVS != 0) {\ + mchunkptr DV = M->dv;\ + insert_small_chunk(M, DV, DVS);\ + }\ + M->dvsize = S;\ + M->dv = P;\ +} + +/* ------------------------- Operations on trees ------------------------- */ + +/* Insert chunk into tree */ +#define insert_large_chunk(M, X, S) {\ + tbinptr* H;\ + bindex_t I;\ + compute_tree_index(S, I);\ + H = treebin_at(M, I);\ + X->index = I;\ + X->child[0] = X->child[1] = 0;\ + if (!treemap_is_marked(M, I)) {\ + mark_treemap(M, I);\ + *H = X;\ + X->parent = (tchunkptr)H;\ + X->fd = X->bk = X;\ + }\ + else {\ + tchunkptr T = *H;\ + size_t K = S << leftshift_for_tree_index(I);\ + for (;;) {\ + if (chunksize(T) != S) {\ + tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\ + K <<= 1;\ + if (*C != 0)\ + T = *C;\ + else if (RTCHECK(ok_address(M, C))) {\ + *C = X;\ + X->parent = T;\ + X->fd = X->bk = X;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + else {\ + tchunkptr F = T->fd;\ + if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\ + T->fd = F->bk = X;\ + X->fd = F;\ + X->bk = T;\ + X->parent = 0;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + }\ + }\ +} + +/* + Unlink steps: + + 1. If x is a chained node, unlink it from its same-sized fd/bk links + and choose its bk node as its replacement. + 2. If x was the last node of its size, but not a leaf node, it must + be replaced with a leaf node (not merely one with an open left or + right), to make sure that lefts and rights of descendents + correspond properly to bit masks. We use the rightmost descendent + of x. We could use any other leaf, but this is easy to locate and + tends to counteract removal of leftmosts elsewhere, and so keeps + paths shorter than minimally guaranteed. This doesn't loop much + because on average a node in a tree is near the bottom. + 3. If x is the base of a chain (i.e., has parent links) relink + x's parent and children to x's replacement (or null if none). +*/ + +#define unlink_large_chunk(M, X) {\ + tchunkptr XP = X->parent;\ + tchunkptr R;\ + if (X->bk != X) {\ + tchunkptr F = X->fd;\ + R = X->bk;\ + if (RTCHECK(ok_address(M, F) && F->bk == X && R->fd == X)) {\ + F->bk = R;\ + R->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else {\ + tchunkptr* RP;\ + if (((R = *(RP = &(X->child[1]))) != 0) ||\ + ((R = *(RP = &(X->child[0]))) != 0)) {\ + tchunkptr* CP;\ + while ((*(CP = &(R->child[1])) != 0) ||\ + (*(CP = &(R->child[0])) != 0)) {\ + R = *(RP = CP);\ + }\ + if (RTCHECK(ok_address(M, RP)))\ + *RP = 0;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + }\ + if (XP != 0) {\ + tbinptr* H = treebin_at(M, X->index);\ + if (X == *H) {\ + if ((*H = R) == 0) \ + clear_treemap(M, X->index);\ + }\ + else if (RTCHECK(ok_address(M, XP))) {\ + if (XP->child[0] == X) \ + XP->child[0] = R;\ + else \ + XP->child[1] = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + if (R != 0) {\ + if (RTCHECK(ok_address(M, R))) {\ + tchunkptr C0, C1;\ + R->parent = XP;\ + if ((C0 = X->child[0]) != 0) {\ + if (RTCHECK(ok_address(M, C0))) {\ + R->child[0] = C0;\ + C0->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + if ((C1 = X->child[1]) != 0) {\ + if (RTCHECK(ok_address(M, C1))) {\ + R->child[1] = C1;\ + C1->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ +} + +/* Relays to large vs small bin operations */ + +#define insert_chunk(M, P, S)\ + if (is_small(S)) insert_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); } + +#define unlink_chunk(M, P, S)\ + if (is_small(S)) unlink_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); } + + +/* Relays to internal calls to malloc/free from realloc, memalign etc */ + +#if ONLY_MSPACES +#define internal_malloc(m, b) mspace_malloc(m, b) +#define internal_free(m, mem) mspace_free(m,mem); +#else /* ONLY_MSPACES */ +#if MSPACES +#define internal_malloc(m, b)\ + ((m == gm)? dlmalloc(b) : mspace_malloc(m, b)) +#define internal_free(m, mem)\ + if (m == gm) dlfree(mem); else mspace_free(m,mem); +#else /* MSPACES */ +#define internal_malloc(m, b) dlmalloc(b) +#define internal_free(m, mem) dlfree(mem) +#endif /* MSPACES */ +#endif /* ONLY_MSPACES */ + +/* ----------------------- Direct-mmapping chunks ----------------------- */ + +/* + Directly mmapped chunks are set up with an offset to the start of + the mmapped region stored in the prev_foot field of the chunk. This + allows reconstruction of the required argument to MUNMAP when freed, + and also allows adjustment of the returned chunk to meet alignment + requirements (especially in memalign). +*/ + +/* Malloc using mmap */ +static void* mmap_alloc(mstate m, size_t nb) { + size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + if (m->footprint_limit != 0) { + size_t fp = m->footprint + mmsize; + if (fp <= m->footprint || fp > m->footprint_limit) + return 0; + } + if (mmsize > nb) { /* Check for wrap around 0 */ + char* mm = (char*)(CALL_DIRECT_MMAP(mmsize)); + if (mm != CMFAIL) { + size_t offset = align_offset(chunk2mem(mm)); + size_t psize = mmsize - offset - MMAP_FOOT_PAD; + mchunkptr p = (mchunkptr)(mm + offset); + p->prev_foot = offset; + p->head = psize; + mark_inuse_foot(m, p, psize); + chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0; + + if (m->least_addr == 0 || mm < m->least_addr) + m->least_addr = mm; + if ((m->footprint += mmsize) > m->max_footprint) + m->max_footprint = m->footprint; + assert(is_aligned(chunk2mem(p))); + check_mmapped_chunk(m, p); + return chunk2mem(p); + } + } + return 0; +} + +/* Realloc using mmap */ +static mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb, int flags) { + size_t oldsize = chunksize(oldp); + flags = flags; /* placate people compiling -Wunused */ + if (is_small(nb)) /* Can't shrink mmap regions below small size */ + return 0; + /* Keep old chunk if big enough but not too big */ + if (oldsize >= nb + SIZE_T_SIZE && + (oldsize - nb) <= (mparams.granularity << 1)) + return oldp; + else { + size_t offset = oldp->prev_foot; + size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD; + size_t newmmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + char* cp = (char*)CALL_MREMAP((char*)oldp - offset, + oldmmsize, newmmsize, flags); + if (cp != CMFAIL) { + mchunkptr newp = (mchunkptr)(cp + offset); + size_t psize = newmmsize - offset - MMAP_FOOT_PAD; + newp->head = psize; + mark_inuse_foot(m, newp, psize); + chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0; + + if (cp < m->least_addr) + m->least_addr = cp; + if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint) + m->max_footprint = m->footprint; + check_mmapped_chunk(m, newp); + return newp; + } + } + return 0; +} + + +/* -------------------------- mspace management -------------------------- */ + +/* Initialize top chunk and its size */ +static void init_top(mstate m, mchunkptr p, size_t psize) { + /* Ensure alignment */ + size_t offset = align_offset(chunk2mem(p)); + p = (mchunkptr)((char*)p + offset); + psize -= offset; + + m->top = p; + m->topsize = psize; + p->head = psize | PINUSE_BIT; + /* set size of fake trailing chunk holding overhead space only once */ + chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE; + m->trim_check = mparams.trim_threshold; /* reset on each update */ +} + +/* Initialize bins for a new mstate that is otherwise zeroed out */ +static void init_bins(mstate m) { + /* Establish circular links for smallbins */ + bindex_t i; + for (i = 0; i < NSMALLBINS; ++i) { + sbinptr bin = smallbin_at(m,i); + bin->fd = bin->bk = bin; + } +} + +#if PROCEED_ON_ERROR + +/* default corruption action */ +static void reset_on_error(mstate m) { + int i; + ++malloc_corruption_error_count; + /* Reinitialize fields to forget about all memory */ + m->smallmap = m->treemap = 0; + m->dvsize = m->topsize = 0; + m->seg.base = 0; + m->seg.size = 0; + m->seg.next = 0; + m->top = m->dv = 0; + for (i = 0; i < NTREEBINS; ++i) + *treebin_at(m, i) = 0; + init_bins(m); +} +#endif /* PROCEED_ON_ERROR */ + +/* Allocate chunk and prepend remainder with chunk in successor base. */ +static void* prepend_alloc(mstate m, char* newbase, char* oldbase, + size_t nb) { + mchunkptr p = align_as_chunk(newbase); + mchunkptr oldfirst = align_as_chunk(oldbase); + size_t psize = (char*)oldfirst - (char*)p; + mchunkptr q = chunk_plus_offset(p, nb); + size_t qsize = psize - nb; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + + assert((char*)oldfirst > (char*)q); + assert(pinuse(oldfirst)); + assert(qsize >= MIN_CHUNK_SIZE); + + /* consolidate remainder with first chunk of old base */ + if (oldfirst == m->top) { + size_t tsize = m->topsize += qsize; + m->top = q; + q->head = tsize | PINUSE_BIT; + check_top_chunk(m, q); + } + else if (oldfirst == m->dv) { + size_t dsize = m->dvsize += qsize; + m->dv = q; + set_size_and_pinuse_of_free_chunk(q, dsize); + } + else { + if (!is_inuse(oldfirst)) { + size_t nsize = chunksize(oldfirst); + unlink_chunk(m, oldfirst, nsize); + oldfirst = chunk_plus_offset(oldfirst, nsize); + qsize += nsize; + } + set_free_with_pinuse(q, qsize, oldfirst); + insert_chunk(m, q, qsize); + check_free_chunk(m, q); + } + + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); +} + +/* Add a segment to hold a new noncontiguous region */ +static void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) { + /* Determine locations and sizes of segment, fenceposts, old top */ + char* old_top = (char*)m->top; + msegmentptr oldsp = segment_holding(m, old_top); + char* old_end = oldsp->base + oldsp->size; + size_t ssize = pad_request(sizeof(struct malloc_segment)); + char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + size_t offset = align_offset(chunk2mem(rawsp)); + char* asp = rawsp + offset; + char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp; + mchunkptr sp = (mchunkptr)csp; + msegmentptr ss = (msegmentptr)(chunk2mem(sp)); + mchunkptr tnext = chunk_plus_offset(sp, ssize); + mchunkptr p = tnext; + int nfences = 0; + + /* reset top to new space */ + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + + /* Set up segment record */ + assert(is_aligned(ss)); + set_size_and_pinuse_of_inuse_chunk(m, sp, ssize); + *ss = m->seg; /* Push current record */ + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmapped; + m->seg.next = ss; + + /* Insert trailing fenceposts */ + for (;;) { + mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE); + p->head = FENCEPOST_HEAD; + ++nfences; + if ((char*)(&(nextp->head)) < old_end) + p = nextp; + else + break; + } + assert(nfences >= 2); + + /* Insert the rest of old top into a bin as an ordinary free chunk */ + if (csp != old_top) { + mchunkptr q = (mchunkptr)old_top; + size_t psize = csp - old_top; + mchunkptr tn = chunk_plus_offset(q, psize); + set_free_with_pinuse(q, psize, tn); + insert_chunk(m, q, psize); + } + + check_top_chunk(m, m->top); +} + +/* -------------------------- System allocation -------------------------- */ + +/* Get memory from system using MORECORE or MMAP */ +static void* sys_alloc(mstate m, size_t nb) { + char* tbase = CMFAIL; + size_t tsize = 0; + flag_t mmap_flag = 0; + size_t asize; /* allocation size */ + + ensure_initialization(); + + /* Directly map large chunks, but only if already initialized */ + if (use_mmap(m) && nb >= mparams.mmap_threshold && m->topsize != 0) { + void* mem = mmap_alloc(m, nb); + if (mem != 0) + return mem; + } + + asize = granularity_align(nb + SYS_ALLOC_PADDING); + if (asize <= nb) + return 0; /* wraparound */ + if (m->footprint_limit != 0) { + size_t fp = m->footprint + asize; + if (fp <= m->footprint || fp > m->footprint_limit) + return 0; + } + + /* + Try getting memory in any of three ways (in most-preferred to + least-preferred order): + 1. A call to MORECORE that can normally contiguously extend memory. + (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or + or main space is mmapped or a previous contiguous call failed) + 2. A call to MMAP new space (disabled if not HAVE_MMAP). + Note that under the default settings, if MORECORE is unable to + fulfill a request, and HAVE_MMAP is true, then mmap is + used as a noncontiguous system allocator. This is a useful backup + strategy for systems with holes in address spaces -- in this case + sbrk cannot contiguously expand the heap, but mmap may be able to + find space. + 3. A call to MORECORE that cannot usually contiguously extend memory. + (disabled if not HAVE_MORECORE) + + In all cases, we need to request enough bytes from system to ensure + we can malloc nb bytes upon success, so pad with enough space for + top_foot, plus alignment-pad to make sure we don't lose bytes if + not on boundary, and round this up to a granularity unit. + */ + + if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) { + char* br = CMFAIL; + msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top); + ACQUIRE_MALLOC_GLOBAL_LOCK(); + + if (ss == 0) { /* First time through or recovery */ + char* base = (char*)CALL_MORECORE(0); + if (base != CMFAIL) { + size_t fp; + /* Adjust to end on a page boundary */ + if (!is_page_aligned(base)) + asize += (page_align((size_t)base) - (size_t)base); + fp = m->footprint + asize; /* recheck limits */ + if (asize > nb && asize < HALF_MAX_SIZE_T && + (m->footprint_limit == 0 || + (fp > m->footprint && fp <= m->footprint_limit)) && + (br = (char*)(CALL_MORECORE(asize))) == base) { + tbase = base; + tsize = asize; + } + } + } + else { + /* Subtract out existing available top space from MORECORE request. */ + asize = granularity_align(nb - m->topsize + SYS_ALLOC_PADDING); + /* Use mem here only if it did continuously extend old space */ + if (asize < HALF_MAX_SIZE_T && + (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) { + tbase = br; + tsize = asize; + } + } + + if (tbase == CMFAIL) { /* Cope with partial failure */ + if (br != CMFAIL) { /* Try to use/extend the space we did get */ + if (asize < HALF_MAX_SIZE_T && + asize < nb + SYS_ALLOC_PADDING) { + size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - asize); + if (esize < HALF_MAX_SIZE_T) { + char* end = (char*)CALL_MORECORE(esize); + if (end != CMFAIL) + asize += esize; + else { /* Can't use; try to release */ + (void) CALL_MORECORE(-asize); + br = CMFAIL; + } + } + } + } + if (br != CMFAIL) { /* Use the space we did get */ + tbase = br; + tsize = asize; + } + else + disable_contiguous(m); /* Don't try contiguous path in the future */ + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + } + + if (HAVE_MMAP && tbase == CMFAIL) { /* Try MMAP */ + char* mp = (char*)(CALL_MMAP(asize)); + if (mp != CMFAIL) { + tbase = mp; + tsize = asize; + mmap_flag = USE_MMAP_BIT; + } + } + + if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */ + if (asize < HALF_MAX_SIZE_T) { + char* br = CMFAIL; + char* end = CMFAIL; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + br = (char*)(CALL_MORECORE(asize)); + end = (char*)(CALL_MORECORE(0)); + RELEASE_MALLOC_GLOBAL_LOCK(); + if (br != CMFAIL && end != CMFAIL && br < end) { + size_t ssize = end - br; + if (ssize > nb + TOP_FOOT_SIZE) { + tbase = br; + tsize = ssize; + } + } + } + } + + if (tbase != CMFAIL) { + + if ((m->footprint += tsize) > m->max_footprint) + m->max_footprint = m->footprint; + + if (!is_initialized(m)) { /* first-time initialization */ + if (m->least_addr == 0 || tbase < m->least_addr) + m->least_addr = tbase; + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmap_flag; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + init_bins(m); +#if !ONLY_MSPACES + if (is_global(m)) + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + else +#endif + { + /* Offset top by embedded malloc_state */ + mchunkptr mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE); + } + } + + else { + /* Try to merge with an existing segment */ + msegmentptr sp = &m->seg; + /* Only consider most recent segment if traversal suppressed */ + while (sp != 0 && tbase != sp->base + sp->size) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & USE_MMAP_BIT) == mmap_flag && + segment_holds(sp, m->top)) { /* append */ + sp->size += tsize; + init_top(m, m->top, m->topsize + tsize); + } + else { + if (tbase < m->least_addr) + m->least_addr = tbase; + sp = &m->seg; + while (sp != 0 && sp->base != tbase + tsize) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & USE_MMAP_BIT) == mmap_flag) { + char* oldbase = sp->base; + sp->base = tbase; + sp->size += tsize; + return prepend_alloc(m, tbase, oldbase, nb); + } + else + add_segment(m, tbase, tsize, mmap_flag); + } + } + + if (nb < m->topsize) { /* Allocate from new or extended top space */ + size_t rsize = m->topsize -= nb; + mchunkptr p = m->top; + mchunkptr r = m->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + check_top_chunk(m, m->top); + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); + } + } + + MALLOC_FAILURE_ACTION; + return 0; +} + +/* ----------------------- system deallocation -------------------------- */ + +/* Unmap and unlink any mmapped segments that don't contain used chunks */ +static size_t release_unused_segments(mstate m) { + size_t released = 0; + int nsegs = 0; + msegmentptr pred = &m->seg; + msegmentptr sp = pred->next; + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + msegmentptr next = sp->next; + ++nsegs; + if (is_mmapped_segment(sp) && !is_extern_segment(sp)) { + mchunkptr p = align_as_chunk(base); + size_t psize = chunksize(p); + /* Can unmap if first chunk holds entire segment and not pinned */ + if (!is_inuse(p) && (char*)p + psize >= base + size - TOP_FOOT_SIZE) { + tchunkptr tp = (tchunkptr)p; + assert(segment_holds(sp, (char*)sp)); + if (p == m->dv) { + m->dv = 0; + m->dvsize = 0; + } + else { + unlink_large_chunk(m, tp); + } + if (CALL_MUNMAP(base, size) == 0) { + released += size; + m->footprint -= size; + /* unlink obsoleted record */ + sp = pred; + sp->next = next; + } + else { /* back out if cannot unmap */ + insert_large_chunk(m, tp, psize); + } + } + } + if (NO_SEGMENT_TRAVERSAL) /* scan only first segment */ + break; + pred = sp; + sp = next; + } + /* Reset check counter */ + m->release_checks = ((nsegs > MAX_RELEASE_CHECK_RATE)? + nsegs : MAX_RELEASE_CHECK_RATE); + return released; +} + +static int sys_trim(mstate m, size_t pad) { + size_t released = 0; + ensure_initialization(); + if (pad < MAX_REQUEST && is_initialized(m)) { + pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */ + + if (m->topsize > pad) { + /* Shrink top space in granularity-size units, keeping at least one */ + size_t unit = mparams.granularity; + size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit - + SIZE_T_ONE) * unit; + msegmentptr sp = segment_holding(m, (char*)m->top); + + if (!is_extern_segment(sp)) { + if (is_mmapped_segment(sp)) { + if (HAVE_MMAP && + sp->size >= extra && + !has_segment_link(m, sp)) { /* can't shrink if pinned */ + /* Prefer mremap, fall back to munmap */ + if ((CALL_MREMAP(sp->base, sp->size, (sp->size - extra), 0) != MFAIL) || + (CALL_MUNMAP(sp->base + (sp->size - extra), extra) == 0)) { + released = extra; + } + } + } + else if (HAVE_MORECORE) { + if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */ + extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + { + /* Make sure end of memory is where we last set it. */ + char* old_br = (char*)(CALL_MORECORE(0)); + if (old_br == sp->base + sp->size) { + char* rel_br = (char*)(CALL_MORECORE(-extra)); + char* new_br = (char*)(CALL_MORECORE(0)); + if (rel_br != CMFAIL && new_br < old_br) + released = old_br - new_br; + } + } + RELEASE_MALLOC_GLOBAL_LOCK(); + } + } + + if (released != 0) { + sp->size -= released; + m->footprint -= released; + init_top(m, m->top, m->topsize - released); + check_top_chunk(m, m->top); + } + } + + /* Unmap any unused mmapped segments */ + if (HAVE_MMAP) + released += release_unused_segments(m); + + /* On failure, disable autotrim to avoid repeated failed future calls */ + if (released == 0 && m->topsize > m->trim_check) + m->trim_check = MAX_SIZE_T; + } + + return (released != 0)? 1 : 0; +} + +/* Consolidate and bin a chunk. Differs from exported versions + of free mainly in that the chunk need not be marked as inuse. +*/ +static void dispose_chunk(mstate m, mchunkptr p, size_t psize) { + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + mchunkptr prev; + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + m->footprint -= psize; + return; + } + prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(m, prev))) { /* consolidate backward */ + if (p != m->dv) { + unlink_chunk(m, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + m->dvsize = psize; + set_free_with_pinuse(p, psize, next); + return; + } + } + else { + CORRUPTION_ERROR_ACTION(m); + return; + } + } + if (RTCHECK(ok_address(m, next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == m->top) { + size_t tsize = m->topsize += psize; + m->top = p; + p->head = tsize | PINUSE_BIT; + if (p == m->dv) { + m->dv = 0; + m->dvsize = 0; + } + return; + } + else if (next == m->dv) { + size_t dsize = m->dvsize += psize; + m->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + return; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(m, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == m->dv) { + m->dvsize = psize; + return; + } + } + } + else { + set_free_with_pinuse(p, psize, next); + } + insert_chunk(m, p, psize); + } + else { + CORRUPTION_ERROR_ACTION(m); + } +} + +/* ---------------------------- malloc --------------------------- */ + +/* allocate a large request from the best fitting chunk in a treebin */ +static void* tmalloc_large(mstate m, size_t nb) { + tchunkptr v = 0; + size_t rsize = -nb; /* Unsigned negation */ + tchunkptr t; + bindex_t idx; + compute_tree_index(nb, idx); + if ((t = *treebin_at(m, idx)) != 0) { + /* Traverse tree for this bin looking for node with size == nb */ + size_t sizebits = nb << leftshift_for_tree_index(idx); + tchunkptr rst = 0; /* The deepest untaken right subtree */ + for (;;) { + tchunkptr rt; + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + v = t; + if ((rsize = trem) == 0) + break; + } + rt = t->child[1]; + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + if (rt != 0 && rt != t) + rst = rt; + if (t == 0) { + t = rst; /* set t to least subtree holding sizes > nb */ + break; + } + sizebits <<= 1; + } + } + if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */ + binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap; + if (leftbits != 0) { + bindex_t i; + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + t = *treebin_at(m, i); + } + } + + while (t != 0) { /* find smallest of tree or subtree */ + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + t = leftmost_child(t); + } + + /* If dv is a better fit, return 0 so malloc will use it */ + if (v != 0 && rsize < (size_t)(m->dvsize - nb)) { + if (RTCHECK(ok_address(m, v))) { /* split */ + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + insert_chunk(m, r, rsize); + } + return chunk2mem(v); + } + } + CORRUPTION_ERROR_ACTION(m); + } + return 0; +} + +/* allocate a small request from the best fitting chunk in a treebin */ +static void* tmalloc_small(mstate m, size_t nb) { + tchunkptr t, v; + size_t rsize; + bindex_t i; + binmap_t leastbit = least_bit(m->treemap); + compute_bit2idx(leastbit, i); + v = t = *treebin_at(m, i); + rsize = chunksize(t) - nb; + + while ((t = leftmost_child(t)) != 0) { + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + } + + if (RTCHECK(ok_address(m, v))) { + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(m, r, rsize); + } + return chunk2mem(v); + } + } + + CORRUPTION_ERROR_ACTION(m); + return 0; +} + +#if !ONLY_MSPACES + +void* dlmalloc(size_t bytes) { + /* + Basic algorithm: + If a small request (< 256 bytes minus per-chunk overhead): + 1. If one exists, use a remainderless chunk in associated smallbin. + (Remainderless means that there are too few excess bytes to + represent as a chunk.) + 2. If it is big enough, use the dv chunk, which is normally the + chunk adjacent to the one used for the most recent small request. + 3. If one exists, split the smallest available chunk in a bin, + saving remainder in dv. + 4. If it is big enough, use the top chunk. + 5. If available, get memory from system and use it + Otherwise, for a large request: + 1. Find the smallest available binned chunk that fits, and use it + if it is better fitting than dv chunk, splitting if necessary. + 2. If better fitting than any binned chunk, use the dv chunk. + 3. If it is big enough, use the top chunk. + 4. If request size >= mmap threshold, try to directly mmap this chunk. + 5. If available, get memory from system and use it + + The ugly goto's here ensure that postaction occurs along all paths. + */ + +#if USE_LOCKS + ensure_initialization(); /* initialize in sys_alloc if not using locks */ +#endif + + if (!PREACTION(gm)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = gm->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(gm, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(gm, b, p, idx); + set_inuse_and_pinuse(gm, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb > gm->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(gm, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(gm, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(gm, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(gm, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + + if (nb <= gm->dvsize) { + size_t rsize = gm->dvsize - nb; + mchunkptr p = gm->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = gm->dv = chunk_plus_offset(p, nb); + gm->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + } + else { /* exhaust dv */ + size_t dvs = gm->dvsize; + gm->dvsize = 0; + gm->dv = 0; + set_inuse_and_pinuse(gm, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb < gm->topsize) { /* Split top */ + size_t rsize = gm->topsize -= nb; + mchunkptr p = gm->top; + mchunkptr r = gm->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + mem = chunk2mem(p); + check_top_chunk(gm, gm->top); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + mem = sys_alloc(gm, nb); + + postaction: + POSTACTION(gm); + return mem; + } + + return 0; +} + +/* ---------------------------- free --------------------------- */ + +void dlfree(void* mem) { + /* + Consolidate freed chunks with preceeding or succeeding bordering + free chunks, if they exist, and then place in a bin. Intermixed + with special cases for top, dv, mmapped chunks, and usage errors. + */ + + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } +#else /* FOOTERS */ +#define fm gm +#endif /* FOOTERS */ + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +#if !FOOTERS +#undef fm +#endif /* FOOTERS */ +} + +void* dlcalloc(size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = dlmalloc(req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +#endif /* !ONLY_MSPACES */ + +/* ------------ Internal support for realloc, memalign, etc -------------- */ + +/* Try to realloc; only in-place unless can_move true */ +static mchunkptr try_realloc_chunk(mstate m, mchunkptr p, size_t nb, + int can_move) { + mchunkptr newp = 0; + size_t oldsize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, oldsize); + if (RTCHECK(ok_address(m, p) && ok_inuse(p) && + ok_next(p, next) && ok_pinuse(next))) { + if (is_mmapped(p)) { + newp = mmap_resize(m, p, nb, can_move); + } + else if (oldsize >= nb) { /* already big enough */ + size_t rsize = oldsize - nb; + if (rsize >= MIN_CHUNK_SIZE) { /* split off remainder */ + mchunkptr r = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, r, rsize); + dispose_chunk(m, r, rsize); + } + newp = p; + } + else if (next == m->top) { /* extend into top */ + if (oldsize + m->topsize > nb) { + size_t newsize = oldsize + m->topsize; + size_t newtopsize = newsize - nb; + mchunkptr newtop = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + newtop->head = newtopsize |PINUSE_BIT; + m->top = newtop; + m->topsize = newtopsize; + newp = p; + } + } + else if (next == m->dv) { /* extend into dv */ + size_t dvs = m->dvsize; + if (oldsize + dvs >= nb) { + size_t dsize = oldsize + dvs - nb; + if (dsize >= MIN_CHUNK_SIZE) { + mchunkptr r = chunk_plus_offset(p, nb); + mchunkptr n = chunk_plus_offset(r, dsize); + set_inuse(m, p, nb); + set_size_and_pinuse_of_free_chunk(r, dsize); + clear_pinuse(n); + m->dvsize = dsize; + m->dv = r; + } + else { /* exhaust dv */ + size_t newsize = oldsize + dvs; + set_inuse(m, p, newsize); + m->dvsize = 0; + m->dv = 0; + } + newp = p; + } + } + else if (!cinuse(next)) { /* extend into next free chunk */ + size_t nextsize = chunksize(next); + if (oldsize + nextsize >= nb) { + size_t rsize = oldsize + nextsize - nb; + unlink_chunk(m, next, nextsize); + if (rsize < MIN_CHUNK_SIZE) { + size_t newsize = oldsize + nextsize; + set_inuse(m, p, newsize); + } + else { + mchunkptr r = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, r, rsize); + dispose_chunk(m, r, rsize); + } + newp = p; + } + } + } + else { + USAGE_ERROR_ACTION(m, oldmem); + } + return newp; +} + +static void* internal_memalign(mstate m, size_t alignment, size_t bytes) { + void* mem = 0; + if (alignment < MIN_CHUNK_SIZE) /* must be at least a minimum chunk size */ + alignment = MIN_CHUNK_SIZE; + if ((alignment & (alignment-SIZE_T_ONE)) != 0) {/* Ensure a power of 2 */ + size_t a = MALLOC_ALIGNMENT << 1; + while (a < alignment) a <<= 1; + alignment = a; + } + if (bytes >= MAX_REQUEST - alignment) { + if (m != 0) { /* Test isn't needed but avoids compiler warning */ + MALLOC_FAILURE_ACTION; + } + } + else { + size_t nb = request2size(bytes); + size_t req = nb + alignment + MIN_CHUNK_SIZE - CHUNK_OVERHEAD; + mem = internal_malloc(m, req); + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (PREACTION(m)) + return 0; + if ((((size_t)(mem)) & (alignment - 1)) != 0) { /* misaligned */ + /* + Find an aligned spot inside chunk. Since we need to give + back leading space in a chunk of at least MIN_CHUNK_SIZE, if + the first calculation places us at a spot with less than + MIN_CHUNK_SIZE leader, we can move to the next aligned spot. + We've allocated enough total room so that this is always + possible. + */ + char* br = (char*)mem2chunk((size_t)(((size_t)((char*)mem + alignment - + SIZE_T_ONE)) & + -alignment)); + char* pos = ((size_t)(br - (char*)(p)) >= MIN_CHUNK_SIZE)? + br : br+alignment; + mchunkptr newp = (mchunkptr)pos; + size_t leadsize = pos - (char*)(p); + size_t newsize = chunksize(p) - leadsize; + + if (is_mmapped(p)) { /* For mmapped chunks, just adjust offset */ + newp->prev_foot = p->prev_foot + leadsize; + newp->head = newsize; + } + else { /* Otherwise, give back leader, use the rest */ + set_inuse(m, newp, newsize); + set_inuse(m, p, leadsize); + dispose_chunk(m, p, leadsize); + } + p = newp; + } + + /* Give back spare room at the end */ + if (!is_mmapped(p)) { + size_t size = chunksize(p); + if (size > nb + MIN_CHUNK_SIZE) { + size_t remainder_size = size - nb; + mchunkptr remainder = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, remainder, remainder_size); + dispose_chunk(m, remainder, remainder_size); + } + } + + mem = chunk2mem(p); + assert (chunksize(p) >= nb); + assert(((size_t)mem & (alignment - 1)) == 0); + check_inuse_chunk(m, p); + POSTACTION(m); + } + } + return mem; +} + +/* + Common support for independent_X routines, handling + all of the combinations that can result. + The opts arg has: + bit 0 set if all elements are same size (using sizes[0]) + bit 1 set if elements should be zeroed +*/ +static void** ialloc(mstate m, + size_t n_elements, + size_t* sizes, + int opts, + void* chunks[]) { + + size_t element_size; /* chunksize of each element, if all same */ + size_t contents_size; /* total size of elements */ + size_t array_size; /* request size of pointer array */ + void* mem; /* malloced aggregate space */ + mchunkptr p; /* corresponding chunk */ + size_t remainder_size; /* remaining bytes while splitting */ + void** marray; /* either "chunks" or malloced ptr array */ + mchunkptr array_chunk; /* chunk for malloced ptr array */ + flag_t was_enabled; /* to disable mmap */ + size_t size; + size_t i; + + ensure_initialization(); + /* compute array length, if needed */ + if (chunks != 0) { + if (n_elements == 0) + return chunks; /* nothing to do */ + marray = chunks; + array_size = 0; + } + else { + /* if empty req, must still return chunk representing empty array */ + if (n_elements == 0) + return (void**)internal_malloc(m, 0); + marray = 0; + array_size = request2size(n_elements * (sizeof(void*))); + } + + /* compute total element size */ + if (opts & 0x1) { /* all-same-size */ + element_size = request2size(*sizes); + contents_size = n_elements * element_size; + } + else { /* add up all the sizes */ + element_size = 0; + contents_size = 0; + for (i = 0; i != n_elements; ++i) + contents_size += request2size(sizes[i]); + } + + size = contents_size + array_size; + + /* + Allocate the aggregate chunk. First disable direct-mmapping so + malloc won't use it, since we would not be able to later + free/realloc space internal to a segregated mmap region. + */ + was_enabled = use_mmap(m); + disable_mmap(m); + mem = internal_malloc(m, size - CHUNK_OVERHEAD); + if (was_enabled) + enable_mmap(m); + if (mem == 0) + return 0; + + if (PREACTION(m)) return 0; + p = mem2chunk(mem); + remainder_size = chunksize(p); + + assert(!is_mmapped(p)); + + if (opts & 0x2) { /* optionally clear the elements */ + memset((size_t*)mem, 0, remainder_size - SIZE_T_SIZE - array_size); + } + + /* If not provided, allocate the pointer array as final part of chunk */ + if (marray == 0) { + size_t array_chunk_size; + array_chunk = chunk_plus_offset(p, contents_size); + array_chunk_size = remainder_size - contents_size; + marray = (void**) (chunk2mem(array_chunk)); + set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size); + remainder_size = contents_size; + } + + /* split out elements */ + for (i = 0; ; ++i) { + marray[i] = chunk2mem(p); + if (i != n_elements-1) { + if (element_size != 0) + size = element_size; + else + size = request2size(sizes[i]); + remainder_size -= size; + set_size_and_pinuse_of_inuse_chunk(m, p, size); + p = chunk_plus_offset(p, size); + } + else { /* the final element absorbs any overallocation slop */ + set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size); + break; + } + } + +#if DEBUG + if (marray != chunks) { + /* final element must have exactly exhausted chunk */ + if (element_size != 0) { + assert(remainder_size == element_size); + } + else { + assert(remainder_size == request2size(sizes[i])); + } + check_inuse_chunk(m, mem2chunk(marray)); + } + for (i = 0; i != n_elements; ++i) + check_inuse_chunk(m, mem2chunk(marray[i])); + +#endif /* DEBUG */ + + POSTACTION(m); + return marray; +} + +/* Try to free all pointers in the given array. + Note: this could be made faster, by delaying consolidation, + at the price of disabling some user integrity checks, We + still optimize some consolidations by combining adjacent + chunks before freeing, which will occur often if allocated + with ialloc or the array is sorted. +*/ +static size_t internal_bulk_free(mstate m, void* array[], size_t nelem) { + size_t unfreed = 0; + if (!PREACTION(m)) { + void** a; + void** fence = &(array[nelem]); + for (a = array; a != fence; ++a) { + void* mem = *a; + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + size_t psize = chunksize(p); +#if FOOTERS + if (get_mstate_for(p) != m) { + ++unfreed; + continue; + } +#endif + check_inuse_chunk(m, p); + *a = 0; + if (RTCHECK(ok_address(m, p) && ok_inuse(p))) { + void ** b = a + 1; /* try to merge with next chunk */ + mchunkptr next = next_chunk(p); + if (b != fence && *b == chunk2mem(next)) { + size_t newsize = chunksize(next) + psize; + set_inuse(m, p, newsize); + *b = chunk2mem(p); + } + else + dispose_chunk(m, p, psize); + } + else { + CORRUPTION_ERROR_ACTION(m); + break; + } + } + } + if (should_trim(m, m->topsize)) + sys_trim(m, 0); + POSTACTION(m); + } + return unfreed; +} + +/* Traversal */ +#if MALLOC_INSPECT_ALL +static void internal_inspect_all(mstate m, + void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg) { + if (is_initialized(m)) { + mchunkptr top = m->top; + msegmentptr s; + for (s = &m->seg; s != 0; s = s->next) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && q->head != FENCEPOST_HEAD) { + mchunkptr next = next_chunk(q); + size_t sz = chunksize(q); + size_t used; + void* start; + if (is_inuse(q)) { + used = sz - CHUNK_OVERHEAD; /* must not be mmapped */ + start = chunk2mem(q); + } + else { + used = 0; + if (is_small(sz)) { /* offset by possible bookkeeping */ + start = (void*)((char*)q + sizeof(malloc_chunk)); + } + else { + start = (void*)((char*)q + sizeof(malloc_tree_chunk)); + } + } + if (start < (void*)next) /* skip if all space is bookkeeping */ + handler(start, next, used, arg); + if (q == top) + break; + q = next; + } + } + } +} +#endif /* MALLOC_INSPECT_ALL */ + +/* ------------------ Exported realloc, memalign, etc -------------------- */ + +#if !ONLY_MSPACES + +void* dlrealloc(void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem == 0) { + mem = dlmalloc(bytes); + } + else if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } +#ifdef REALLOC_ZERO_BYTES_FREES + else if (bytes == 0) { + dlfree(oldmem); + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = gm; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 1); + POSTACTION(m); + if (newp != 0) { + check_inuse_chunk(m, newp); + mem = chunk2mem(newp); + } + else { + mem = internal_malloc(m, bytes); + if (mem != 0) { + size_t oc = chunksize(oldp) - overhead_for(oldp); + memcpy(mem, oldmem, (oc < bytes)? oc : bytes); + internal_free(m, oldmem); + } + } + } + } + return mem; +} + +void* dlrealloc_in_place(void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem != 0) { + if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = gm; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0); + POSTACTION(m); + if (newp == oldp) { + check_inuse_chunk(m, newp); + mem = oldmem; + } + } + } + } + return mem; +} + +void* dlmemalign(size_t alignment, size_t bytes) { + if (alignment <= MALLOC_ALIGNMENT) { + return dlmalloc(bytes); + } + return internal_memalign(gm, alignment, bytes); +} + +int dlposix_memalign(void** pp, size_t alignment, size_t bytes) { + void* mem = 0; + if (alignment == MALLOC_ALIGNMENT) + mem = dlmalloc(bytes); + else { + size_t d = alignment / sizeof(void*); + size_t r = alignment % sizeof(void*); + if (r != 0 || d == 0 || (d & (d-SIZE_T_ONE)) != 0) + return EINVAL; + else if (bytes >= MAX_REQUEST - alignment) { + if (alignment < MIN_CHUNK_SIZE) + alignment = MIN_CHUNK_SIZE; + mem = internal_memalign(gm, alignment, bytes); + } + } + if (mem == 0) + return ENOMEM; + else { + *pp = mem; + return 0; + } +} + +void* dlvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, bytes); +} + +void* dlpvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE)); +} + +void** dlindependent_calloc(size_t n_elements, size_t elem_size, + void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + return ialloc(gm, n_elements, &sz, 3, chunks); +} + +void** dlindependent_comalloc(size_t n_elements, size_t sizes[], + void* chunks[]) { + return ialloc(gm, n_elements, sizes, 0, chunks); +} + +size_t dlbulk_free(void* array[], size_t nelem) { + return internal_bulk_free(gm, array, nelem); +} + +#if MALLOC_INSPECT_ALL +void dlmalloc_inspect_all(void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg) { + ensure_initialization(); + if (!PREACTION(gm)) { + internal_inspect_all(gm, handler, arg); + POSTACTION(gm); + } +} +#endif /* MALLOC_INSPECT_ALL */ + +int dlmalloc_trim(size_t pad) { + int result = 0; + ensure_initialization(); + if (!PREACTION(gm)) { + result = sys_trim(gm, pad); + POSTACTION(gm); + } + return result; +} + +size_t dlmalloc_footprint(void) { + return gm->footprint; +} + +size_t dlmalloc_max_footprint(void) { + return gm->max_footprint; +} + +size_t dlmalloc_footprint_limit(void) { + size_t maf = gm->footprint_limit; + return maf == 0 ? MAX_SIZE_T : maf; +} + +size_t dlmalloc_set_footprint_limit(size_t bytes) { + size_t result; /* invert sense of 0 */ + if (bytes == 0) + result = granularity_align(1); /* Use minimal size */ + if (bytes == MAX_SIZE_T) + result = 0; /* disable */ + else + result = granularity_align(bytes); + return gm->footprint_limit = result; +} + +#if !NO_MALLINFO +struct mallinfo dlmallinfo(void) { + return internal_mallinfo(gm); +} +#endif /* NO_MALLINFO */ + +#if !NO_MALLOC_STATS +void dlmalloc_stats() { + internal_malloc_stats(gm); +} +#endif /* NO_MALLOC_STATS */ + +int dlmallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +size_t dlmalloc_usable_size(void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (is_inuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +#endif /* !ONLY_MSPACES */ + +/* ----------------------------- user mspaces ---------------------------- */ + +#if MSPACES + +static mstate init_user_mstate(char* tbase, size_t tsize) { + size_t msize = pad_request(sizeof(struct malloc_state)); + mchunkptr mn; + mchunkptr msp = align_as_chunk(tbase); + mstate m = (mstate)(chunk2mem(msp)); + memset(m, 0, msize); + (void)INITIAL_LOCK(&m->mutex); + msp->head = (msize|INUSE_BITS); + m->seg.base = m->least_addr = tbase; + m->seg.size = m->footprint = m->max_footprint = tsize; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + m->mflags = mparams.default_mflags; + m->extp = 0; + m->exts = 0; + disable_contiguous(m); + init_bins(m); + mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) - TOP_FOOT_SIZE); + check_top_chunk(m, m->top); + return m; +} + +mspace create_mspace(size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + size_t rs = ((capacity == 0)? mparams.granularity : + (capacity + TOP_FOOT_SIZE + msize)); + size_t tsize = granularity_align(rs); + char* tbase = (char*)(CALL_MMAP(tsize)); + if (tbase != CMFAIL) { + m = init_user_mstate(tbase, tsize); + m->seg.sflags = USE_MMAP_BIT; + set_lock(m, locked); + } + } + return (mspace)m; +} + +mspace create_mspace_with_base(void* base, size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity > msize + TOP_FOOT_SIZE && + capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + m = init_user_mstate((char*)base, capacity); + m->seg.sflags = EXTERN_BIT; + set_lock(m, locked); + } + return (mspace)m; +} + +int mspace_track_large_chunks(mspace msp, int enable) { + int ret = 0; + mstate ms = (mstate)msp; + if (!PREACTION(ms)) { + if (!use_mmap(ms)) + ret = 1; + if (!enable) + enable_mmap(ms); + else + disable_mmap(ms); + POSTACTION(ms); + } + return ret; +} + +size_t destroy_mspace(mspace msp) { + size_t freed = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + msegmentptr sp = &ms->seg; + (void)DESTROY_LOCK(&ms->mutex); /* destroy before unmapped */ + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + flag_t flag = sp->sflags; + sp = sp->next; + if ((flag & USE_MMAP_BIT) && !(flag & EXTERN_BIT) && + CALL_MUNMAP(base, size) == 0) + freed += size; + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return freed; +} + +/* + mspace versions of routines are near-clones of the global + versions. This is not so nice but better than the alternatives. +*/ + +void* mspace_malloc(mspace msp, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (!PREACTION(ms)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = ms->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(ms, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(ms, b, p, idx); + set_inuse_and_pinuse(ms, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb > ms->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(ms, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(ms, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(ms, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(ms, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + + if (nb <= ms->dvsize) { + size_t rsize = ms->dvsize - nb; + mchunkptr p = ms->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = ms->dv = chunk_plus_offset(p, nb); + ms->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + } + else { /* exhaust dv */ + size_t dvs = ms->dvsize; + ms->dvsize = 0; + ms->dv = 0; + set_inuse_and_pinuse(ms, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb < ms->topsize) { /* Split top */ + size_t rsize = ms->topsize -= nb; + mchunkptr p = ms->top; + mchunkptr r = ms->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + mem = chunk2mem(p); + check_top_chunk(ms, ms->top); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + mem = sys_alloc(ms, nb); + + postaction: + POSTACTION(ms); + return mem; + } + + return 0; +} + +void mspace_free(mspace msp, void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + msp = msp; /* placate people compiling -Wunused */ +#else /* FOOTERS */ + mstate fm = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +} + +void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = internal_malloc(ms, req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +void* mspace_realloc(mspace msp, void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem == 0) { + mem = mspace_malloc(msp, bytes); + } + else if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } +#ifdef REALLOC_ZERO_BYTES_FREES + else if (bytes == 0) { + mspace_free(msp, oldmem); + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = (mstate)msp; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 1); + POSTACTION(m); + if (newp != 0) { + check_inuse_chunk(m, newp); + mem = chunk2mem(newp); + } + else { + mem = mspace_malloc(m, bytes); + if (mem != 0) { + size_t oc = chunksize(oldp) - overhead_for(oldp); + memcpy(mem, oldmem, (oc < bytes)? oc : bytes); + mspace_free(m, oldmem); + } + } + } + } + return mem; +} + +void* mspace_realloc_in_place(mspace msp, void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem != 0) { + if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = (mstate)msp; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + msp = msp; /* placate people compiling -Wunused */ + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0); + POSTACTION(m); + if (newp == oldp) { + check_inuse_chunk(m, newp); + mem = oldmem; + } + } + } + } + return mem; +} + +void* mspace_memalign(mspace msp, size_t alignment, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (alignment <= MALLOC_ALIGNMENT) + return mspace_malloc(msp, bytes); + return internal_memalign(ms, alignment, bytes); +} + +void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, &sz, 3, chunks); +} + +void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, sizes, 0, chunks); +} + +size_t mspace_bulk_free(mspace msp, void* array[], size_t nelem) { + return internal_bulk_free((mstate)msp, array, nelem); +} + +#if MALLOC_INSPECT_ALL +void mspace_inspect_all(mspace msp, + void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (!PREACTION(ms)) { + internal_inspect_all(ms, handler, arg); + POSTACTION(ms); + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} +#endif /* MALLOC_INSPECT_ALL */ + +int mspace_trim(mspace msp, size_t pad) { + int result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (!PREACTION(ms)) { + result = sys_trim(ms, pad); + POSTACTION(ms); + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +#if !NO_MALLOC_STATS +void mspace_malloc_stats(mspace msp) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + internal_malloc_stats(ms); + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} +#endif /* NO_MALLOC_STATS */ + +size_t mspace_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +size_t mspace_max_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->max_footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +size_t mspace_footprint_limit(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + size_t maf = ms->footprint_limit; + result = (maf == 0) ? MAX_SIZE_T : maf; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +size_t mspace_set_footprint_limit(mspace msp, size_t bytes) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (bytes == 0) + result = granularity_align(1); /* Use minimal size */ + if (bytes == MAX_SIZE_T) + result = 0; /* disable */ + else + result = granularity_align(bytes); + ms->footprint_limit = result; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +#if !NO_MALLINFO +struct mallinfo mspace_mallinfo(mspace msp) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + } + return internal_mallinfo(ms); +} +#endif /* NO_MALLINFO */ + +size_t mspace_usable_size(void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (is_inuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +int mspace_mallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +#endif /* MSPACES */ + + +/* -------------------- Alternative MORECORE functions ------------------- */ + +/* + Guidelines for creating a custom version of MORECORE: + + * For best performance, MORECORE should allocate in multiples of pagesize. + * MORECORE may allocate more memory than requested. (Or even less, + but this will usually result in a malloc failure.) + * MORECORE must not allocate memory when given argument zero, but + instead return one past the end address of memory from previous + nonzero call. + * For best performance, consecutive calls to MORECORE with positive + arguments should return increasing addresses, indicating that + space has been contiguously extended. + * Even though consecutive calls to MORECORE need not return contiguous + addresses, it must be OK for malloc'ed chunks to span multiple + regions in those cases where they do happen to be contiguous. + * MORECORE need not handle negative arguments -- it may instead + just return MFAIL when given negative arguments. + Negative arguments are always multiples of pagesize. MORECORE + must not misinterpret negative args as large positive unsigned + args. You can suppress all such calls from even occurring by defining + MORECORE_CANNOT_TRIM, + + As an example alternative MORECORE, here is a custom allocator + kindly contributed for pre-OSX macOS. It uses virtually but not + necessarily physically contiguous non-paged memory (locked in, + present and won't get swapped out). You can use it by uncommenting + this section, adding some #includes, and setting up the appropriate + defines above: + + #define MORECORE osMoreCore + + There is also a shutdown routine that should somehow be called for + cleanup upon program exit. + + #define MAX_POOL_ENTRIES 100 + #define MINIMUM_MORECORE_SIZE (64 * 1024U) + static int next_os_pool; + void *our_os_pools[MAX_POOL_ENTRIES]; + + void *osMoreCore(int size) + { + void *ptr = 0; + static void *sbrk_top = 0; + + if (size > 0) + { + if (size < MINIMUM_MORECORE_SIZE) + size = MINIMUM_MORECORE_SIZE; + if (CurrentExecutionLevel() == kTaskLevel) + ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0); + if (ptr == 0) + { + return (void *) MFAIL; + } + // save ptrs so they can be freed during cleanup + our_os_pools[next_os_pool] = ptr; + next_os_pool++; + ptr = (void *) ((((size_t) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK); + sbrk_top = (char *) ptr + size; + return ptr; + } + else if (size < 0) + { + // we don't currently support shrink behavior + return (void *) MFAIL; + } + else + { + return sbrk_top; + } + } + + // cleanup any allocated memory pools + // called as last thing before shutting down driver + + void osCleanupMem(void) + { + void **ptr; + + for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++) + if (*ptr) + { + PoolDeallocate(*ptr); + *ptr = 0; + } + } + +*/ + + +/* ----------------------------------------------------------------------- +History: + v2.8.5 Sun May 22 10:26:02 2011 Doug Lea (dl at gee) + * Always perform unlink checks unless INSECURE + * Add posix_memalign. + * Improve realloc to expand in more cases; expose realloc_in_place. + Thanks to Peter Buhr for the suggestion. + * Add footprint_limit, inspect_all, bulk_free. Thanks + to Barry Hayes and others for the suggestions. + * Internal refactorings to avoid calls while holding locks + * Use non-reentrant locks by default. Thanks to Roland McGrath + for the suggestion. + * Small fixes to mspace_destroy, reset_on_error. + * Various configuration extensions/changes. Thanks + to all who contributed these. + + V2.8.4a Thu Apr 28 14:39:43 2011 (dl at gee.cs.oswego.edu) + * Update Creative Commons URL + + V2.8.4 Wed May 27 09:56:23 2009 Doug Lea (dl at gee) + * Use zeros instead of prev foot for is_mmapped + * Add mspace_track_large_chunks; thanks to Jean Brouwers + * Fix set_inuse in internal_realloc; thanks to Jean Brouwers + * Fix insufficient sys_alloc padding when using 16byte alignment + * Fix bad error check in mspace_footprint + * Adaptations for ptmalloc; thanks to Wolfram Gloger. + * Reentrant spin locks; thanks to Earl Chew and others + * Win32 improvements; thanks to Niall Douglas and Earl Chew + * Add NO_SEGMENT_TRAVERSAL and MAX_RELEASE_CHECK_RATE options + * Extension hook in malloc_state + * Various small adjustments to reduce warnings on some compilers + * Various configuration extensions/changes for more platforms. Thanks + to all who contributed these. + + V2.8.3 Thu Sep 22 11:16:32 2005 Doug Lea (dl at gee) + * Add max_footprint functions + * Ensure all appropriate literals are size_t + * Fix conditional compilation problem for some #define settings + * Avoid concatenating segments with the one provided + in create_mspace_with_base + * Rename some variables to avoid compiler shadowing warnings + * Use explicit lock initialization. + * Better handling of sbrk interference. + * Simplify and fix segment insertion, trimming and mspace_destroy + * Reinstate REALLOC_ZERO_BYTES_FREES option from 2.7.x + * Thanks especially to Dennis Flanagan for help on these. + + V2.8.2 Sun Jun 12 16:01:10 2005 Doug Lea (dl at gee) + * Fix memalign brace error. + + V2.8.1 Wed Jun 8 16:11:46 2005 Doug Lea (dl at gee) + * Fix improper #endif nesting in C++ + * Add explicit casts needed for C++ + + V2.8.0 Mon May 30 14:09:02 2005 Doug Lea (dl at gee) + * Use trees for large bins + * Support mspaces + * Use segments to unify sbrk-based and mmap-based system allocation, + removing need for emulation on most platforms without sbrk. + * Default safety checks + * Optional footer checks. Thanks to William Robertson for the idea. + * Internal code refactoring + * Incorporate suggestions and platform-specific changes. + Thanks to Dennis Flanagan, Colin Plumb, Niall Douglas, + Aaron Bachmann, Emery Berger, and others. + * Speed up non-fastbin processing enough to remove fastbins. + * Remove useless cfree() to avoid conflicts with other apps. + * Remove internal memcpy, memset. Compilers handle builtins better. + * Remove some options that no one ever used and rename others. + + V2.7.2 Sat Aug 17 09:07:30 2002 Doug Lea (dl at gee) + * Fix malloc_state bitmap array misdeclaration + + V2.7.1 Thu Jul 25 10:58:03 2002 Doug Lea (dl at gee) + * Allow tuning of FIRST_SORTED_BIN_SIZE + * Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte. + * Better detection and support for non-contiguousness of MORECORE. + Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger + * Bypass most of malloc if no frees. Thanks To Emery Berger. + * Fix freeing of old top non-contiguous chunk im sysmalloc. + * Raised default trim and map thresholds to 256K. + * Fix mmap-related #defines. Thanks to Lubos Lunak. + * Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield. + * Branch-free bin calculation + * Default trim and mmap thresholds now 256K. + + V2.7.0 Sun Mar 11 14:14:06 2001 Doug Lea (dl at gee) + * Introduce independent_comalloc and independent_calloc. + Thanks to Michael Pachos for motivation and help. + * Make optional .h file available + * Allow > 2GB requests on 32bit systems. + * new WIN32 sbrk, mmap, munmap, lock code from . + Thanks also to Andreas Mueller , + and Anonymous. + * Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for + helping test this.) + * memalign: check alignment arg + * realloc: don't try to shift chunks backwards, since this + leads to more fragmentation in some programs and doesn't + seem to help in any others. + * Collect all cases in malloc requiring system memory into sysmalloc + * Use mmap as backup to sbrk + * Place all internal state in malloc_state + * Introduce fastbins (although similar to 2.5.1) + * Many minor tunings and cosmetic improvements + * Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK + * Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS + Thanks to Tony E. Bennett and others. + * Include errno.h to support default failure action. + + V2.6.6 Sun Dec 5 07:42:19 1999 Doug Lea (dl at gee) + * return null for negative arguments + * Added Several WIN32 cleanups from Martin C. Fong + * Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h' + (e.g. WIN32 platforms) + * Cleanup header file inclusion for WIN32 platforms + * Cleanup code to avoid Microsoft Visual C++ compiler complaints + * Add 'USE_DL_PREFIX' to quickly allow co-existence with existing + memory allocation routines + * Set 'malloc_getpagesize' for WIN32 platforms (needs more work) + * Use 'assert' rather than 'ASSERT' in WIN32 code to conform to + usage of 'assert' in non-WIN32 code + * Improve WIN32 'sbrk()' emulation's 'findRegion()' routine to + avoid infinite loop + * Always call 'fREe()' rather than 'free()' + + V2.6.5 Wed Jun 17 15:57:31 1998 Doug Lea (dl at gee) + * Fixed ordering problem with boundary-stamping + + V2.6.3 Sun May 19 08:17:58 1996 Doug Lea (dl at gee) + * Added pvalloc, as recommended by H.J. Liu + * Added 64bit pointer support mainly from Wolfram Gloger + * Added anonymously donated WIN32 sbrk emulation + * Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen + * malloc_extend_top: fix mask error that caused wastage after + foreign sbrks + * Add linux mremap support code from HJ Liu + + V2.6.2 Tue Dec 5 06:52:55 1995 Doug Lea (dl at gee) + * Integrated most documentation with the code. + * Add support for mmap, with help from + Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Use last_remainder in more cases. + * Pack bins using idea from colin@nyx10.cs.du.edu + * Use ordered bins instead of best-fit threshhold + * Eliminate block-local decls to simplify tracing and debugging. + * Support another case of realloc via move into top + * Fix error occuring when initial sbrk_base not word-aligned. + * Rely on page size for units instead of SBRK_UNIT to + avoid surprises about sbrk alignment conventions. + * Add mallinfo, mallopt. Thanks to Raymond Nijssen + (raymond@es.ele.tue.nl) for the suggestion. + * Add `pad' argument to malloc_trim and top_pad mallopt parameter. + * More precautions for cases where other routines call sbrk, + courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Added macros etc., allowing use in linux libc from + H.J. Lu (hjl@gnu.ai.mit.edu) + * Inverted this history list + + V2.6.1 Sat Dec 2 14:10:57 1995 Doug Lea (dl at gee) + * Re-tuned and fixed to behave more nicely with V2.6.0 changes. + * Removed all preallocation code since under current scheme + the work required to undo bad preallocations exceeds + the work saved in good cases for most test programs. + * No longer use return list or unconsolidated bins since + no scheme using them consistently outperforms those that don't + given above changes. + * Use best fit for very large chunks to prevent some worst-cases. + * Added some support for debugging + + V2.6.0 Sat Nov 4 07:05:23 1995 Doug Lea (dl at gee) + * Removed footers when chunks are in use. Thanks to + Paul Wilson (wilson@cs.texas.edu) for the suggestion. + + V2.5.4 Wed Nov 1 07:54:51 1995 Doug Lea (dl at gee) + * Added malloc_trim, with help from Wolfram Gloger + (wmglo@Dent.MED.Uni-Muenchen.DE). + + V2.5.3 Tue Apr 26 10:16:01 1994 Doug Lea (dl at g) + + V2.5.2 Tue Apr 5 16:20:40 1994 Doug Lea (dl at g) + * realloc: try to expand in both directions + * malloc: swap order of clean-bin strategy; + * realloc: only conditionally expand backwards + * Try not to scavenge used bins + * Use bin counts as a guide to preallocation + * Occasionally bin return list chunks in first scan + * Add a few optimizations from colin@nyx10.cs.du.edu + + V2.5.1 Sat Aug 14 15:40:43 1993 Doug Lea (dl at g) + * faster bin computation & slightly different binning + * merged all consolidations to one part of malloc proper + (eliminating old malloc_find_space & malloc_clean_bin) + * Scan 2 returns chunks (not just 1) + * Propagate failure in realloc if malloc returns 0 + * Add stuff to allow compilation on non-ANSI compilers + from kpv@research.att.com + + V2.5 Sat Aug 7 07:41:59 1993 Doug Lea (dl at g.oswego.edu) + * removed potential for odd address access in prev_chunk + * removed dependency on getpagesize.h + * misc cosmetics and a bit more internal documentation + * anticosmetics: mangled names in macros to evade debugger strangeness + * tested on sparc, hp-700, dec-mips, rs6000 + with gcc & native cc (hp, dec only) allowing + Detlefs & Zorn comparison study (in SIGPLAN Notices.) + + Trial version Fri Aug 28 13:14:29 1992 Doug Lea (dl at g.oswego.edu) + * Based loosely on libg++-1.2X malloc. (It retains some of the overall + structure of old version, but most details differ.) + +*/ + diff --git a/luprex/ext/eris-master/FILEFORMAT b/luprex/ext/eris-master/FILEFORMAT new file mode 100644 index 00000000..e32d0102 --- /dev/null +++ b/luprex/ext/eris-master/FILEFORMAT @@ -0,0 +1,239 @@ +/* +The format is largely equal to that of Pluto, with some changes to reduce +file size, and where necessary due to changes in Lua's architecture. + +Pseudo-C is used to express the file format. Padding is assumed to be +nonexistent. The keyword "one_of" is used to express a concept similar to +"union", except that its size is the size of the actual datatype chosen. Thus, +objects which contain, directly or indirectly, a one_of, may vary in size. The +keyword "if" is used to express the fact that the following block may or may +not be present, usually indicated by the type of a previous "one_of". +*/ + +struct PersistedData { + Header header; /* The header used for basic validation. */ + Object rootobj; /* The root object that was persisted. */ +}; + +struct Header { + char header[4] = "ERIS"; /* Header signature for rudimentary validation */ + uint8_t sizeof_number; /* sizeof(lua_Number) to check type compatibility */ + lua_Number test; /* -1.234567890 to check representation compatibility */ + uint8_t sizeof_int; /* sizeof(int) in persisted data */ + uint8_t sizeof_size_t; /* sizeof(size_t) in persisted data */ + /* Note that the last two fields determine the size of the int and size_t + * fields in the following definitions. We write each value in the native + * "size" and check for truncation when reading, if necessary. */ +}; + +struct Object { + one_of { + int type; /* if the value is one of LUA_TXXX or ERIS_PERMANENT */ + Reference r; /* otherwise */ + /* Note that the types LUA_TNIL, LUA_TBOOLEAN, LUA_TNUMBER and + * LUA_TLIGHTUSERDATA will never be "referenced", but always be written + * directly. */ + } + if (type) { + RealObject o; /* if we have a type, not a reference */ + /* If the object is not primitive (see list above) we remember it and + * increment the reference counter, and point any future occurrences of + * it to this one via a reference (see above, Reference r). */ + } +}; + +struct Reference { + int ref; /* The index the object was registered with */ +}; + +struct RealObject { + one_of { + uint8_t b; /* If type == LUA_TBOOLEAN */ + size_t l; /* If type == LUA_TLIGHTUSERDATA */ + Number n; /* If type == LUA_TNUMBER */ + String s; /* If type == LUA_TSTRING */ + Table t; /* If type == LUA_TTABLE */ + Closure f; /* If type == LUA_TFUNCTION */ + Userdata u; /* If type == LUA_TUSERDATA */ + Thread th; /* If type == LUA_TTHREAD */ + Proto p; /* If type == LUA_TPROTO (from lobject.h) */ + UpVal uv; /* If type == LUA_TUPVAL (from lobject.h) */ + PermKey pk; /* if type == ERIS_PERMANENT */ + }; +}; + +struct Number { + if(sizeof(lua_Number) == sizeof(uint32_t)) { + uint32_t rep; /* binary representation of the number, stored as + * integer to re-use endian-agnosticism */ + } + else if (sizeof(lua_Number) == sizeof(uint64_t)) { + uint64_t rep; /* binary representation of the number, stored as + * integer to re-use endian-agnosticism */ + } + else { + /* unsupported float type -- error if asserts are enabled */ + } +}; + +struct String { + size_t length; /* The length of the string */ + char str[length]; /* The actual string (not always null terminated) */ +}; + +struct Table { + uint8_t isSpecial; /* 1 if SP is used; 0 otherwise */ + one_of { + Object c; /* if isspecial == 1; closure to refill the table */ + LiteralTable t; /* if isspecial == 0; literal table info */ + }; +}; + +struct LiteralTable { + Pair p[]; /* key/value pairs */ + Object nil = nil; /* Nil reference to terminate */ + Object metatable; /* The metatable (nil for none, otherwise LUA_TTABLE) */ +}; + +struct Pair { + Object key; /* never nil, since that indicates the end */ + Object value; /* never nil, since the entry wouldn't exist then */ +}; + +struct Userdata { + uint8_t isSpecial; /* 1 for special persistence, 0 for literal */ + one_of { + Object c; /* if isspecial == 1; closure to recreate the udata */ + LiteralUserdata lu; /* if is_special is 0 */ + }; +}; + +struct LiteralUserdata { + size_t length; /* Size of the data */ + char data[length]; /* The actual data */ + Object metatable; /* The metatable (nil for none, otherwise LUA_TTABLE) */ +}; + +struct Closure { + uint8_t isCClosure; /* 1 if the closure is a C closure; 0 otherwise */ + uint8_t nups; /* Number of upvalues the function uses */ + one_of { + CClosure ccl; /* if isCClosure == 1 */ + LClosure lcl; /* if isCClosure == 0; it's a Lua closure */ + }; +}; + +struct CClosure { + Object f; /* The actual C function. Must be available via the + * permanents table on persist and unpersist. */ + Object upvals[Closure.nups]; /* All upvalues */ + /* Note that here the upvalues are the actual objects, i.e. these are not + * of type LUA_TUPVAL, since C closures' upalues are always closed. */ +}; + +struct LClosure { + Object proto; /* The proto this function uses */ + Object upvals[Closure.nups]; /* All upvalues */ +}; + +struct UpVal { + Object obj; /* The object this upval refers to; we proxy it with + * the LUA_TUPVAL type to keep shared upvalues intact */ +} + +struct Proto { + int linedefined; /* Start of line range */ + int lastlinedefined; /* End of line range */ + uint8_t numparams; /* Number of parameters taken */ + uint8_t is_vararg; /* 1 if function accepts varargs, 0 otherwise */ + uint8_t maxstacksize; /* Size of stack reserved for the function */ + + int sizecode; /* Number of instructions in code */ + Instruction code[sizecode]; /* The proto's code */ + + int sizek; /* Number of constants referenced */ + Object k[sizek]; /* Constants referenced */ + + int sizep; /* Number of inner Protos referenced */ + Object p[sizep]; /* Inner Protos referenced */ + + int sizeupvalues; /* Number of upvalues used */ + Upvaldesc upvalues[sizeupvalues]; /* The locations of the upvalues */ + + uint8_t debug; /* 1 if debug data is present; 0 otherwise */ + if (debug) { + Object source; /* The source code string */ + + int sizelineinfo; /* Number of opcode-line mappings */ + int lineinfo[sizelineinfo]; /* opcode-line mappings */ + + int sizelocvars; /* Number of local variable names */ + LocVar[sizelocvars]; /* Local variable names */ + + Object upvalnames[sizeupvalues]; /* Upvalue names */ + } +}; + +struct Upvaldesc { + uint8_t instack; /* whether it is in stack */ + uint8_t idx; /* index of upvalue (in stack or in outer function's list) */ +}; + +struct LocVar { + int startpc; /* Point where variable is active */ + int endpc; /* Point where variable is dead */ + Object name; /* Name of the local variable */ +}; + +struct Thread { + int stacksize; /* The overall size of the stack filled with objects, + * including all stack frames. */ + size_t top; /* top = L->top - L->stack; */ + Object stack[stacksize]; /* All stack values, bottom up */ + + uint8_t status; /* current thread status (ok, yield) */ + size_t errfunc; /* current error handling function (stack index) */ + + CallInfo ci[]; /* The CallInfo stack, starting with base_ci */ + if (status == LUA_YIELD) { + size_t extra; /* value of thread->ci->extra, which is the original + * value of thread->ci->func */ + } + + OpenUpval openupvals[]; /* Upvalues to open */ +}; + +struct CallInfo { + size_t func; /* func = ci->func - thread->stack */ + size_t top; /* top = ci->top - thread->stack */ + int16_t nresults; /* expected number of results from this function */ + uint8_t callstatus; + if (callstatus & CIST_YPCALL) { + size_t extra; /* the stack level of the function being pcalled */ + } + if (callstatus & CIST_LUA) { + size_t base; /* base = ci->u.l.base - thread-stack */ + size_t savedpc; /* savedpc = ci->u.l.savedpc - ci_func(ci)->p->code */ + } + else { + uint8_t status; + if (callstatus & (CIST_YPCALL | CIST_YIELDED)) { + int ctx; /* context info. in case of yields */ + Object k; /* C function, callback for resuming */ + } + } + uint8_t hasNext; /* 1 if there's another CI to read; 0 otherwise */ +}; + +struct OpenUpval { + size_t idx; /* stack index of the value + 1; 0 if end of list */ + Object upval; /* The upvalue */ +}; + +struct PermKey { + uint8_t type; /* The actual LUA_TXXX of the original value. */ + Object key; /* The value to use as a key when unpersisting. */ + /* Note that we store the type of the original value (replaced by the + * permanent table value used as a key when unpersisting) to ensure the + * value in the permanents table when unpersisting has the correct type. */ +}; diff --git a/luprex/ext/eris-master/LICENSE b/luprex/ext/eris-master/LICENSE new file mode 100644 index 00000000..c010040e --- /dev/null +++ b/luprex/ext/eris-master/LICENSE @@ -0,0 +1,45 @@ +Eris +Copyright (c) 2013-2015 Florian Nücke. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +----------------------------------------------------------------------------- + +Lua 5.2.3 from http://www.lua.org/ with patches http://www.lua.org/bugs.html +slightly modified to access internal library functions for persistence. + +Copyright © 1994–2013 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/luprex/ext/eris-master/Makefile b/luprex/ext/eris-master/Makefile new file mode 100644 index 00000000..2738e93f --- /dev/null +++ b/luprex/ext/eris-master/Makefile @@ -0,0 +1,113 @@ +# Makefile for installing Lua +# See doc/readme.html for installation and customization instructions. + +# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= + +# Your platform. See PLATS for possible values. +PLAT= none + +# Where to install. The installation starts in the src and doc directories, +# so take care if INSTALL_TOP is not an absolute path. See the local target. +# You may want to make INSTALL_LMOD and INSTALL_CMOD consistent with +# LUA_ROOT, LUA_LDIR, and LUA_CDIR in luaconf.h. +INSTALL_TOP= $(DESTDIR)/usr/local +INSTALL_BIN= $(INSTALL_TOP)/bin +INSTALL_INC= $(INSTALL_TOP)/include +INSTALL_LIB= $(INSTALL_TOP)/lib +INSTALL_MAN= $(INSTALL_TOP)/man/man1 +INSTALL_LMOD= $(INSTALL_TOP)/share/lua/$V +INSTALL_CMOD= $(INSTALL_TOP)/lib/lua/$V + +# How to install. If your install program does not support "-p", then +# you may have to run ranlib on the installed liblua.a. +INSTALL= install -p +INSTALL_EXEC= $(INSTALL) -m 0755 +INSTALL_DATA= $(INSTALL) -m 0644 +# +# If you don't have "install" you can use "cp" instead. +# INSTALL= cp -p +# INSTALL_EXEC= $(INSTALL) +# INSTALL_DATA= $(INSTALL) + +# Other utilities. +MKDIR= mkdir -p +RM= rm -f + +# == END OF USER SETTINGS -- NO NEED TO CHANGE ANYTHING BELOW THIS LINE ======= + +# Convenience platforms targets. +PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris + +# What to install. +TO_BIN= lua luac +TO_INC= lua.h luaconf.h lualib.h lauxlib.h lua.hpp eris.h +TO_LIB= liblua.a +TO_MAN= lua.1 luac.1 + +# Lua version and release. +V= 5.2 +R= $V.4 + +# Targets start here. +all: $(PLAT) + +$(PLATS) clean: + cd src && $(MAKE) $@ + +test: dummy + src/lua -v + "test/persist" test/persist.lua test.eris && "test/unpersist" test/unpersist.lua test.eris + +install: dummy + cd src && $(MKDIR) $(INSTALL_BIN) $(INSTALL_INC) $(INSTALL_LIB) $(INSTALL_MAN) $(INSTALL_LMOD) $(INSTALL_CMOD) + cd src && $(INSTALL_EXEC) $(TO_BIN) $(INSTALL_BIN) + cd src && $(INSTALL_DATA) $(TO_INC) $(INSTALL_INC) + cd src && $(INSTALL_DATA) $(TO_LIB) $(INSTALL_LIB) + +uninstall: + cd src && cd $(INSTALL_BIN) && $(RM) $(TO_BIN) + cd src && cd $(INSTALL_INC) && $(RM) $(TO_INC) + cd src && cd $(INSTALL_LIB) && $(RM) $(TO_LIB) + +local: + $(MAKE) install INSTALL_TOP=../install + +none: + @echo "Please do 'make PLATFORM' where PLATFORM is one of these:" + @echo " $(PLATS)" + @echo "See doc/readme.html for complete instructions." + +# make may get confused with test/ and install/ +dummy: + +# echo config parameters +echo: + @cd src && $(MAKE) -s echo + @echo "PLAT= $(PLAT)" + @echo "V= $V" + @echo "R= $R" + @echo "TO_BIN= $(TO_BIN)" + @echo "TO_INC= $(TO_INC)" + @echo "TO_LIB= $(TO_LIB)" + @echo "TO_MAN= $(TO_MAN)" + @echo "INSTALL_TOP= $(INSTALL_TOP)" + @echo "INSTALL_BIN= $(INSTALL_BIN)" + @echo "INSTALL_INC= $(INSTALL_INC)" + @echo "INSTALL_LIB= $(INSTALL_LIB)" + @echo "INSTALL_MAN= $(INSTALL_MAN)" + @echo "INSTALL_LMOD= $(INSTALL_LMOD)" + @echo "INSTALL_CMOD= $(INSTALL_CMOD)" + @echo "INSTALL_EXEC= $(INSTALL_EXEC)" + @echo "INSTALL_DATA= $(INSTALL_DATA)" + +# echo pkg-config data +pc: + @echo "version=$R" + @echo "prefix=$(INSTALL_TOP)" + @echo "libdir=$(INSTALL_LIB)" + @echo "includedir=$(INSTALL_INC)" + +# list targets that do not create files (but not all makes understand .PHONY) +.PHONY: all $(PLATS) clean test install local none dummy echo pecho lecho + +# (end of Makefile) diff --git a/luprex/ext/eris-master/README.md b/luprex/ext/eris-master/README.md new file mode 100644 index 00000000..f57795df --- /dev/null +++ b/luprex/ext/eris-master/README.md @@ -0,0 +1,202 @@ +Eris - Heavy Duty Persistence for Lua +===================================== + +First things first: this is essentially a rewrite of [Pluto][], targeting Lua 5.2. If you need something like this for Lua 5.1 I strongly suggest you have a look at that. In fact, have a look at it anyway, because the documentation isn't half bad, and a lot of it applies to Eris, too. + +Although my implementation is strongly influenced by Pluto, in my humble opinion there are still sufficient changes in the architecture that a new name is appropriate. So, to stick with the theme, I named it Eris. Note that all those changes are internal. The API is almost left untouched to make a transition easier. + +Eris can serialize almost anything you can have in a Lua VM and can unserialize it again at a later point, even in a different VM. This in particular includes yielded coroutines, which is very handy for saving long running, multi-session scripting systems, for example in games: just persist the state into the save file and unpersist it upon load, and the scripts won't even know the game was quit inbetween. + +Eris currently comes with Lua 5.2.3. It should also build with 5.2.2. It may build with earlier 5.2.x versions, but I have not tried it. It *won't* work with other Lua implementations, such as LuaJIT, because Eris works directly on some internal structures of vanilla Lua and uses non-API macros and functions. + +I think Eris can be considered stable. I have distributed it in a piece of software that has been used by many users on varying platforms, and there were no problems that had to do with Eris at all. There are also the testcases that came with Pluto - extended with some Lua 5.2 specific ones, such as yielded `pcall`s - run through successfully. + +Installation +============ + +Think of Eris as a custom "distribution" of Lua. Compile and install Eris like you would Lua. You will get a shared and/or static library that will contain both Lua and Eris. The version string has been adjusted to reflect this: `Lua+Eris 5.2.3`. + +Usage +===== + +C API +----- + +Like Pluto, Eris offers two functions to persist to or read from an arbitrary source. Eris uses the Lua typedefs `lua_Writer` and `lua_Reader` for this purpose, the replacements of `lua_Chunkwriter` and `lua_Chunkreader` used by Pluto. + +* `void eris_dump(lua_State *L, lua_Writer writer, void *ud);` `[-0, +0, e]` + This provides an interface to Eris' persist functionality for writing in an arbitrary way, using a writer. When called, the stack in `L` must look like this: + 1. `perms:table` + 2. `value:any` + + That is, `perms` must be a table at stack position 1. This table is used as the permanent object table. `value` is the value that should be persisted. It can be any persistable Lua value. 'writer' is the writer function used to store all data, `ud` is passed to the writer function whenever it is called. + + This function is equivalent to `pluto_persist`. + +* `void eris_undump(lua_State *L, lua_Reader reader, void *ud);` `[-0, +1, e]` + This provides an interface to Eris' unpersist functionality for reading in an arbitrary way, using a reader. When called, the stack in `L` must look like this: + 1. `perms:table` + + That is, `perms` must be a table at stack position 1. This table is used as the permanent object table. This must hold the inverse mapping present in the permanent object table used when persisting. `reader` is the reader function used to read all data, `ud` is passed to the reader function whenever it is called. The result of the operation will be pushed onto the stack. + + This function is equivalent to `pluto_unpersist`. The function will only check the stack's top. Like Pluto, Eris uses Lua's own ZIO to handle buffered reading. Note that unlike with Pluto, the value can in fact be `nil`. + +In addition to these, Eris also offers two more convenient functions, if you simply wish to persist an object to a string. These behave like the functions exposed to Lua. + +* `void eris_persist(lua_State *L, int perms, int value);` `[-0, +1, e]` + It expects the permanent object table at the specified index `perms` and the value to persist at the specified index `value`. It will push the resulting binary string onto the stack on success. + +* `void eris_unpersist(lua_State *L, int perms, int value);` `[-0, +1, e]` + It expects the permanent object table at the specified index `perms` and the binary string containing persisted data at the specified index `value`. It will push the resulting value onto the stack on success. + +The subtle name change from Pluto was done because Lua's own dump/undump works with a writer/reader, so it felt more consistent this way. + +Finally, you can change some of Eris' behavior via settings that can be queried and adjusted via the following functions: + +* `void eris_get_setting(lua_State *L, const char *name);` `[-0, +1, e]` + This will push the current value of the setting with the specified name onto the stack. If there is no setting with the specified name an error will be thrown. Available settings are: + - `debug`, a boolean value indicating whether to persist debug information of function prototypes, such as line numbers and variable names. The default is `true`. + - `maxrec`, an unsigned integer value indicating the maximum complexity of objects: the number of "persist" recursions, for example for nested tables. If an object has a higher complexity we throw an error. This can be used to avoid segfaults due to deep recursion when handling user-provided data. The default is `10000`. + - `path`, a boolean value indicating whether to generate the "path" in the object to display it when an error occurs. **Important:** this adds significant overhead and should only be used to debug errors. The default is `false`. + - `spio`, a boolean value indicating whether to pass IO objects along as light userdata to special persistence functions. When enabled, this will pass the `lua_Writer` and its associated `void*` in addition to the original object when persisting, and the `ZIO*` when unpersisting. The default is `false`. + - `spkey`, a string that is the name of the field in the metatable of tables and userdata used to control persistence (see Special Persistence). The default is `__persist`. + +* `void eris_set_setting(lua_State *L, const char *name, int value);` `[-0, +0, e]` + This will change the value of the setting with the specified name to the value at the specified stack index `value`. The names are the same as described in `eris_get_setting()`. If the specified is invalid, an error will be thrown. If the value at the specified index has the wrong type for the specified setting, an error will be thrown. Specify a `nil` value to reset the setting to its default value. + +Note that all settings are stored in the registry of the provided Lua state, so they will be shared across all coroutines. + +Lua +--- + +You can either load Eris as a table onto the Lua stack via `luaopen_eris()` or just call `luaL_openlibs()` which will open it in addition to all other libraries and register the global table `eris`. As with Pluto, there are the two functions for persisting/unpersisting in this table. In addition, there is a function that allows configuring Eris on the fly: + +* `string eris.persist([perms,] value)` + This persists the provided value and returns the resulting data as a binary string. Note that passing the permanent object table is optional. If only one argument is given Eris assumes it's the object to persist, and the permanent object table is empty. If given, `perms` must be a table. `value` can be any type. + +* `any eris.unpersist([perms,] value)` + This unpersists the provided binary string that resulted from an earlier call to `eris.persist()` and return the unpersisted value. Note that passing the permanent object table is optional. If only one argument is given Eris assumes it's the data representing a persisted object, and the permanent object table is empty. If given, `perms` must be a table. `value` must be a string. + +* `[value] eris.settings(name[, value])` + This allows changing Eris' settings for the Lua VM the script runs in (Eris stores its settings in the registry). For available settings see the documentation of the corresponding C functions above. If this function is called with only a name it will return the current value of that setting. If it is called with a value also, it will set the setting to that value and return nothing. + +Concepts +======== + +Persistence +----------- + +Eris will persist most objects out of the box. This includes basic value types (nil, boolean, light userdata (as the literal pointer value), number) as well as strings, tables, closures and threads. For tables and userdata, metatables are also persisted. Tables, functions and threads are persisted recursively, i.e. each value referenced in them (key/value, upvalue, stack) will in turn be persisted. + +C closures are only supported if the underlying C function is in the permanent object table. Userdata is only supported if the special persistence metafield is present (see below). + +Like Pluto, Eris will store objects only once and reference this first occasion whenever the object should be persisted again. This ensures that references are kept across persisting an object, and has the nice side effect of keeping the size of the output small. + +Permanent Objects +----------------- + +Whenever Eris tries to persist any non-basic value (i.e. *not* nil, boolean, light userdata or number) it will check if the value it is about to persist is a key in the table of permanent objects. If it is, Eris will write the associated value instead (the "permanent key"). When unpersisting, the opposite process takes place: for each value persisted this way Eris will look for the permanent key in the permanent object table, and use the value associated with that key. + +For example, this allows persisting values that reference API functions, i.e. C functions provided by the native host. These cannot be persisted literally, because what it boils down to is that those are function pointers, and these may change over multiple program runs or between machines. + +Note that Eris requires the value type to be the same before persisting and after unpersisting. That is, if the value replaced with a key from the permanent object table was a string, it must be a string that is stored permanent object table used for unpersisting at that key: +```lua +> eris.unpersist({[1] = "asd"}, eris.persist({["asd"] = 1}, "asd")) -- OK +> eris.unpersist({[1] = true}, eris.persist({["asd"] = 1}, "asd")) +stdin:1: bad permanent value (string expected, got boolean) +``` + +The permanent object table must not contain two identical values, i.e. two different objects must not be persisted with the same permanent key. This would lead to errors when unpersisting if the original types mismatch, and weird behavior otherwise. You must ensure this yourself, Eris does no checks in this regard. + +Permanent keys starting with `__eris.` are reserved for internal use. For now they are used for functions internal to Lua's libraries, namely the resume functions for yieldable C functions such as `pcall`. + +Special Persistence +------------------- + +Another concept carried over from Pluto is "special persistence". Tables and userdata can be annotated with a metatable field (named `__persist` per default). This field can be one of three types: `nil` for default behavior, `boolean` or `function`: + +* `true` marks the object for literal persistence. This is the default for tables. Trying to literally persist userdata without this will result in an error. If set, however, the userdata's memory block will be written as-is, and read back as-is. Note that the table's or userdata's metatable will also be persisted, if present. If you do not want that you'll have to use special persistence (see `function`). +* `false` marks the object as "forbidden". When such an object is encountered an error will be thrown. +* `function` is very special indeed. If present, this function will called when the object should be persisted. It must return a closure, which is persisted in place of the original object. The returned closure is then run when unpersisting, and is expected to return an object of the object's original type (i.e. table or userdata). Per default, the function stored in the metatable will receive one parameter, the original object. You can configure Eris to also pass along the used writer and userdata associated with it (see `eris_dump()`). + + Here is an example, originally from the Pluto documentation: + ```lua + vec = { x = 2, y = 1, z = 4 } + setmetatable(vec, { __persist = function(oldtbl) + local x = oldtbl.x + local y = oldtbl.y + local z = oldtbl.z + local mt = getmetatable(oldtbl) + return function() + newtbl = {} + newtbl.x = x + newtbl.y = y + newtbl.z = z + setmetatable(newtbl, mt) + return newtbl + end + end }) + ``` + + Also from the Pluto documentation: + > It is important that the fixup closure returned not reference the original table directly, as that table would again be persisted as an upvalue, leading to an infinite loop. Also note that the object's metatable is *not* automatically persisted; it is necessary for the fixup closure to reset it, if it wants. + +Cross-platform compatibility +============================ + +There are three components to this: byte order (little endian, big endian, ...), architecture (16 Bit, 32 Bit, 64 Bit) and floating point representation. +- The way values are persisted is endian-agnostic (big endianness tested on a [MIPS Debian][mips] running in [QEMU][]). Note that the testcase for literal userdata persistence fails when performed cross-platform. This is to be expected, since that data is actually an integer and Eris has no concept of the actual data stored in a userdatum. +- All potentially architecture-specific values are persisted as they are, i.e. when persisting in a 32-bit application `size_t` will usually be 4 bytes long, when persisting in a 64-bit application it'll be 8 bytes long. This means data will never be truncated when writing. We write the sizes of these types (`int` and `size_t`) in the persisting application into the header and check for truncation when reading. If there is data loss due to this, Eris will throw an error. At least errors when reading 64-bit data in a 32-bit application should be quite unlikely, however, since usually `int` will still be 4 bytes long, and `size_t` is only used for stack size and locations - and the maximum stack size is usually smaller than the maximum value of a 32-bit unsigned integer - for string lengths and userdata size, both of which should rarely be that large. What *could* be an issue, is light userdata. +- The binary floating point representation is expected to be the same on all systems sharing persisted data, which will usually be [IEEE 754][floats]. Also, all systems have to use the same type, i.e. `float` or `double`. You cannot load data persisted from a Lua installation using `float` into one that uses `double`. Eris performs a small check as part of its header and if the local model is incompatible to the data it will throw an error. + +So all in all, Eris should be largely cross-platform compatible. + +If you need more control over how things are persisted on your system: it should be relatively easy to adjust Eris in that regard, since reading and writing of the different elementary types is already split up into a couple of functions. + +Limitations +=========== + +* C functions cannot be persisted. Put them in the permanent object table. + - C closures can only be persisted if the underlying C function is in the permanent object table. +* Hook information is not persisted. You have to re-install hooks for persisted threads after unpersisting them. +* Metatables of basic types are not persisted. A workaround may be to add a dummy table with a `__persist` callback, which in turn stores these metatables, reinstalls them and returns a dummy table upon unpersisting. +* Neither the thread running Eris nor its parents can be persisted. More generally: no *running* thread can be persisted, only suspended ones - either because they were not started yet, or because they yielded. +* Threads that yielded out of a hook function cannot be persisted. I have not even tested this, since this seems to only be possible for certain hooks (line and count) and when the hook function yields from C itself. +* When the loaded data contains functions, this is like loading compiled code, and may therefore be potentially unsafe. You should only unpersist trusted data. +* Loops in the permanent value table (e.g. `{a = "b", b = "a"}`) will result in an infinite loop when persisting. Make sure there aren't any. + +Core library +============ + +You will notice that I decided to bundle this with Lua directly, instead of as a standalone library. This is in part due to how deeply Eris has to interact with Lua. It has to manually create internal data structures and populate them. Later versions of Pluto worked around this by extracting the used internal functions and only bundling those with the library. I decided against this for now, since with Lua 5.2 there's another thing that I need access to: the internally used C resume functions, used for yieldable C calls in the libraries (e.g. `pcall`). I had to patch the library sources by adding a function that pushed these C functions to the table with permanent values. These patches are very unintrusive: they just add a couple of lines to the end of the relevant library files. This means it is possible to persist a yielded `pcall` out of the box. + +Testing +======= + +Eris uses the same test suite Pluto uses, extended with a couple of Lua 5.2 specific tests, such as persisting yielded `pcall`s. The executables are built automatically into the test folder when building normally and can be run using `make test`. I only tested this on Windows (MinGW 32 and 64 bit) and Linux (Mint 64 bit). If this causes issues on other platforms please submit a patch, thanks. + +Differences to Pluto +==================== + +Quite obviously most design choices were taken from Pluto, partially to make it easier to migrate for people already familiar with Pluto, largely because they are pretty good as they are. There are some minute differences however. + +* The resulting persisted data, while using the same basic idea, is structured slightly differently. +* On the C side, Eris provides two new functions, `eris_persist` and `eris_unpersist` which work on with the stack, so you don't have to write your own `lua_Writer`/`lua_Reader` implementation. +* Better error reporting. When debugging, I recommend enabling path generation, e.g. from Lua via `eris.settings("path", true)`. This will result in all error messages containing a "path" that specifies where in the object the error occurred. For example: + + ```lua + > eris.persist({ + >> good = true, + >> [false] = setmetatable({}, { + >> __index = setmetatable({}, {__persist = false}) + >> })}) + stdin:1: attempt to persist forbidden table (root[false]@metatable.__index) + ``` + This is disabled per default due to the additional processing and memory overhead. + + +[Pluto]: https://github.com/hoelzro/pluto +[mips]: http://people.debian.org/~aurel32/qemu/mips/ +[QEMU]: http://qemu.org/ +[floats]: http://en.wikipedia.org/wiki/IEEE_floating_point +[maxstack]: https://github.com/fnuecke/eris/blob/c1674d99c1ee76c5fe1c30e64bfbb1ace52b011e/src/luaconf.h#L351 diff --git a/luprex/ext/eris-master/src/Makefile b/luprex/ext/eris-master/src/Makefile new file mode 100644 index 00000000..d97156ae --- /dev/null +++ b/luprex/ext/eris-master/src/Makefile @@ -0,0 +1,213 @@ +# Makefile for building Lua+Eris, based on the original Lua Makefile. + +# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= + +# Your platform. See PLATS for possible values. +PLAT= none + +CC= gcc +CFLAGS= -O2 -Wall -DLUA_COMPAT_ALL $(SYSCFLAGS) $(MYCFLAGS) +LDFLAGS= $(SYSLDFLAGS) $(MYLDFLAGS) +LIBS= -lm $(SYSLIBS) $(MYLIBS) + +AR= ar rcu +RANLIB= ranlib +RM= rm -f + +SYSCFLAGS= +SYSLDFLAGS= +SYSLIBS= + +MYCFLAGS= +MYLDFLAGS= +MYLIBS= +MYOBJS=eris.o + +# == END OF USER SETTINGS -- NO NEED TO CHANGE ANYTHING BELOW THIS LINE ======= + +PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris win-gcc-static + +LUA_A= liblua.a +CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o \ + lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o \ + ltm.o lundump.o lvm.o lzio.o +LIB_O= lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o \ + lmathlib.o loslib.o lstrlib.o ltablib.o loadlib.o linit.o +BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS) + +LUA_T= lua +LUA_O= lua.o + +LUAC_T= luac +LUAC_O= luac.o + +TESTP_T= ../test/persist +TESTP_O= ../test/persist.o + +TESTUP_T= ../test/unpersist +TESTUP_O= ../test/unpersist.o + +ALL_O= $(BASE_O) $(LUA_O) $(LUAC_O) $(TESTP_O) $(TESTUP_O) +ALL_T= $(LUA_A) $(LUA_T) $(LUAC_T) $(TESTP_T) $(TESTUP_T) +ALL_A= $(LUA_A) + +# Targets start here. +default: $(PLAT) + +all: $(ALL_T) + +o: $(ALL_O) + +a: $(ALL_A) + +$(LUA_A): $(BASE_O) + $(AR) $@ $(BASE_O) + $(RANLIB) $@ + +$(LUA_T): $(LUA_O) $(LUA_A) + $(CC) -o $@ $(LDFLAGS) $(LUA_O) $(LUA_A) $(LIBS) + +$(LUAC_T): $(LUAC_O) $(LUA_A) + $(CC) -o $@ $(LDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS) + +$(TESTP_T): $(TESTP_O) $(LUA_A) + $(CC) -o $@ $(LDFLAGS) $(TESTP_O) $(LUA_A) $(LIBS) + +$(TESTUP_T): $(TESTUP_O) $(LUA_A) + $(CC) -o $@ $(LDFLAGS) $(TESTUP_O) $(LUA_A) $(LIBS) + +$(TESTP_O): lua.h lualib.h lauxlib.h + $(CC) -c -o $@ ../test/persist.c -I../src + +$(TESTUP_O): ../test/unpersist.c lua.h lualib.h lauxlib.h + $(CC) -c -o $@ ../test/unpersist.c -I../src + +clean: + $(RM) $(ALL_T) $(ALL_O) + +depend: + @$(CC) $(CFLAGS) -MM l*.c + +echo: + @echo "PLAT= $(PLAT)" + @echo "CC= $(CC)" + @echo "CFLAGS= $(CFLAGS)" + @echo "LDFLAGS= $(SYSLDFLAGS)" + @echo "LIBS= $(LIBS)" + @echo "AR= $(AR)" + @echo "RANLIB= $(RANLIB)" + @echo "RM= $(RM)" + +# Convenience targets for popular platforms +ALL= all + +none: + @echo "Please do 'make PLATFORM' where PLATFORM is one of these:" + @echo " $(PLATS)" + +aix: + $(MAKE) $(ALL) CC="xlc" CFLAGS="-O2 -DLUA_USE_POSIX -DLUA_USE_DLOPEN" SYSLIBS="-ldl" SYSLDFLAGS="-brtl -bexpall" + +ansi: + $(MAKE) $(ALL) SYSCFLAGS="-DLUA_ANSI" + +bsd: + $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" SYSLIBS="-Wl,-E" + +freebsd: + $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_LINUX" SYSLIBS="-Wl,-E -lreadline" + +generic: $(ALL) + +linux: + $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_LINUX" SYSLIBS="-Wl,-E -ldl -lreadline" + +macosx: + $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_MACOSX" SYSLIBS="-lreadline" CC=cc + +mingw: + $(MAKE) "LUA_T=lua.exe" \ + "AR=$(CC) -shared -o" "RANLIB=strip --strip-unneeded" \ + "SYSCFLAGS=-DLUA_BUILD_AS_DLL" "SYSLIBS=" "SYSLDFLAGS=-s" lua.exe + $(MAKE) "LUAC_T=luac.exe" luac.exe + $(MAKE) "TESTP_T=../test/persist.exe" ../test/persist.exe + $(MAKE) "TESTUP_T=../test/unpersist.exe" ../test/unpersist.exe + +win-gcc-static: + $(MAKE) "LUA_T=lua.exe" "SYSLIBS=" "SYSLDFLAGS=-s" lua.exe + $(MAKE) "LUAC_T=luac.exe" luac.exe + $(MAKE) "TESTP_T=../test/persist.exe" ../test/persist.exe + $(MAKE) "TESTUP_T=../test/unpersist.exe" ../test/unpersist.exe + +posix: + $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_POSIX" + +solaris: + $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" SYSLIBS="-ldl" + +# list targets that do not create files (but not all makes understand .PHONY) +.PHONY: all $(PLATS) default o a clean depend echo none + +# DO NOT DELETE + +lapi.o: lapi.c lua.h luaconf.h lapi.h llimits.h lstate.h lobject.h ltm.h \ + lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h lstring.h ltable.h lundump.h \ + lvm.h +lauxlib.o: lauxlib.c lua.h luaconf.h lauxlib.h +lbaselib.o: lbaselib.c lua.h luaconf.h lauxlib.h lualib.h +lbitlib.o: lbitlib.c lua.h luaconf.h lauxlib.h lualib.h +lcode.o: lcode.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \ + lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h lgc.h \ + lstring.h ltable.h lvm.h +lcorolib.o: lcorolib.c lua.h luaconf.h lauxlib.h lualib.h +lctype.o: lctype.c lctype.h lua.h luaconf.h llimits.h +ldblib.o: ldblib.c lua.h luaconf.h lauxlib.h lualib.h +ldebug.o: ldebug.c lua.h luaconf.h lapi.h llimits.h lstate.h lobject.h \ + ltm.h lzio.h lmem.h lcode.h llex.h lopcodes.h lparser.h ldebug.h ldo.h \ + lfunc.h lstring.h lgc.h ltable.h lvm.h +ldo.o: ldo.c lua.h luaconf.h lapi.h llimits.h lstate.h lobject.h ltm.h \ + lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h lopcodes.h lparser.h \ + lstring.h ltable.h lundump.h lvm.h +ldump.o: ldump.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h \ + lzio.h lmem.h lundump.h +lfunc.o: lfunc.c lua.h luaconf.h lfunc.h lobject.h llimits.h lgc.h \ + lstate.h ltm.h lzio.h lmem.h +lgc.o: lgc.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ + lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h +linit.o: linit.c lua.h luaconf.h lualib.h lauxlib.h +liolib.o: liolib.c lua.h luaconf.h lauxlib.h lualib.h +llex.o: llex.c lua.h luaconf.h lctype.h llimits.h ldo.h lobject.h \ + lstate.h ltm.h lzio.h lmem.h llex.h lparser.h lstring.h lgc.h ltable.h +lmathlib.o: lmathlib.c lua.h luaconf.h lauxlib.h lualib.h +lmem.o: lmem.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ + ltm.h lzio.h lmem.h ldo.h lgc.h +loadlib.o: loadlib.c lua.h luaconf.h lauxlib.h lualib.h +lobject.o: lobject.c lua.h luaconf.h lctype.h llimits.h ldebug.h lstate.h \ + lobject.h ltm.h lzio.h lmem.h ldo.h lstring.h lgc.h lvm.h +lopcodes.o: lopcodes.c lopcodes.h llimits.h lua.h luaconf.h +loslib.o: loslib.c lua.h luaconf.h lauxlib.h lualib.h +lparser.o: lparser.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \ + lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h lfunc.h \ + lstring.h lgc.h ltable.h +lstate.o: lstate.c lua.h luaconf.h lapi.h llimits.h lstate.h lobject.h \ + ltm.h lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h llex.h lstring.h \ + ltable.h +lstring.o: lstring.c lua.h luaconf.h lmem.h llimits.h lobject.h lstate.h \ + ltm.h lzio.h lstring.h lgc.h +lstrlib.o: lstrlib.c lua.h luaconf.h lauxlib.h lualib.h +ltable.o: ltable.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ + ltm.h lzio.h lmem.h ldo.h lgc.h lstring.h ltable.h lvm.h +ltablib.o: ltablib.c lua.h luaconf.h lauxlib.h lualib.h +ltm.o: ltm.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h lzio.h \ + lmem.h lstring.h lgc.h ltable.h +lua.o: lua.c lua.h luaconf.h lauxlib.h lualib.h +luac.o: luac.c lua.h luaconf.h lauxlib.h lobject.h llimits.h lstate.h \ + ltm.h lzio.h lmem.h lundump.h ldebug.h lopcodes.h +lundump.o: lundump.c lua.h luaconf.h ldebug.h lstate.h lobject.h \ + llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lstring.h lgc.h lundump.h +lvm.o: lvm.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ + lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lstring.h ltable.h lvm.h +lzio.o: lzio.c lua.h luaconf.h llimits.h lmem.h lstate.h lobject.h ltm.h \ + lzio.h +eris.o: eris.c lua.h lauxlib.h lualib.h ldebug.h ldo.h lfunc.h lobject.h \ + lstate.h lstring.h lzio.h eris.h diff --git a/luprex/ext/eris-master/src/eris.c b/luprex/ext/eris-master/src/eris.c new file mode 100644 index 00000000..e153f8c7 --- /dev/null +++ b/luprex/ext/eris-master/src/eris.c @@ -0,0 +1,2758 @@ +/* +Eris - Heavy-duty persistence for Lua 5.2.4 - Based on Pluto +Copyright (c) 2013-2015 by Florian Nuecke. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +/* Standard library headers. */ +#include +#include +#include +#include +#include + +/* Not using stdbool because Visual Studio lives in the past... */ +#ifndef __cplusplus +typedef int bool; +#define false 0 +#define true 1 +#endif + +/* Mark us as part of the Lua core to get access to what we need. */ +#define LUA_CORE + +/* Public Lua headers. */ +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" + +/* Internal Lua headers. */ +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "lzio.h" + +/* Eris header. */ +#include "eris.h" + +/* +** {=========================================================================== +** Default settings. +** ============================================================================ +*/ + +/* Note that these are the default settings. They can be changed either from C + * by calling eris_g|set_setting() or from Lua using eris.settings(). */ + +/* The metatable key we use to allow customized persistence for tables and + * userdata. */ +static const char *const kPersistKey = "__persist"; + +/* Whether to pass the IO object (reader/writer) to the special function + * defined in the metafield or not. This is disabled per default because it + * mey allow Lua scripts to do more than they should. Enable this as needed. */ +static const bool kPassIOToPersist = false; + +/* Whether to persist debug information such as line numbers and upvalue and + * local variable names. */ +static const bool kWriteDebugInformation = true; + +/* Generate a human readable "path" that is shown together with error messages + * to indicate where in the object the error occurred. For example: + * eris.persist({false, bad = setmetatable({}, {__persist = false})}) + * Will produce: main:1: attempt to persist forbidden table (root.bad) + * This can be used for debugging, but is disabled per default due to the + * processing and memory overhead this introduces. */ +static const bool kGeneratePath = false; + +/* The maximum object complexity. This is the number of allowed recursions when + * persisting or unpersisting an object, for example for nested tables. This is + * used to avoid segfaults when writing or reading user data. */ +static const lua_Unsigned kMaxComplexity = 10000; + +/* +** ============================================================================ +** Lua internals interfacing. +** ============================================================================ +*/ + +/* Lua internals we use. We define these as macros to make it easier to swap + * them out, should the need ever arise. For example, the later Pluto versions + * copied these function to own files (presumably to allow building it as an + * extra shared library). These should be all functions we use that are not + * declared in lua.h or lauxlib.h. If there are some still directly in the code + * they were missed and should be replaced with a macro added here instead. */ +/* I'm quite sure we won't ever want to do this, because Eris needs a slightly + * patched Lua version to be able to persist some of the library functions, + * anyway: it needs to put the continuation C functions in the perms table. */ +/* ldebug.h */ +#define eris_ci_func ci_func +/* ldo.h */ +#define eris_incr_top incr_top +#define eris_savestack savestack +#define eris_restorestack restorestack +#define eris_reallocstack luaD_reallocstack +/* lfunc.h */ +#define eris_newproto luaF_newproto +#define eris_newLclosure luaF_newLclosure +#define eris_newupval luaF_newupval +#define eris_findupval luaF_findupval +/* lgc.h */ +#define eris_barrierproto luaC_barrierproto +/* lmem.h */ +#define eris_reallocvector luaM_reallocvector +/* lobject.h */ +#define eris_ttypenv ttypenv +#define eris_clLvalue clLvalue +#define eris_setnilvalue setnilvalue +#define eris_setclLvalue setclLvalue +#define eris_setobj setobj +#define eris_setsvalue2n setsvalue2n +/* lstate.h */ +#define eris_isLua isLua +#define eris_gch gch +#define eris_gco2uv gco2uv +#define eris_obj2gco obj2gco +#define eris_extendCI luaE_extendCI +/* lstring. h */ +#define eris_newlstr luaS_newlstr +/* lzio.h */ +#define eris_initbuffer luaZ_initbuffer +#define eris_buffer luaZ_buffer +#define eris_sizebuffer luaZ_sizebuffer +#define eris_bufflen luaZ_bufflen +#define eris_init luaZ_init +#define eris_read luaZ_read + +/* These are required for cross-platform support, since the size of TValue may + * differ, so the byte offset used by savestack/restorestack in Lua it is not a + * valid measure. */ +#define eris_savestackidx(L, p) ((p) - (L)->stack) +#define eris_restorestackidx(L, n) ((L)->stack + (n)) + +/* Enabled if we have a patched version of Lua (for accessing internals). */ +#if 1 + +/* Functions in Lua libraries used to access C functions we need to add to the + * permanents table to fully support yielded coroutines. */ +extern void eris_permbaselib(lua_State *L, int forUnpersist); +extern void eris_permcorolib(lua_State *L, int forUnpersist); +extern void eris_permloadlib(lua_State *L, int forUnpersist); +extern void eris_permiolib(lua_State *L, int forUnpersist); +extern void eris_permstrlib(lua_State *L, int forUnpersist); + +/* Utility macro for populating the perms table with internal C functions. */ +#define populateperms(L, forUnpersist) {\ + eris_permbaselib(L, forUnpersist);\ + eris_permcorolib(L, forUnpersist);\ + eris_permloadlib(L, forUnpersist);\ + eris_permiolib(L, forUnpersist);\ + eris_permstrlib(L, forUnpersist);\ +} + +#else + +/* Does nothing if we don't have a patched version of Lua. */ +#define populateperms(L, forUnpersist) ((void)0) + +#endif + +/* +** ============================================================================ +** Language strings for errors. +** ============================================================================ +*/ + +#define ERIS_ERR_CFUNC "attempt to persist a light C function (%p)" +#define ERIS_ERR_COMPLEXITY "object too complex" +#define ERIS_ERR_HOOK "cannot persist yielded hooks" +#define ERIS_ERR_METATABLE "bad metatable, not nil or table" +#define ERIS_ERR_NOFUNC "attempt to persist unknown function type" +#define ERIS_ERR_READ "could not read data" +#define ERIS_ERR_SPER_FUNC "%s did not return a function" +#define ERIS_ERR_SPER_LOAD "bad unpersist function (%s expected, returned %s)" +#define ERIS_ERR_SPER_PROT "attempt to persist forbidden table" +#define ERIS_ERR_SPER_TYPE "%d not nil, boolean, or function" +#define ERIS_ERR_SPER_UFUNC "invalid restore function" +#define ERIS_ERR_SPER_UPERM "bad permanent value (%s expected, got %s)" +#define ERIS_ERR_SPER_UPERMNIL "bad permanent value (no value)" +#define ERIS_ERR_STACKBOUNDS "stack index out of bounds" +#define ERIS_ERR_TABLE "bad table value, got a nil value" +#define ERIS_ERR_THREAD "cannot persist currently running thread" +#define ERIS_ERR_THREADCI "invalid callinfo" +#define ERIS_ERR_THREADCTX "bad C continuation function" +#define ERIS_ERR_THREADERRF "invalid errfunc" +#define ERIS_ERR_THREADPC "saved program counter out of bounds" +#define ERIS_ERR_TRUNC_INT "int value would get truncated" +#define ERIS_ERR_TRUNC_SIZE "size_t value would get truncated" +#define ERIS_ERR_TYPE_FLOAT "unsupported lua_Number type" +#define ERIS_ERR_TYPE_INT "unsupported int type" +#define ERIS_ERR_TYPE_SIZE "unsupported size_t type" +#define ERIS_ERR_TYPEP "trying to persist unknown type %d" +#define ERIS_ERR_TYPEU "trying to unpersist unknown type %d" +#define ERIS_ERR_UCFUNC "bad C closure (C function expected, got %s)" +#define ERIS_ERR_UCFUNCNULL "bad C closure (C function expected, got null)" +#define ERIS_ERR_USERDATA "attempt to literally persist userdata" +#define ERIS_ERR_WRITE "could not write data" +#define ERIS_ERR_REF "invalid reference #%d. this usually means a special "\ + "persistence callback of a table referenced said table "\ + "(directly or indirectly via an upvalue)." + +/* +** ============================================================================ +** Constants, settings, types and forward declarations. +** ============================================================================ +*/ + +/* The "type" we write when we persist a value via a replacement from the + * permanents table. This is just an arbitrary number, but it must we lower + * than the reference offset (below) and outside the range Lua uses for its + * types (> LUA_TOTALTAGS). */ +#define ERIS_PERMANENT (LUA_TOTALTAGS + 1) + +/* This is essentially the first reference we'll use. We do this to save one + * field in our persisted data: if the value is smaller than this, the object + * itself follows, otherwise we have a reference to an already unpersisted + * object. Note that in the reftable the actual entries are still stored + * starting at the first array index to have a sequence (when unpersisting). */ +#define ERIS_REFERENCE_OFFSET (ERIS_PERMANENT + 1) + +/* Avoids having to write the NULL all the time, plus makes it easier adding + * a custom error message should you ever decide you want one. */ +#define eris_checkstack(L, n) luaL_checkstack(L, n, NULL) + +/* Used for internal consistency checks, for debugging. These are true asserts + * in the sense that they should never fire, even for bad inputs. */ +#if 0 +#define eris_assert(c) assert(c) +#define eris_ifassert(e) e +#else +#define eris_assert(c) ((void)0) +#define eris_ifassert(e) ((void)0) +#endif + +/* State information when persisting an object. */ +typedef struct PersistInfo { + lua_Writer writer; + void *ud; + const char *metafield; + bool writeDebugInfo; +} PersistInfo; + +/* State information when unpersisting an object. */ +typedef struct UnpersistInfo { + ZIO zio; + size_t sizeof_int; + size_t sizeof_size_t; +} UnpersistInfo; + +/* Info shared in persist and unpersist. */ +typedef struct Info { + lua_State *L; + lua_Unsigned level; + int refcount; /* int because rawseti/rawgeti takes an int. */ + lua_Unsigned maxComplexity; + bool generatePath; + bool passIOToPersist; + /* Which one it really is will always be clear from the context. */ + union { + PersistInfo pi; + UnpersistInfo upi; + } u; +} Info; + +/* Type names, used for error messages. */ +static const char *const kTypenames[] = { + "nil", "boolean", "lightuserdata", "number", "string", + "table", "function", "userdata", "thread", "proto", "upval", + "deadkey", "permanent" +}; + +/* Setting names as used in eris.settings / eris_g|set_setting. Also, the + * addresses of these static variables are used as keys in the registry of Lua + * states to save the current values of the settings (as light userdata). */ +static const char *const kSettingMetafield = "spkey"; +static const char *const kSettingPassIOToPersist = "spio"; +static const char *const kSettingGeneratePath = "path"; +static const char *const kSettingWriteDebugInfo = "debug"; +static const char *const kSettingMaxComplexity = "maxrec"; + +/* Header we prefix to persisted data for a quick check when unpersisting. */ +static char const kHeader[] = { 'E', 'R', 'I', 'S' }; +#define HEADER_LENGTH sizeof(kHeader) + +/* Floating point number used to check compatibility of loaded data. */ +static const lua_Number kHeaderNumber = (lua_Number)-1.234567890; + +/* Stack indices of some internal values/tables, to avoid magic numbers. */ +#define PERMIDX 1 +#define REFTIDX 2 +#define BUFFIDX 3 +#define PATHIDX 4 + +/* Table indices for upvalue tables, keeping track of upvals to open. */ +#define UVTOCL 1 +#define UVTONU 2 +#define UVTVAL 3 +#define UVTREF 4 + +/* }======================================================================== */ + +/* +** {=========================================================================== +** Utility functions. +** ============================================================================ +*/ + +/* Pushes an object into the reference table when unpersisting. This creates an + * entry pointing from the id the object is referenced by to the object. */ +static int +registerobject(Info *info) { /* perms reftbl ... obj */ + const int reference = ++(info->refcount); + eris_checkstack(info->L, 1); + lua_pushvalue(info->L, -1); /* perms reftbl ... obj obj */ + lua_rawseti(info->L, REFTIDX, reference); /* perms reftbl ... obj */ + return reference; +} + +/** ======================================================================== */ + +/* Pushes a TString* onto the stack if it holds a value, nil if it is NULL. */ +static void +pushtstring(lua_State* L, TString *ts) { /* ... */ + if (ts) { + eris_setsvalue2n(L, L->top, ts); + eris_incr_top(L); /* ... str */ + } + else { + lua_pushnil(L); /* ... nil */ + } +} + +/* Creates a copy of the string on top of the stack and sets it as the value + * of the specified TString**. */ +static void +copytstring(lua_State* L, TString **ts) { + size_t length; + const char *value = lua_tolstring(L, -1, &length); + *ts = eris_newlstr(L, value, length); +} + +/** ======================================================================== */ + +/* Pushes the specified segment to the current path, if we're generating one. + * This supports formatting strings using Lua's formatting capabilities. */ +static void +pushpath(Info *info, const char* fmt, ...) { /* perms reftbl var path ... */ + if (!info->generatePath) { + return; + } + else { + va_list argp; + eris_checkstack(info->L, 1); + va_start(argp, fmt); + lua_pushvfstring(info->L, fmt, argp); /* perms reftbl var path ... str */ + va_end(argp); + lua_rawseti(info->L, PATHIDX, luaL_len(info->L, PATHIDX) + 1); + } /* perms reftbl var path ... */ +} + +/* Pops the last added segment from the current path if we're generating one. */ +static void +poppath(Info *info) { /* perms reftbl var path ... */ + if (!info->generatePath) { + return; + } + eris_checkstack(info->L, 1); + lua_pushnil(info->L); /* perms reftbl var path ... nil */ + lua_rawseti(info->L, PATHIDX, luaL_len(info->L, PATHIDX)); +} /* perms reftbl var path ... */ + +/* Concatenates all current path segments into one string, pushes it and + * returns it. This is relatively inefficient, but it's for errors only and + * keeps the stack small, so it's better this way. */ +static const char* +path(Info *info) { /* perms reftbl var path ... */ + if (!info->generatePath) { + return ""; + } + eris_checkstack(info->L, 3); + lua_pushstring(info->L, ""); /* perms reftbl var path ... str */ + lua_pushnil(info->L); /* perms reftbl var path ... str nil */ + while (lua_next(info->L, PATHIDX)) { /* perms reftbl var path ... str k v */ + lua_insert(info->L, -2); /* perms reftbl var path ... str v k */ + lua_insert(info->L, -3); /* perms reftbl var path ... k str v */ + lua_concat(info->L, 2); /* perms reftbl var path ... k str */ + lua_insert(info->L, -2); /* perms reftbl var path ... str k */ + } /* perms reftbl var path ... str */ + return lua_tostring(info->L, -1); +} + +/* Generates an error message with the appended path, if available. */ +static int +eris_error(Info *info, const char *fmt, ...) { /* ... */ + va_list argp; + eris_checkstack(info->L, 5); + + luaL_where(info->L, 1); /* ... where */ + va_start(argp, fmt); + lua_pushvfstring(info->L, fmt, argp); /* ... where str */ + va_end(argp); + if (info->generatePath) { + lua_pushstring(info->L, " ("); /* ... where str " (" */ + path(info); /* ... where str " (" path */ + lua_pushstring(info->L, ")"); /* ... where str " (" path ")" */ + lua_concat(info->L, 5); /* ... msg */ + } + else { + lua_concat(info->L, 2); /* ... msg */ + } + return lua_error(info->L); +} + +/** ======================================================================== */ + +/* Tries to get a setting from the registry. */ +static bool +get_setting(lua_State *L, void *key) { /* ... */ + eris_checkstack(L, 1); + lua_pushlightuserdata(L, key); /* ... key */ + lua_gettable(L, LUA_REGISTRYINDEX); /* ... value/nil */ + if (lua_isnil(L, -1)) { /* ... nil */ + lua_pop(L, 1); /* ... */ + return false; + } /* ... value */ + return true; +} + +/* Stores a setting in the registry (or removes it if the value is nil). */ +static void +set_setting(lua_State *L, void *key) { /* ... value */ + eris_checkstack(L, 2); + lua_pushlightuserdata(L, key); /* ... value key */ + lua_insert(L, -2); /* ... key value */ + lua_settable(L, LUA_REGISTRYINDEX); /* ... */ +} + +/* Used as a callback for luaL_opt to check boolean setting values. */ +static bool +checkboolean(lua_State *L, int narg) { /* ... bool? ... */ + if (!lua_isboolean(L, narg)) { /* ... :( ... */ + return luaL_argerror(L, narg, lua_pushfstring(L, + "boolean expected, got %s", lua_typename(L, lua_type(L, narg)))); + } /* ... bool ... */ + return lua_toboolean(L, narg); +} + +/* }======================================================================== */ + +/* +** {=========================================================================== +** Persist and unpersist. +** ============================================================================ +*/ + +/* I have macros and I'm not afraid to use them! These are highly situational + * and assume an Info* named 'info' is available. */ + +/* Writes a raw memory block with the specified size. */ +#define WRITE_RAW(value, size) {\ + if (info->u.pi.writer(info->L, (value), (size), info->u.pi.ud)) \ + eris_error(info, ERIS_ERR_WRITE); } + +/* Writes a single value with the specified type. */ +#define WRITE_VALUE(value, type) write_##type(info, value) + +/* Writes a typed array with the specified length. */ +#define WRITE(value, length, type) { \ + int i; for (i = 0; i < length; ++i) WRITE_VALUE((value)[i], type); } + +/** ======================================================================== */ + +/* Reads a raw block of memory with the specified size. */ +#define READ_RAW(value, size) {\ + if (eris_read(&info->u.upi.zio, (value), (size))) \ + eris_error(info, ERIS_ERR_READ); } + +/* Reads a single value with the specified type. */ +#define READ_VALUE(type) read_##type(info) + +/* Reads a typed array with the specified length. */ +#define READ(value, length, type) { \ + int i; for (i = 0; i < length; ++i) (value)[i] = READ_VALUE(type); } + +/** ======================================================================== */ + +static void +write_uint8_t(Info *info, uint8_t value) { + WRITE_RAW(&value, sizeof(uint8_t)); +} + +static void +write_uint16_t(Info *info, uint16_t value) { + write_uint8_t(info, value); + write_uint8_t(info, value >> 8); +} + +static void +write_uint32_t(Info *info, uint32_t value) { + write_uint8_t(info, value); + write_uint8_t(info, value >> 8); + write_uint8_t(info, value >> 16); + write_uint8_t(info, value >> 24); +} + +static void +write_uint64_t(Info *info, uint64_t value) { + write_uint8_t(info, value); + write_uint8_t(info, value >> 8); + write_uint8_t(info, value >> 16); + write_uint8_t(info, value >> 24); + write_uint8_t(info, value >> 32); + write_uint8_t(info, value >> 40); + write_uint8_t(info, value >> 48); + write_uint8_t(info, value >> 56); +} + +static void +write_int16_t(Info *info, int16_t value) { + write_uint16_t(info, (uint16_t)value); +} + +static void +write_int32_t(Info *info, int32_t value) { + write_uint32_t(info, (uint32_t)value); +} + +static void +write_int64_t(Info *info, int64_t value) { + write_uint64_t(info, (uint64_t)value); +} + +static void +write_float32(Info *info, float value) { + uint32_t rep; + memcpy(&rep, &value, sizeof(float)); + write_uint32_t(info, rep); +} + +static void +write_float64(Info *info, double value) { + uint64_t rep; + memcpy(&rep, &value, sizeof(double)); + write_uint64_t(info, rep); +} + +/* Note regarding the following: any decent compiler should be able + * to reduce these to just the write call, since sizeof is constant. */ + +static void +write_int(Info *info, int value) { + if (sizeof(int) == sizeof(int16_t)) { + write_int16_t(info, value); + } + else if (sizeof(int) == sizeof(int32_t)) { + write_int32_t(info, value); + } + else if (sizeof(int) == sizeof(int64_t)) { + write_int64_t(info, value); + } + else { + eris_error(info, ERIS_ERR_TYPE_INT); + } +} + +static void +write_size_t(Info *info, size_t value) { + if (sizeof(size_t) == sizeof(uint16_t)) { + write_uint16_t(info, value); + } + else if (sizeof(size_t) == sizeof(uint32_t)) { + write_uint32_t(info, value); + } + else if (sizeof(size_t) == sizeof(uint64_t)) { + write_uint64_t(info, value); + } + else { + eris_error(info, ERIS_ERR_TYPE_SIZE); + } +} + +static void +write_lua_Number(Info *info, lua_Number value) { + if (sizeof(lua_Number) == sizeof(uint32_t)) { + write_float32(info, value); + } + else if (sizeof(lua_Number) == sizeof(uint64_t)) { + write_float64(info, value); + } + else { + eris_error(info, ERIS_ERR_TYPE_FLOAT); + } +} + +/* Note that Lua only ever uses 32 bits of the Instruction type, so we can + * assert that there will be no truncation, even if the underlying type has + * more bits (might be the case on some 64 bit systems). */ + +static void +write_Instruction(Info *info, Instruction value) { + if (sizeof(Instruction) == sizeof(uint32_t)) { + write_uint32_t(info, value); + } + else { + uint32_t pvalue = (uint32_t)value; + /* Lua only uses 32 bits for its instructions. */ + eris_assert((Instruction)pvalue == value); + write_uint32_t(info, pvalue); + } +} + +/** ======================================================================== */ + +static uint8_t +read_uint8_t(Info *info) { + uint8_t value; + READ_RAW(&value, sizeof(uint8_t)); + return value; +} + +static uint16_t +read_uint16_t(Info *info) { + return (uint16_t)read_uint8_t(info) | + ((uint16_t)read_uint8_t(info) << 8); +} + +static uint32_t +read_uint32_t(Info *info) { + return (uint32_t)read_uint8_t(info) | + ((uint32_t)read_uint8_t(info) << 8) | + ((uint32_t)read_uint8_t(info) << 16) | + ((uint32_t)read_uint8_t(info) << 24); +} + +static uint64_t +read_uint64_t(Info *info) { + return (uint64_t)read_uint8_t(info) | + ((uint64_t)read_uint8_t(info) << 8) | + ((uint64_t)read_uint8_t(info) << 16) | + ((uint64_t)read_uint8_t(info) << 24) | + ((uint64_t)read_uint8_t(info) << 32) | + ((uint64_t)read_uint8_t(info) << 40) | + ((uint64_t)read_uint8_t(info) << 48) | + ((uint64_t)read_uint8_t(info) << 56); +} + +static int16_t +read_int16_t(Info *info) { + return (int16_t)read_uint16_t(info); +} + +static int32_t +read_int32_t(Info *info) { + return (int32_t)read_uint32_t(info); +} + +static int64_t +read_int64_t(Info *info) { + return (int64_t)read_uint64_t(info); +} + +static float +read_float32(Info *info) { + float value; + uint32_t rep = read_uint32_t(info); + memcpy(&value, &rep, sizeof(float)); + return value; +} + +static double +read_float64(Info *info) { + double value; + uint64_t rep = read_uint64_t(info); + memcpy(&value, &rep, sizeof(double)); + return value; +} + +/* Note regarding the following: unlike with writing the sizeof check will be + * impossible to optimize away, since it depends on the input; however, the + * truncation check may be optimized away in the case where the read data size + * equals the native one, so reading data written on the same machine should be + * reasonably quick. Doing a (rather rudimentary) benchmark this did not have + * any measurable impact on performance. */ + +static int +read_int(Info *info) { + int value; + if (info->u.upi.sizeof_int == sizeof(int16_t)) { + int16_t pvalue = read_int16_t(info); + value = (int)pvalue; + if ((int32_t)value != pvalue) { + eris_error(info, ERIS_ERR_TRUNC_INT); + } + } + else if (info->u.upi.sizeof_int == sizeof(int32_t)) { + int32_t pvalue = read_int32_t(info); + value = (int)pvalue; + if ((int32_t)value != pvalue) { + eris_error(info, ERIS_ERR_TRUNC_INT); + } + } + else if (info->u.upi.sizeof_int == sizeof(int64_t)) { + int64_t pvalue = read_int64_t(info); + value = (int)pvalue; + if ((int64_t)value != pvalue) { + eris_error(info, ERIS_ERR_TRUNC_INT); + } + } + else { + eris_error(info, ERIS_ERR_TYPE_INT); + value = 0; /* not reached */ + } + return value; +} + +static size_t +read_size_t(Info *info) { + size_t value; + if (info->u.upi.sizeof_size_t == sizeof(uint16_t)) { + uint16_t pvalue = read_uint16_t(info); + value = (size_t)pvalue; + if ((uint32_t)value != pvalue) { + eris_error(info, ERIS_ERR_TRUNC_SIZE); + } + } + else if (info->u.upi.sizeof_size_t == sizeof(uint32_t)) { + uint32_t pvalue = read_uint32_t(info); + value = (size_t)pvalue; + if ((uint32_t)value != pvalue) { + eris_error(info, ERIS_ERR_TRUNC_SIZE); + } + } + else if (info->u.upi.sizeof_size_t == sizeof(uint64_t)) { + uint64_t pvalue = read_uint64_t(info); + value = (size_t)pvalue; + if ((uint64_t)value != pvalue) { + eris_error(info, ERIS_ERR_TRUNC_SIZE); + } + } + else { + eris_error(info, ERIS_ERR_TYPE_SIZE); + value = 0; /* not reached */ + } + return value; +} + +static lua_Number +read_lua_Number(Info *info) { + if (sizeof(lua_Number) == sizeof(uint32_t)) { + return read_float32(info); + } + else if (sizeof(lua_Number) == sizeof(uint64_t)) { + return read_float64(info); + } + else { + eris_error(info, ERIS_ERR_TYPE_FLOAT); + return 0; /* not reached */ + } +} + +static Instruction +read_Instruction(Info *info) { + return (Instruction)read_uint32_t(info); +} + +/** ======================================================================== */ + +/* Forward declarations for recursively called top-level functions. */ +static void persist_keyed(Info*, int type); +static void persist(Info*); +static void unpersist(Info*); + +/* +** ============================================================================ +** Simple types. +** ============================================================================ +*/ + +static void +p_boolean(Info *info) { /* ... bool */ + WRITE_VALUE(lua_toboolean(info->L, -1), uint8_t); +} + +static void +u_boolean(Info *info) { /* ... */ + eris_checkstack(info->L, 1); + lua_pushboolean(info->L, READ_VALUE(uint8_t)); /* ... bool */ + + eris_assert(lua_type(info->L, -1) == LUA_TBOOLEAN); +} + +/** ======================================================================== */ + +static void +p_pointer(Info *info) { /* ... ludata */ + WRITE_VALUE((size_t)lua_touserdata(info->L, -1), size_t); +} + +static void +u_pointer(Info *info) { /* ... */ + eris_checkstack(info->L, 1); + lua_pushlightuserdata(info->L, (void*)READ_VALUE(size_t)); /* ... ludata */ + + eris_assert(lua_type(info->L, -1) == LUA_TLIGHTUSERDATA); +} + +/** ======================================================================== */ + +static void +p_number(Info *info) { /* ... num */ + WRITE_VALUE(lua_tonumber(info->L, -1), lua_Number); +} + +static void +u_number(Info *info) { /* ... */ + eris_checkstack(info->L, 1); + lua_pushnumber(info->L, READ_VALUE(lua_Number)); /* ... num */ + + eris_assert(lua_type(info->L, -1) == LUA_TNUMBER); +} + +/** ======================================================================== */ + +static void +p_string(Info *info) { /* ... str */ + size_t length; + const char *value = lua_tolstring(info->L, -1, &length); + WRITE_VALUE(length, size_t); + WRITE_RAW(value, length); +} + +static void +u_string(Info *info) { /* ... */ + eris_checkstack(info->L, 2); + { + /* TODO Can we avoid this copy somehow? (Without it getting too nasty) */ + const size_t length = READ_VALUE(size_t); + char *value = (char*)lua_newuserdata(info->L, length * sizeof(char)); /* ... tmp */ + READ_RAW(value, length); + lua_pushlstring(info->L, value, length); /* ... tmp str */ + lua_replace(info->L, -2); /* ... str */ + } + registerobject(info); + + eris_assert(lua_type(info->L, -1) == LUA_TSTRING); +} + +/* +** ============================================================================ +** Tables and userdata. +** ============================================================================ +*/ + +static void +p_metatable(Info *info) { /* ... obj */ + eris_checkstack(info->L, 1); + pushpath(info, "@metatable"); + if (!lua_getmetatable(info->L, -1)) { /* ... obj mt? */ + lua_pushnil(info->L); /* ... obj nil */ + } /* ... obj mt/nil */ + persist(info); /* ... obj mt/nil */ + lua_pop(info->L, 1); /* ... obj */ + poppath(info); +} + +static void +u_metatable(Info *info) { /* ... tbl */ + eris_checkstack(info->L, 1); + pushpath(info, "@metatable"); + unpersist(info); /* ... tbl mt/nil? */ + if (lua_istable(info->L, -1)) { /* ... tbl mt */ + lua_setmetatable(info->L, -2); /* ... tbl */ + } + else if (lua_isnil(info->L, -1)) { /* ... tbl nil */ + lua_pop(info->L, 1); /* ... tbl */ + } + else { /* tbl :( */ + eris_error(info, ERIS_ERR_METATABLE); + } + poppath(info); +} + +/** ======================================================================== */ + +static void +p_literaltable(Info *info) { /* ... tbl */ + eris_checkstack(info->L, 3); + + uint16_t bits = lua_getflagbits(info->L, -1); + WRITE_VALUE(bits, uint16_t); + + /* Persist all key / value pairs. */ + lua_pushnil(info->L); /* ... tbl nil */ + while (lua_next(info->L, -2)) { /* ... tbl k v */ + lua_pushvalue(info->L, -2); /* ... tbl k v k */ + + if (info->generatePath) { + if (lua_type(info->L, -1) == LUA_TSTRING) { + const char *key = lua_tostring(info->L, -1); + pushpath(info, ".%s", key); + } + else { + const char *key = luaL_tolstring(info->L, -1, NULL); + pushpath(info, "[%s]", key); + lua_pop(info->L, 1); + } + } + + persist(info); /* ... tbl k v k */ + lua_pop(info->L, 1); /* ... tbl k v */ + persist(info); /* ... tbl k v */ + lua_pop(info->L, 1); /* ... tbl k */ + + poppath(info); + } /* ... tbl */ + + /* Terminate list. */ + lua_pushnil(info->L); /* ... tbl nil */ + persist(info); /* ... tbl nil */ + lua_pop(info->L, 1); /* ... tbl */ + + p_metatable(info); +} + +static void +u_literaltable(Info *info) { /* ... */ + eris_checkstack(info->L, 3); + + lua_newtable(info->L); /* ... tbl */ + uint16_t bits = READ_VALUE(uint16_t); + lua_setflagbits(info->L, -1, bits); + + /* Preregister table for handling of cycles (keys, values or metatable). */ + registerobject(info); + + /* Unpersist all key / value pairs. */ + for (;;) { + pushpath(info, "@key"); + unpersist(info); /* ... tbl key/nil */ + poppath(info); + if (lua_isnil(info->L, -1)) { /* ... tbl nil */ + lua_pop(info->L, 1); /* ... tbl */ + break; + } /* ... tbl key */ + + if (info->generatePath) { + if (lua_type(info->L, -1) == LUA_TSTRING) { + const char *key = lua_tostring(info->L, -1); + pushpath(info, ".%s", key); + } + else { + const char *key = luaL_tolstring(info->L, -1, NULL); + pushpath(info, "[%s]", key); + lua_pop(info->L, 1); + } + } + + unpersist(info); /* ... tbl key value? */ + if (!lua_isnil(info->L, -1)) { /* ... tbl key value */ + lua_rawset(info->L, -3); /* ... tbl */ + } + else { + eris_error(info, ERIS_ERR_TABLE); + } + + poppath(info); + } + + u_metatable(info); /* ... tbl */ +} + +/** ======================================================================== */ + +static void +p_literaluserdata(Info *info) { /* ... udata */ + const size_t size = lua_rawlen(info->L, -1); + const void *value = lua_touserdata(info->L, -1); + WRITE_VALUE(size, size_t); + WRITE_RAW(value, size); + p_metatable(info); /* ... udata */ +} + +static void +u_literaluserdata(Info *info) { /* ... */ + eris_checkstack(info->L, 1); + { + size_t size = READ_VALUE(size_t); + void *value = lua_newuserdata(info->L, size); /* ... udata */ + READ_RAW(value, size); /* ... udata */ + } + registerobject(info); + u_metatable(info); +} + +/** ======================================================================== */ + +typedef void (*Callback) (Info*); + +static void +p_special(Info *info, Callback literal) { /* ... obj */ + int allow = (lua_type(info->L, -1) == LUA_TTABLE); + eris_checkstack(info->L, 4); + + /* Check whether we should persist literally, or via the metafunction. */ + if (lua_getmetatable(info->L, -1)) { /* ... obj mt */ + lua_pushstring(info->L, info->u.pi.metafield); /* ... obj mt pkey */ + lua_rawget(info->L, -2); /* ... obj mt persist? */ + switch (lua_type(info->L, -1)) { + /* No entry, act according to default. */ + case LUA_TNIL: /* ... obj mt nil */ + lua_pop(info->L, 2); /* ... obj */ + break; + + /* Boolean value, tells us whether allowed or not. */ + case LUA_TBOOLEAN: /* ... obj mt bool */ + allow = lua_toboolean(info->L, -1); + lua_pop(info->L, 2); /* ... obj */ + break; + + /* Function value, call it and don't persist literally. */ + case LUA_TFUNCTION: /* ... obj mt func */ + lua_replace(info->L, -2); /* ... obj func */ + lua_pushvalue(info->L, -2); /* ... obj func obj */ + + if (info->passIOToPersist) { + lua_pushlightuserdata(info->L, (void*)info->u.pi.writer); + /* ... obj func obj writer */ + lua_pushlightuserdata(info->L, info->u.pi.ud); + /* ... obj func obj writer ud */ + lua_call(info->L, 3, 1); /* ... obj func? */ + } + else { + lua_call(info->L, 1, 1); /* ... obj func? */ + } + if (!lua_isfunction(info->L, -1)) { /* ... obj :( */ + eris_error(info, ERIS_ERR_SPER_FUNC, info->u.pi.metafield); + } /* ... obj func */ + + /* Special persistence, call this function when unpersisting. */ + WRITE_VALUE(true, uint8_t); + persist(info); /* ... obj func */ + lua_pop(info->L, 1); /* ... obj */ + return; + default: /* ... obj mt :( */ + eris_error(info, ERIS_ERR_SPER_TYPE, info->u.pi.metafield); + return; /* not reached */ + } + } + + if (allow) { + /* Not special but literally persisted object. */ + WRITE_VALUE(false, uint8_t); + literal(info); /* ... obj */ + } + else if (lua_type(info->L, -1) == LUA_TTABLE) { + eris_error(info, ERIS_ERR_SPER_PROT); + } + else { + eris_error(info, ERIS_ERR_USERDATA); + } +} + +static void +u_special(Info *info, int type, Callback literal) { /* ... */ + eris_checkstack(info->L, 2); + if (READ_VALUE(uint8_t)) { + int reference; + /* Reserve entry in the reftable before unpersisting the function to keep + * the reference order intact. We can set this to nil at first, because + * there's no way the special function would access this. */ + lua_pushnil(info->L); /* ... nil */ + reference = registerobject(info); + lua_pop(info->L, 1); /* ... */ + /* Increment reference counter by one to compensate for the increment when + * persisting a special object. */ + unpersist(info); /* ... spfunc? */ + if (!lua_isfunction(info->L, -1)) { /* ... :( */ + eris_error(info, ERIS_ERR_SPER_UFUNC); + } /* ... spfunc */ + + if (info->passIOToPersist) { + lua_pushlightuserdata(info->L, &info->u.upi.zio); /* ... spfunc zio */ + lua_call(info->L, 1, 1); /* ... obj? */ + } else { + lua_call(info->L, 0, 1); /* ... obj? */ + } + + if (lua_type(info->L, -1) != type) { /* ... :( */ + const char *want = kTypenames[type]; + const char *have = kTypenames[lua_type(info->L, -1)]; + eris_error(info, ERIS_ERR_SPER_LOAD, want, have); + } /* ... obj */ + + /* Update the reftable entry. */ + lua_pushvalue(info->L, -1); /* ... obj obj */ + lua_rawseti(info->L, 2, reference); /* ... obj */ + } + else { + literal(info); /* ... obj */ + } +} + +/** ======================================================================== */ + +static void +p_table(Info *info) { /* ... tbl */ + p_literaltable(info); +} + +static void +u_table(Info *info) { + u_literaltable(info); /* ... tbl */ + + eris_assert(lua_type(info->L, -1) == LUA_TTABLE); +} + +/** ======================================================================== */ + +static void +p_userdata(Info *info) { /* perms reftbl ... udata */ + p_special(info, p_literaluserdata); +} + +static void +u_userdata(Info *info) { /* ... */ + u_special(info, LUA_TUSERDATA, u_literaluserdata); /* ... udata */ + + eris_assert(lua_type(info->L, -1) == LUA_TUSERDATA); +} + +/* +** ============================================================================ +** Closures and threads. +** ============================================================================ +*/ + +/* We track the actual upvalues themselves by pushing their "id" (meaning a + * pointer to them) as lightuserdata to the reftable. This is safe because + * lightuserdata will not normally end up in there, because simple value types + * are always persisted directly (because that'll be just as large, memory- + * wise as when pointing to the first instance). Same for protos. */ + +static void +p_proto(Info *info) { /* ... proto */ + int i; + const Proto *p = (Proto*)lua_touserdata(info->L, -1); + eris_checkstack(info->L, 3); + + /* Write general information. */ + WRITE_VALUE(p->linedefined, int); + WRITE_VALUE(p->lastlinedefined, int); + WRITE_VALUE(p->numparams, uint8_t); + WRITE_VALUE(p->is_vararg, uint8_t); + WRITE_VALUE(p->maxstacksize, uint8_t); + + /* Write byte code. */ + WRITE_VALUE(p->sizecode, int); + WRITE(p->code, p->sizecode, Instruction); + + /* Write constants. */ + WRITE_VALUE(p->sizek, int); + pushpath(info, ".constants"); + for (i = 0; i < p->sizek; ++i) { + pushpath(info, "[%d]", i); + eris_setobj(info->L, info->L->top++, &p->k[i]); /* ... lcl proto obj */ + persist(info); /* ... lcl proto obj */ + lua_pop(info->L, 1); /* ... lcl proto */ + poppath(info); + } + poppath(info); + + /* Write child protos. */ + WRITE_VALUE(p->sizep, int); + pushpath(info, ".protos"); + for (i = 0; i < p->sizep; ++i) { + pushpath(info, "[%d]", i); + lua_pushlightuserdata(info->L, p->p[i]); /* ... lcl proto proto */ + lua_pushvalue(info->L, -1); /* ... lcl proto proto proto */ + persist_keyed(info, LUA_TPROTO); /* ... lcl proto proto */ + lua_pop(info->L, 1); /* ... lcl proto */ + poppath(info); + } + poppath(info); + + /* Write upvalues. */ + WRITE_VALUE(p->sizeupvalues, int); + for (i = 0; i < p->sizeupvalues; ++i) { + WRITE_VALUE(p->upvalues[i].instack, uint8_t); + WRITE_VALUE(p->upvalues[i].idx, uint8_t); + } + + /* If we don't have to persist debug information skip the rest. */ + WRITE_VALUE(info->u.pi.writeDebugInfo, uint8_t); + if (!info->u.pi.writeDebugInfo) { + return; + } + + /* Write function source code. */ + pushtstring(info->L, p->source); /* ... lcl proto source */ + persist(info); /* ... lcl proto source */ + lua_pop(info->L, 1); /* ... lcl proto */ + + /* Write line information. */ + WRITE_VALUE(p->sizelineinfo, int); + WRITE(p->lineinfo, p->sizelineinfo, int); + + /* Write locals info. */ + WRITE_VALUE(p->sizelocvars, int); + pushpath(info, ".locvars"); + for (i = 0; i < p->sizelocvars; ++i) { + pushpath(info, "[%d]", i); + WRITE_VALUE(p->locvars[i].startpc, int); + WRITE_VALUE(p->locvars[i].endpc, int); + pushtstring(info->L, p->locvars[i].varname); /* ... lcl proto varname */ + persist(info); /* ... lcl proto varname */ + lua_pop(info->L, 1); /* ... lcl proto */ + poppath(info); + } + poppath(info); + + /* Write upvalue names. */ + pushpath(info, ".upvalnames"); + for (i = 0; i < p->sizeupvalues; ++i) { + pushpath(info, "[%d]", i); + pushtstring(info->L, p->upvalues[i].name); /* ... lcl proto name */ + persist(info); /* ... lcl proto name */ + lua_pop(info->L, 1); /* ... lcl proto */ + poppath(info); + } + poppath(info); +} + +static void +u_proto(Info *info) { /* ... proto */ + int i, n; + Proto *p = (Proto*)lua_touserdata(info->L, -1); + eris_assert(p); + + eris_checkstack(info->L, 2); + + /* Preregister proto for handling of cycles (probably impossible, but + * maybe via the constants of the proto... not worth taking the risk). */ + registerobject(info); + + /* Read general information. */ + p->linedefined = READ_VALUE(int); + p->lastlinedefined = READ_VALUE(int); + p->numparams = READ_VALUE(uint8_t); + p->is_vararg = READ_VALUE(uint8_t); + p->maxstacksize = READ_VALUE(uint8_t); + + /* Read byte code. */ + int sizecode = READ_VALUE(int); + eris_reallocvector(info->L, p->code, 0, sizecode, Instruction); + p->sizecode = sizecode; + READ(p->code, p->sizecode, Instruction); + + /* Read constants. */ + int sizek = READ_VALUE(int); + eris_reallocvector(info->L, p->k, 0, sizek, TValue); + p->sizek = sizek; + /* Set all values to nil to avoid confusing the GC. */ + for (i = 0, n = p->sizek; i < n; ++i) { + eris_setnilvalue(&p->k[i]); + } + pushpath(info, ".constants"); + for (i = 0, n = p->sizek; i < n; ++i) { + pushpath(info, "[%d]", i); + unpersist(info); /* ... proto obj */ + eris_setobj(info->L, &p->k[i], info->L->top - 1); + lua_pop(info->L, 1); /* ... proto */ + poppath(info); + } + poppath(info); + + /* Allocate space for child protos */ + /* Null all entries to avoid confusing the GC. */ + int sizep = READ_VALUE(int); + eris_reallocvector(info->L, p->p, 0, sizep, Proto*); + memset(p->p, 0, sizep * sizeof(Proto*)); + p->sizep = sizep; + + /* Read child protos. */ + pushpath(info, ".protos"); + for (i = 0, n = p->sizep; i < n; ++i) { + Proto *cp; + pushpath(info, "[%d]", i); + p->p[i] = cp = eris_newproto(info->L); + luaC_objbarrier(info->L, p, cp); + lua_pushlightuserdata(info->L, (void*)cp); /* ... proto nproto */ + unpersist(info); /* ... proto nproto nproto/oproto */ + cp = (Proto*)lua_touserdata(info->L, -1); + if (cp != p->p[i]) { /* ... proto nproto oproto */ + /* Just overwrite it, GC will clean this up. */ + p->p[i] = cp; + } + lua_pop(info->L, 2); /* ... proto */ + poppath(info); + } + poppath(info); + + /* Read upvalues. */ + int sizeupvalues = READ_VALUE(int); + eris_reallocvector(info->L, p->upvalues, 0, sizeupvalues, Upvaldesc); + p->sizeupvalues = sizeupvalues; + for (i = 0, n = p->sizeupvalues; i < n; ++i) { + p->upvalues[i].name = NULL; + p->upvalues[i].instack = READ_VALUE(uint8_t); + p->upvalues[i].idx = READ_VALUE(uint8_t); + } + + /* Read debug information if any is present. */ + if (!READ_VALUE(uint8_t)) { + /* Match stack behaviour of alternative branch. */ + lua_pushvalue(info->L, -1); /* ... proto proto */ + return; + } + + /* Read function source code. */ + unpersist(info); /* ... proto str */ + copytstring(info->L, &p->source); + lua_pop(info->L, 1); /* ... proto */ + + /* Read line information. */ + int sizelineinfo = READ_VALUE(int); + eris_reallocvector(info->L, p->lineinfo, 0, sizelineinfo, int); + p->sizelineinfo = sizelineinfo; + + READ(p->lineinfo, p->sizelineinfo, int); + + /* Read locals info. */ + int sizelocvars = READ_VALUE(int); + eris_reallocvector(info->L, p->locvars, 0, sizelocvars, LocVar); + p->sizelocvars = sizelocvars; + /* Null the variable names to avoid confusing the GC. */ + for (i = 0, n = p->sizelocvars; i < n; ++i) { + p->locvars[i].varname = NULL; + } + pushpath(info, ".locvars"); + for (i = 0, n = p->sizelocvars; i < n; ++i) { + pushpath(info, "[%d]", i); + p->locvars[i].startpc = READ_VALUE(int); + p->locvars[i].endpc = READ_VALUE(int); + unpersist(info); /* ... proto str */ + copytstring(info->L, &p->locvars[i].varname); + lua_pop(info->L, 1); /* ... proto */ + poppath(info); + } + poppath(info); + + /* Read upvalue names. */ + pushpath(info, ".upvalnames"); + for (i = 0, n = p->sizeupvalues; i < n; ++i) { + pushpath(info, "[%d]", i); + unpersist(info); /* ... proto str */ + copytstring(info->L, &p->upvalues[i].name); + lua_pop(info->L, 1); /* ... proto */ + poppath(info); + } + poppath(info); + lua_pushvalue(info->L, -1); /* ... proto proto */ + + eris_assert(lua_type(info->L, -1) == LUA_TLIGHTUSERDATA); +} + +/** ======================================================================== */ + +static void +p_upval(Info *info) { /* ... obj */ + persist(info); /* ... obj */ +} + +static void +u_upval(Info *info) { /* ... */ + eris_checkstack(info->L, 2); + + /* Create the table we use to store the stack location to the upval (1+2), + * the value of the upval (3) and any references to the upvalue's value (4+). + * References are stored as two entries each, the actual closure holding the + * upvalue, and the index of the upvalue in that closure. */ + lua_createtable(info->L, 5, 0); /* ... tbl */ + registerobject(info); + unpersist(info); /* ... tbl obj */ + lua_rawseti(info->L, -2, UVTVAL); /* ... tbl */ + + eris_assert(lua_type(info->L, -1) == LUA_TTABLE); +} + +/** ======================================================================== */ + +/* For Lua closures we write the upvalue ID, which is usually the memory + * address at which it is stored. This is used to tell which upvalues are + * identical when unpersisting. */ +/* In either case we store the upvale *values*, i.e. the actual objects they + * point to. As in Pluto, we will restore any upvalues of Lua closures as + * closed as first, i.e. the upvalue will store the TValue itself. When + * loading a thread containing the upvalue (meaning it's the actual owner of + * the upvalue) we open it, i.e. we point it to the thread's upvalue list. + * For C closures, upvalues are always closed. */ +static void +p_closure(Info *info) { /* perms reftbl ... func */ + int nup; + eris_checkstack(info->L, 2); + switch (ttype(info->L->top - 1)) { + case LUA_TLCF: /* light C function */ + /* We cannot persist these, they have to be handled via the permtable. */ + eris_error(info, ERIS_ERR_CFUNC, lua_tocfunction(info->L, -1)); + return; /* not reached */ + case LUA_TCCL: /* C closure */ { /* perms reftbl ... ccl */ + CClosure *cl = clCvalue(info->L->top - 1); + /* Mark it as a C closure. */ + WRITE_VALUE(true, uint8_t); + /* Write the upvalue count first, since we have to know it when creating + * a new closure when unpersisting. */ + WRITE_VALUE(cl->nupvalues, uint8_t); + + /* We can only persist these if the underlying C function is in the + * permtable. So we try to persist it first as a light C function. If it + * isn't in the permtable that'll cause an error (in the case above). */ + lua_pushcfunction(info->L, lua_tocfunction(info->L, -1)); + /* perms reftbl ... ccl cfunc */ + persist(info); /* perms reftbl ... ccl cfunc */ + lua_pop(info->L, 1); /* perms reftbl ... ccl */ + + /* Persist the upvalues. Since for C closures all upvalues are always + * closed we can just write the actual values. */ + pushpath(info, ".upvalues"); + for (nup = 1; nup <= cl->nupvalues; ++nup) { + pushpath(info, "[%d]", nup); + lua_getupvalue(info->L, -1, nup); /* perms reftbl ... ccl obj */ + persist(info); /* perms reftbl ... ccl obj */ + lua_pop(info->L, 1); /* perms reftbl ... ccl */ + poppath(info); + } + poppath(info); + break; + } + case LUA_TLCL: /* Lua function */ { /* perms reftbl ... lcl */ + LClosure *cl = eris_clLvalue(info->L->top - 1); + /* Mark it as a Lua closure. */ + WRITE_VALUE(false, uint8_t); + /* Write the upvalue count first, since we have to know it when creating + * a new closure when unpersisting. */ + WRITE_VALUE(cl->nupvalues, uint8_t); + + /* Persist the function's prototype. Pass the proto as a parameter to + * p_proto so that it can access it and register it in the ref table. */ + pushpath(info, ".proto"); + lua_pushlightuserdata(info->L, cl->p); /* perms reftbl ... lcl proto */ + lua_pushvalue(info->L, -1); /* perms reftbl ... lcl proto proto */ + persist_keyed(info, LUA_TPROTO); /* perms reftbl ... lcl proto */ + lua_pop(info->L, 1); /* perms reftbl ... lcl */ + poppath(info); + + /* Persist the upvalues. We pretend to write these as their own type, + * to get proper identity preservation. We also pass them as a parameter + * to p_upval so it can register the upvalue in the reference table. */ + pushpath(info, ".upvalues"); + for (nup = 1; nup <= cl->nupvalues; ++nup) { + const char *name = lua_getupvalue(info->L, -1, nup); + /* perms reftbl ... lcl obj */ + pushpath(info, ".%s", name); + lua_pushlightuserdata(info->L, lua_upvalueid(info->L, -2, nup)); + /* perms reftbl ... lcl obj id */ + persist_keyed(info, LUA_TUPVAL); /* perms reftbl ... lcl obj */ + lua_pop(info->L, 1); /* perms reftble ... lcl */ + poppath(info); + } + poppath(info); + break; + } + default: + eris_error(info, ERIS_ERR_NOFUNC); + return; /* not reached */ + } +} + +static void +u_closure(Info *info) { /* ... */ + int nup; + bool isCClosure = READ_VALUE(uint8_t); + lu_byte nups = READ_VALUE(uint8_t); + if (isCClosure) { + lua_CFunction f; + + /* Reserve reference for the closure to avoid light C function or its + * perm table key going first. */ + const int reference = ++(info->refcount); + + /* nups is guaranteed to be >= 1, otherwise it'd be a light C function. */ + eris_checkstack(info->L, nups < 2 ? 2 : nups); + + /* Read the C function from the permanents table. */ + unpersist(info); /* ... cfunc */ + if (!lua_iscfunction(info->L, -1)) { + eris_error(info, ERIS_ERR_UCFUNC, kTypenames[lua_type(info->L, -1)]); + } + f = lua_tocfunction(info->L, -1); + if (!f) { + eris_error(info, ERIS_ERR_UCFUNCNULL); + } + lua_pop(info->L, 1); /* ... */ + + /* Now this is a little roundabout, but we want to create the closure + * before unpersisting the actual upvalues to avoid cycles. So we have to + * create it with all nil first, then fill the upvalues in afterwards. */ + for (nup = 1; nup <= nups; ++nup) { + lua_pushnil(info->L); /* ... nil[1] ... nil[nup] */ + } + lua_pushcclosure(info->L, f, nups); /* ... ccl */ + + /* Create the entry in the reftable. */ + lua_pushvalue(info->L, -1); /* perms reftbl ... ccl ccl */ + lua_rawseti(info->L, REFTIDX, reference); /* perms reftbl ... ccl */ + + /* Unpersist actual upvalues. */ + pushpath(info, ".upvalues"); + for (nup = 1; nup <= nups; ++nup) { + pushpath(info, "[%d]", nup); + unpersist(info); /* ... ccl obj */ + lua_setupvalue(info->L, -2, nup); /* ... ccl */ + poppath(info); + } + poppath(info); + } + else { + Closure *cl; + Proto *p; + + eris_checkstack(info->L, 4); + + /* Create closure and anchor it on the stack (avoid collection via GC). */ + cl = eris_newLclosure(info->L, nups); + eris_setclLvalue(info->L, info->L->top, cl); /* ... lcl */ + eris_incr_top(info->L); + + /* Preregister closure for handling of cycles (upvalues). */ + registerobject(info); + + /* Read prototype. In general, we create protos (and upvalues) before + * trying to read them and pass a pointer to the instance along to the + * unpersist function. This way the instance is safely hooked up to an + * object, so we don't have to worry about it getting GCed. */ + pushpath(info, ".proto"); + cl->l.p = eris_newproto(info->L); + /* Push the proto into which to unpersist as a parameter to u_proto. */ + lua_pushlightuserdata(info->L, cl->l.p); /* ... lcl nproto */ + unpersist(info); /* ... lcl nproto nproto/oproto */ + eris_assert(lua_type(info->L, -1) == LUA_TLIGHTUSERDATA); + /* The proto we have now may differ, if we already unpersisted it before. + * In that case we now have a reference to the originally unpersisted + * proto so we'll use that. */ + p = (Proto*)lua_touserdata(info->L, -1); + if (p != cl->l.p) { /* ... lcl nproto oproto */ + /* Just overwrite the old one, GC will clean this up. */ + cl->l.p = p; + } + lua_pop(info->L, 2); /* ... lcl */ + eris_assert(cl->l.p->sizeupvalues == nups); + poppath(info); + + /* Unpersist all upvalues. */ + pushpath(info, ".upvalues"); + for (nup = 1; nup <= nups; ++nup) { + UpVal **uv = &cl->l.upvals[nup - 1]; + /* Get the actual name of the upvalue, if possible. */ + if (p->upvalues[nup - 1].name) { + pushpath(info, "[%s]", getstr(p->upvalues[nup - 1].name)); + } + else { + pushpath(info, "[%d]", nup); + } + unpersist(info); /* ... lcl tbl */ + eris_assert(lua_type(info->L, -1) == LUA_TTABLE); + lua_rawgeti(info->L, -1, UVTOCL); /* ... lcl tbl olcl/nil */ + if (lua_isnil(info->L, -1)) { /* ... lcl tbl nil */ + lua_pop(info->L, 1); /* ... lcl tbl */ + lua_pushvalue(info->L, -2); /* ... lcl tbl lcl */ + lua_rawseti(info->L, -2, UVTOCL); /* ... lcl tbl */ + lua_pushinteger(info->L, nup); /* ... lcl tbl nup */ + lua_rawseti(info->L, -2, UVTONU); /* ... lcl tbl */ + *uv = eris_newupval(info->L); + } + else { /* ... lcl tbl olcl */ + LClosure *ocl; + int onup; + eris_assert(lua_type(info->L, -1) == LUA_TFUNCTION); + ocl = eris_clLvalue(info->L->top - 1); + lua_pop(info->L, 1); /* ... lcl tbl */ + lua_rawgeti(info->L, -1, UVTONU); /* ... lcl tbl onup */ + eris_assert(lua_type(info->L, -1) == LUA_TNUMBER); + onup = lua_tointeger(info->L, -1); + lua_pop(info->L, 1); /* ... lcl tbl */ + *uv = ocl->upvals[onup - 1]; + } + luaC_objbarrier(info->L, cl, *uv); + + /* Set the upvalue's actual value and add our reference to the upvalue to + * the list, for reference patching if we have to open the upvalue in + * u_thread. Either is only necessary if the upvalue is still closed. */ + if ((*uv)->v == &(*uv)->u.value) { + int i; + /* Always update the value of the upvalue's value for closed upvalues, + * even if we re-used one - if we had a cycle, it might have been + * incorrectly initialized to nil before (or rather, not yet set). */ + lua_rawgeti(info->L, -1, UVTVAL); /* ... lcl tbl obj */ + eris_setobj(info->L, &(*uv)->u.value, info->L->top - 1); + lua_pop(info->L, 1); /* ... lcl tbl */ + + lua_pushinteger(info->L, nup); /* ... lcl tbl nup */ + lua_pushvalue(info->L, -3); /* ... lcl tbl nup lcl */ + if (luaL_len(info->L, -3) >= UVTVAL) { + /* Got a valid sequence (value already set), insert at the end. */ + i = luaL_len(info->L, -3); + lua_rawseti(info->L, -3, i + 1); /* ... lcl tbl nup */ + lua_rawseti(info->L, -2, i + 2); /* ... lcl tbl */ + } + else { /* ... lcl tbl nup lcl */ + /* Find where to insert. This can happen if we have cycles, in which + * case the table is not fully initialized at this point, i.e. the + * value is not in it, yet (we work around that by always setting it, + * as seen above). */ + for (i = UVTREF;; i += 2) { + lua_rawgeti(info->L, -3, i); /* ... lcl tbl nup lcl lcl/nil */ + if (lua_isnil(info->L, -1)) { /* ... lcl tbl nup lcl nil */ + lua_pop(info->L, 1); /* ... lcl tbl nup lcl */ + lua_rawseti(info->L, -3, i); /* ... lcl tbl nup */ + lua_rawseti(info->L, -2, i + 1); /* ... lcl tbl */ + break; + } + else { + lua_pop(info->L, 1); /* ... lcl tbl nup lcl */ + } + } /* ... lcl tbl */ + } + } + + lua_pop(info->L, 1); /* ... lcl */ + poppath(info); + } + poppath(info); + + eris_barrierproto(info->L, p, cl); + p->cache = cl; /* save it in cache for reuse, see lvm.c:416 */ + } + + eris_assert(lua_type(info->L, -1) == LUA_TFUNCTION); +} + +/** ======================================================================== */ + +static void +p_thread(Info *info) { /* ... thread */ + lua_State* thread = lua_tothread(info->L, -1); + size_t level = 0, total = thread->top - thread->stack; + CallInfo *ci; + GCObject *uvi; + + eris_checkstack(info->L, 2); + + /* We cannot persist any running threads, because by definition we *are* that + * running thread. And we use the stack. So yeah, really not a good idea. */ + if (thread == info->L) { + eris_error(info, ERIS_ERR_THREAD); + return; /* not reached */ + } + + /* Persist the stack. Save the total size and used space first. */ + WRITE_VALUE(thread->stacksize, int); + WRITE_VALUE(total, size_t); + + /* The Lua stack looks like this: + * stack ... top ... stack_last + * Where stack <= top <= stack_last, and "top" actually being the first free + * element, i.e. there's nothing stored there. So we stop one below that. */ + pushpath(info, ".stack"); + lua_pushnil(info->L); /* ... thread nil */ + /* Since the thread's stack may be re-allocated in the meantime, we cannot + * use pointer arithmetic here (i.e. o = thread->stack; ...; ++o). Instead we + * have to keep track of our position in the stack directly (which we do for + * the path info anyway) and compute the actual address each time. + */ + for (; level < total; ++level) { + pushpath(info, "[%d]", level); + eris_setobj(info->L, info->L->top - 1, thread->stack + level); + /* ... thread obj */ + persist(info); /* ... thread obj */ + poppath(info); + } + lua_pop(info->L, 1); /* ... thread */ + poppath(info); + + /* If the thread isn't running this must be the default value, which is 1. */ + eris_assert(thread->nny == 1); + + /* Error jump info should only be set while thread is running. */ + eris_assert(thread->errorJmp == NULL); + + /* thread->oldpc always seems to be uninitialized, at least gdb always shows + * it as 0xbaadf00d when I set a breakpoint here. */ + + /* Write general information. */ + WRITE_VALUE(thread->status, uint8_t); + WRITE_VALUE(thread->nextid, uint64_t); + WRITE_VALUE(eris_savestackidx(thread, + eris_restorestack(thread, thread->errfunc)), size_t); + /* These are only used while a thread is being executed or can be deduced: + WRITE_VALUE(thread->nCcalls, uint16_t); + WRITE_VALUE(thread->allowhook, uint8_t); */ + + /* Hooks are not supported, bloody can of worms, those. + WRITE_VALUE(thread->hookmask, uint8_t); + WRITE_VALUE(thread->basehookcount, int); + WRITE_VALUE(thread->hookcount, int); */ + + if (thread->hook) { + /* TODO Warn that hooks are not persisted? */ + } + + /* Write call information (stack frames). In 5.2 CallInfo is stored in a + * linked list that originates in thead.base_ci. Upon initialization the + * thread.ci is set to thread.base_ci. During thread calls this is extended + * and always represents the tail of the callstack, though not necessarily of + * the linked list (which can be longer if the callstack was deeper earlier, + * but shrunk due to returns). */ + pushpath(info, ".callinfo"); + level = 0; + eris_assert(&thread->base_ci != thread->ci->next); + for (ci = &thread->base_ci; ci != thread->ci->next; ci = ci->next) { + pushpath(info, "[%d]", level++); + WRITE_VALUE(eris_savestackidx(thread, ci->func), size_t); + WRITE_VALUE(eris_savestackidx(thread, ci->top), size_t); + WRITE_VALUE(ci->nresults, int16_t); + WRITE_VALUE(ci->callstatus, uint8_t); + /* CallInfo.extra is used in two contexts: if L->status == LUA_YIELD and + * CallInfo is the one stored as L->ci, in which case ci->extra refers to + * the original value of L->ci->func, and when we have a yieldable pcall + * (ci->callstatus & CIST_YPCALL) where ci->extra holds the stack level + * of the function being called (see lua_pcallk). We save the ci->extra + * for L->ci after the loop, because we won't know which one it is when + * unpersisting. */ + if (ci->callstatus & CIST_YPCALL) { + WRITE_VALUE(eris_savestackidx(thread, + eris_restorestack(thread, ci->extra)), size_t); + } + + eris_assert(eris_isLua(ci) || (ci->callstatus & CIST_TAIL) == 0); + if (ci->callstatus & CIST_HOOKYIELD) { + eris_error(info, ERIS_ERR_HOOK); + } + + if (eris_isLua(ci)) { + const LClosure *lcl = eris_ci_func(ci); + WRITE_VALUE(eris_savestackidx(thread, ci->u.l.base), size_t); + WRITE_VALUE(ci->u.l.savedpc - lcl->p->code, size_t); + } + else { + WRITE_VALUE(ci->u.c.status, uint8_t); + + /* These are only used while a thread is being executed: + WRITE_VALUE(ci->u.c.old_errfunc, ptrdiff_t); + WRITE_VALUE(ci->u.c.old_allowhook, uint8_t); */ + + /* TODO Is this really right? Hooks may be a problem? */ + if (ci->callstatus & (CIST_YPCALL | CIST_YIELDED)) { + WRITE_VALUE(ci->u.c.ctx, int); + eris_assert(ci->u.c.k); + lua_pushcfunction(info->L, ci->u.c.k); /* ... thread func */ + persist(info); /* ... thread func/nil */ + lua_pop(info->L, 1); /* ... thread */ + } + } + + /* Write whether there's more to come. */ + WRITE_VALUE(ci->next == thread->ci->next, uint8_t); + + poppath(info); + } + /** See comment on ci->extra in loop. */ + if (thread->status == LUA_YIELD) { + WRITE_VALUE(eris_savestackidx(thread, + eris_restorestack(thread, thread->ci->extra)), size_t); + } + poppath(info); + + pushpath(info, ".openupval"); + lua_pushnil(info->L); /* ... thread nil */ + level = 0; + for (uvi = thread->openupval; + uvi != NULL; + uvi = eris_gch(uvi)->next) + { + UpVal *uv = eris_gco2uv(uvi); + pushpath(info, "[%d]", level++); + WRITE_VALUE(eris_savestackidx(thread, uv->v) + 1, size_t); + eris_setobj(info->L, info->L->top - 1, uv->v); /* ... thread obj */ + lua_pushlightuserdata(info->L, uv); /* ... thread obj id */ + persist_keyed(info, LUA_TUPVAL); /* ... thread obj */ + poppath(info); + } + WRITE_VALUE(0, size_t); + lua_pop(info->L, 1); /* ... thread */ + poppath(info); +} + +/* Used in u_thread to validate read stack positions. */ +#define validate(stackpos, inclmax) \ + if ((stackpos) < thread->stack || stackpos > (inclmax)) { \ + (stackpos) = thread->stack; \ + eris_error(info, ERIS_ERR_STACKBOUNDS); } + +/* I had so hoped to get by without any 'hacks', but I surrender. We mark the + * thread as incomplete to avoid the GC messing with it while we're building + * it. Otherwise it may try to shrink its stack. We do this by setting its + * stack field to null for every call that may trigger a GC run, since that + * field is what's used to determine whether threads should be shrunk. See + * lgc.c:699. Some of the locks could probably be joined (since nothing + * inbetween requires the stack field to be valid), but I prefer to keep the + * "invalid" blocks as small as possible to make it clearer. Also, locking and + * unlocking are really just variable assignments, so they're really cheap. */ +#define LOCK(L) (L->stack = NULL) +#define UNLOCK(L) (L->stack = stack) + +static void +u_thread(Info *info) { /* ... */ + lua_State* thread; + size_t level; + StkId stack, o; + + eris_checkstack(info->L, 3); + + thread = lua_newthread(info->L); /* ... thread */ + registerobject(info); + + /* Unpersist the stack. Read size first and adjust accordingly. */ + eris_reallocstack(thread, READ_VALUE(int)); + stack = thread->stack; /* After the realloc in case the address changes. */ + thread->top = thread->stack + READ_VALUE(size_t); + validate(thread->top, thread->stack_last); + + /* Read the elements one by one. */ + LOCK(thread); + pushpath(info, ".stack"); + UNLOCK(thread); + level = 0; + for (o = stack; o < thread->top; ++o) { + LOCK(thread); + pushpath(info, "[%d]", level++); + unpersist(info); /* ... thread obj */ + UNLOCK(thread); + eris_setobj(thread, o, info->L->top - 1); + lua_pop(info->L, 1); /* ... thread */ + LOCK(thread); + poppath(info); + UNLOCK(thread); + } + LOCK(thread); + poppath(info); + UNLOCK(thread); + + /* As in p_thread, just to make sure. */ + eris_assert(thread->nny == 1); + eris_assert(thread->errorJmp == NULL); + eris_assert(thread->hook == NULL); + + /* See comment in persist. */ + thread->oldpc = NULL; + + /* Read general information. */ + thread->status = READ_VALUE(uint8_t); + thread->nextid = READ_VALUE(uint64_t); + thread->errfunc = eris_savestack(thread, + eris_restorestackidx(thread, READ_VALUE(size_t))); + if (thread->errfunc) { + o = eris_restorestack(thread, thread->errfunc); + validate(o, thread->top); + if (eris_ttypenv(o) != LUA_TFUNCTION) { + eris_error(info, ERIS_ERR_THREADERRF); + } + } + /* These are only used while a thread is being executed or can be deduced: + thread->nCcalls = READ_VALUE(uint16_t); + thread->allowhook = READ_VALUE(uint8_t); */ + eris_assert(thread->allowhook == 1); + + /* Not supported. + thread->hookmask = READ_VALUE(uint8_t); + thread->basehookcount = READ_VALUE(int); + thread->hookcount = READ_VALUE(int); */ + + /* Read call information (stack frames). */ + LOCK(thread); + pushpath(info, ".callinfo"); + UNLOCK(thread); + thread->ci = &thread->base_ci; + level = 0; + for (;;) { + LOCK(thread); + pushpath(info, "[%d]", level++); + UNLOCK(thread); + thread->ci->func = eris_restorestackidx(thread, READ_VALUE(size_t)); + validate(thread->ci->func, thread->top - 1); + thread->ci->top = eris_restorestackidx(thread, READ_VALUE(size_t)); + validate(thread->ci->top, thread->stack_last); + thread->ci->nresults = READ_VALUE(int16_t); + thread->ci->callstatus = READ_VALUE(uint8_t); + /** See comment in p_thread. */ + if (thread->ci->callstatus & CIST_YPCALL) { + thread->ci->extra = eris_savestack(thread, + eris_restorestackidx(thread, READ_VALUE(size_t))); + o = eris_restorestack(thread, thread->ci->extra); + validate(o, thread->top); + if (eris_ttypenv(o) != LUA_TFUNCTION) { + eris_error(info, ERIS_ERR_THREADCI); + } + } + + if (eris_isLua(thread->ci)) { + LClosure *lcl = eris_ci_func(thread->ci); + thread->ci->u.l.base = eris_restorestackidx(thread, READ_VALUE(size_t)); + validate(thread->ci->u.l.base, thread->top); + thread->ci->u.l.savedpc = lcl->p->code + READ_VALUE(size_t); + if (thread->ci->u.l.savedpc < lcl->p->code || + thread->ci->u.l.savedpc > lcl->p->code + lcl->p->sizecode) + { + thread->ci->u.l.savedpc = lcl->p->code; /* Just to be safe. */ + eris_error(info, ERIS_ERR_THREADPC); + } + } + else { + thread->ci->u.c.status = READ_VALUE(uint8_t); + + /* These are only used while a thread is being executed: + thread->ci->u.c.old_errfunc = READ_VALUE(ptrdiff_t); + thread->ci->u.c.old_allowhook = READ_VALUE(uint8_t); */ + thread->ci->u.c.old_errfunc = 0; + thread->ci->u.c.old_allowhook = 0; + + /* TODO Is this really right? */ + if (thread->ci->callstatus & (CIST_YPCALL | CIST_YIELDED)) { + thread->ci->u.c.ctx = READ_VALUE(int); + LOCK(thread); + unpersist(info); /* ... thread func? */ + UNLOCK(thread); + if (lua_iscfunction(info->L, -1)) { /* ... thread func */ + thread->ci->u.c.k = lua_tocfunction(info->L, -1); + } + else { + eris_error(info, ERIS_ERR_THREADCTX); + return; /* not reached */ + } + lua_pop(info->L, 1); /* ... thread */ + } + else { + thread->ci->u.c.ctx = 0; + thread->ci->u.c.k = NULL; + } + } + LOCK(thread); + poppath(info); + UNLOCK(thread); + + /* Read in value for check for next iteration. */ + if (READ_VALUE(uint8_t)) { + break; + } + else { + thread->ci = eris_extendCI(thread); + } + } + if (thread->status == LUA_YIELD) { + thread->ci->extra = eris_savestack(thread, + eris_restorestackidx(thread, READ_VALUE(size_t))); + o = eris_restorestack(thread, thread->ci->extra); + validate(o, thread->top); + if (eris_ttypenv(o) != LUA_TFUNCTION) { + eris_error(info, ERIS_ERR_THREADCI); + } + } + LOCK(thread); + poppath(info); + UNLOCK(thread); + + /* Get from context: only zero for dead threads, otherwise one. */ + thread->nCcalls = thread->status != LUA_OK || lua_gettop(thread) != 0; + + /* Proceed to open upvalues. These upvalues will already exist due to the + * functions using them having been unpersisted (they'll usually be in the + * stack of the thread). For this reason we store all previous references to + * the upvalue in a table that is returned when we try to unpersist an + * upvalue, so that we can adjust these references in here. */ + LOCK(thread); + pushpath(info, ".openupval"); + UNLOCK(thread); + level = 0; + for (;;) { + UpVal *nuv; + StkId stk; + /* Get the position of the upvalue on the stack. As a special value we pass + * zero to indicate there are no more upvalues. */ + const size_t offset = READ_VALUE(size_t); + if (offset == 0) { + break; + } + LOCK(thread); + pushpath(info, "[%d]", level); + UNLOCK(thread); + stk = eris_restorestackidx(thread, offset - 1); + validate(stk, thread->top - 1); + LOCK(thread); + unpersist(info); /* ... thread tbl */ + UNLOCK(thread); + eris_assert(lua_type(info->L, -1) == LUA_TTABLE); + + /* Create the open upvalue either way. */ + LOCK(thread); + nuv = eris_findupval(thread, stk); + UNLOCK(thread); + + /* Then check if we need to patch some references. */ + lua_rawgeti(info->L, -1, UVTREF); /* ... thread tbl lcl/nil */ + if (!lua_isnil(info->L, -1)) { /* ... thread tbl lcl */ + int i, n; + eris_assert(lua_type(info->L, -1) == LUA_TFUNCTION); + /* Already exists, replace it. To do this we have to patch all the + * references to the already existing one, which we added to the table in + * u_closure. */ + lua_pop(info->L, 1); /* ... thread tbl */ + for (i = UVTREF, n = luaL_len(info->L, -1); i <= n; i += 2) { + LClosure *cl; + int nup; + lua_rawgeti(info->L, -1, i); /* ... thread tbl lcl */ + cl = eris_clLvalue(info->L->top - 1); + lua_pop(info->L, 1); /* ... thread tbl */ + lua_rawgeti(info->L, -1, i + 1); /* ... thread tbl nup */ + nup = lua_tointeger(info->L, -1); + lua_pop(info->L, 1); /* ... thread tbl */ + /* Open the upvalue by pointing to the stack and register in GC. */ + cl->upvals[nup - 1] = nuv; + luaC_objbarrier(info->L, cl, nuv); + } + } + else { /* ... thread tbl nil */ + eris_assert(lua_isnil(info->L, -1)); + lua_pop(info->L, 1); /* ... thread tbl */ + } + + /* Store open upvalue in table for future references. */ + LOCK(thread); + lua_pop(info->L, 1); /* ... thread */ + poppath(info); + UNLOCK(thread); + } + poppath(info); + + eris_assert(lua_type(info->L, -1) == LUA_TTHREAD); +} + +#undef UNLOCK +#undef LOCK + +#undef validate + +/* +** ============================================================================ +** Top-level delegator. +** ============================================================================ +*/ + +static void +persist_typed(Info *info, int type) { /* perms reftbl ... obj */ + eris_ifassert(const int top = lua_gettop(info->L)); + if (info->level >= info->maxComplexity) { + eris_error(info, ERIS_ERR_COMPLEXITY); + } + ++info->level; + + WRITE_VALUE(type, int); + switch(type) { + case LUA_TBOOLEAN: + p_boolean(info); + break; + case LUA_TLIGHTUSERDATA: + p_pointer(info); + break; + case LUA_TNUMBER: + p_number(info); + break; + case LUA_TSTRING: + p_string(info); + break; + case LUA_TTABLE: + p_table(info); + break; + case LUA_TFUNCTION: + p_closure(info); + break; + case LUA_TUSERDATA: + p_userdata(info); + break; + case LUA_TTHREAD: + p_thread(info); + break; + case LUA_TPROTO: + p_proto(info); + break; + case LUA_TUPVAL: + p_upval(info); + break; + default: + eris_error(info, ERIS_ERR_TYPEP, type); + } /* perms reftbl ... obj */ + + --info->level; + eris_assert(top == lua_gettop(info->L)); +} + +/* Second-level delegating persist function, used for cases when persisting + * data that's stored in the reftable with a key that is not the data itself, + * namely upvalues and protos. */ +static void +persist_keyed(Info *info, int type) { /* perms reftbl ... obj refkey */ + eris_checkstack(info->L, 2); + + /* Keep a copy of the key for pushing it to the reftable, if necessary. */ + lua_pushvalue(info->L, -1); /* perms reftbl ... obj refkey refkey */ + + /* If the object has already been written, write a reference to it. */ + lua_rawget(info->L, REFTIDX); /* perms reftbl ... obj refkey ref? */ + if (!lua_isnil(info->L, -1)) { /* perms reftbl ... obj refkey ref */ + const int reference = lua_tointeger(info->L, -1); + WRITE_VALUE(reference + ERIS_REFERENCE_OFFSET, int); + lua_pop(info->L, 2); /* perms reftbl ... obj */ + return; + } /* perms reftbl ... obj refkey nil */ + lua_pop(info->L, 1); /* perms reftbl ... obj refkey */ + + /* Copy the refkey for the perms check below. */ + lua_pushvalue(info->L, -1); /* perms reftbl ... obj refkey refkey */ + + /* Put the value in the reference table. This creates an entry pointing from + * the object (or its key) to the id the object is referenced by. */ + lua_pushinteger(info->L, ++(info->refcount)); + /* perms reftbl ... obj refkey refkey ref */ + lua_rawset(info->L, REFTIDX); /* perms reftbl ... obj refkey */ + + /* At this point, we'll give the permanents table a chance to play. */ + lua_gettable(info->L, PERMIDX); /* perms reftbl ... obj permkey? */ + if (!lua_isnil(info->L, -1)) { /* perms reftbl ... obj permkey */ + type = lua_type(info->L, -2); + /* Prepend permanent "type" so that we know it's a permtable key. This will + * trigger u_permanent when unpersisting. Also write the original type, so + * that we can verify what we get in the permtable when unpersisting is of + * the same kind we had when persisting. */ + WRITE_VALUE(ERIS_PERMANENT, int); + WRITE_VALUE(type, uint8_t); + persist(info); /* perms reftbl ... obj permkey */ + lua_pop(info->L, 1); /* perms reftbl ... obj */ + } + else { /* perms reftbl ... obj nil */ + /* No entry in the permtable for this object, persist it directly. */ + lua_pop(info->L, 1); /* perms reftbl ... obj */ + persist_typed(info, type); /* perms reftbl ... obj */ + } /* perms reftbl ... obj */ +} + +/* Top-level delegating persist function. */ +static void +persist(Info *info) { /* perms reftbl ... obj */ + /* Grab the object's type. */ + const int type = lua_type(info->L, -1); + + /* If the object is nil, only write its type. */ + if (type == LUA_TNIL) { + WRITE_VALUE(type, int); + } + /* Write simple values directly, because writing a "reference" would take up + * just as much space and we can save ourselves work this way. */ + else if (type == LUA_TBOOLEAN || + type == LUA_TLIGHTUSERDATA || + type == LUA_TNUMBER) + { + persist_typed(info, type); /* perms reftbl ... obj */ + } + /* For all non-simple values we keep a record in the reftable, so that we + * keep references alive across persisting and unpersisting an object. This + * has the nice side-effect of saving some space. */ + else { + eris_checkstack(info->L, 1); + lua_pushvalue(info->L, -1); /* perms reftbl ... obj obj */ + persist_keyed(info, type); /* perms reftbl ... obj */ + } +} + +/** ======================================================================== */ + +static void +u_permanent(Info *info) { /* perms reftbl ... */ + const int type = READ_VALUE(uint8_t); + /* Reserve reference to avoid the key going first. */ + const int reference = ++(info->refcount); + eris_checkstack(info->L, 1); + unpersist(info); /* perms reftbl ... permkey */ + lua_gettable(info->L, PERMIDX); /* perms reftbl ... obj? */ + if (lua_isnil(info->L, -1)) { /* perms reftbl ... nil */ + /* Since we may need permanent values to rebuild other structures, namely + * closures and threads, we cannot allow perms to fail unpersisting. */ + eris_error(info, ERIS_ERR_SPER_UPERMNIL); + } + else if (lua_type(info->L, -1) != type) { /* perms reftbl ... :( */ + /* For the same reason that we cannot allow nil we must also require the + * unpersisted value to be of the correct type. */ + const char *want = kTypenames[type]; + const char *have = kTypenames[lua_type(info->L, -1)]; + eris_error(info, ERIS_ERR_SPER_UPERM, want, have); + } /* perms reftbl ... obj */ + /* Create the entry in the reftable. */ + lua_pushvalue(info->L, -1); /* perms reftbl ... obj obj */ + lua_rawseti(info->L, REFTIDX, reference); /* perms reftbl ... obj */ +} + +static void +unpersist(Info *info) { /* perms reftbl ... */ + eris_ifassert(const int top = lua_gettop(info->L)); + if (info->level >= info->maxComplexity) { + eris_error(info, ERIS_ERR_COMPLEXITY); + } + ++info->level; + + eris_checkstack(info->L, 1); + { + const int typeOrReference = READ_VALUE(int); + if (typeOrReference > ERIS_REFERENCE_OFFSET) { + const int reference = typeOrReference - ERIS_REFERENCE_OFFSET; + lua_rawgeti(info->L, REFTIDX, reference); /* perms reftbl ud ... obj? */ + if (lua_isnil(info->L, -1)) { /* perms reftbl ud ... :( */ + eris_error(info, ERIS_ERR_REF, reference); + } /* perms reftbl ud ... obj */ + } + else { + const int type = typeOrReference; + switch (type) { + case LUA_TNIL: + lua_pushnil(info->L); + break; + case LUA_TBOOLEAN: + u_boolean(info); + break; + case LUA_TLIGHTUSERDATA: + u_pointer(info); + break; + case LUA_TNUMBER: + u_number(info); + break; + case LUA_TSTRING: + u_string(info); + break; + case LUA_TTABLE: + u_table(info); + break; + case LUA_TFUNCTION: + u_closure(info); + break; + case LUA_TUSERDATA: + u_userdata(info); + break; + case LUA_TTHREAD: + u_thread(info); + break; + case LUA_TPROTO: + u_proto(info); + break; + case LUA_TUPVAL: + u_upval(info); + break; + case ERIS_PERMANENT: + u_permanent(info); + break; + default: + eris_error(info, ERIS_ERR_TYPEU, type); + } /* perms reftbl ... obj? */ + } + } + + --info->level; + eris_assert(top + 1 == lua_gettop(info->L)); +} + +/* }======================================================================== */ + +/* +** {=========================================================================== +** Writer and reader implementation for library calls. +** ============================================================================ +*/ + +/* Implementation note: we use the MBuffer struct, but we don't use the built- + * in reallocation functions since we'll keep our working copy on the stack, to + * allow for proper collection if we have to throw an error. This is very much + * what the auxlib does with its buffer functionality. Which we don't use since + * we cannot guarantee stack balance inbetween calls to luaL_add*. */ + +static int +writer(lua_State *L, const void *p, size_t sz, void *ud) { + /* perms reftbl buff path? ... */ + const char *value = (const char*)p; + Mbuffer *buff = (Mbuffer*)ud; + const size_t size = eris_bufflen(buff); + const size_t capacity = eris_sizebuffer(buff); + if (capacity - size < sz) { + size_t newcapacity = capacity * 2; /* overflow checked below */ + if (newcapacity - size < sz) { + newcapacity = capacity + sz; /* overflow checked below */ + } + if (newcapacity <= capacity) { + /* Overflow in capacity, buffer size limit reached. */ + return 1; + } else { + char *newbuff; + eris_checkstack(L, 1); + newbuff = (char*)lua_newuserdata(L, newcapacity * sizeof(char)); + /* perms reftbl buff path? ... nbuff */ + memcpy(newbuff, eris_buffer(buff), eris_bufflen(buff)); + lua_replace(L, BUFFIDX); /* perms reftbl nbuff path? ... */ + eris_buffer(buff) = newbuff; + eris_sizebuffer(buff) = newcapacity; + } + } + memcpy(&eris_buffer(buff)[eris_bufflen(buff)], value, sz); + eris_bufflen(buff) += sz; + return 0; +} + +/** ======================================================================== */ + +/* Readonly, interface compatible with MBuffer macros. */ +typedef struct RBuffer { + const char *buffer; + size_t n; + size_t buffsize; +} RBuffer; + +static const char* +reader(lua_State *L, void *ud, size_t *sz) { + RBuffer *buff = (RBuffer*)ud; + (void) L; /* unused */ + if (eris_bufflen(buff) == 0) { + return NULL; + } + *sz = eris_bufflen(buff); + eris_bufflen(buff) = 0; + return eris_buffer(buff); +} + +/* }======================================================================== */ + +/* +** {=========================================================================== +** Library functions. +** ============================================================================ +*/ + +static void +p_header(Info *info) { + WRITE_RAW(kHeader, HEADER_LENGTH); + WRITE_VALUE(sizeof(lua_Number), uint8_t); + WRITE_VALUE(kHeaderNumber, lua_Number); + WRITE_VALUE(sizeof(int), uint8_t); + WRITE_VALUE(sizeof(size_t), uint8_t); +} + +static void +u_header(Info *info) { + char header[HEADER_LENGTH]; + uint8_t number_size; + READ_RAW(header, HEADER_LENGTH); + if (strncmp(kHeader, header, HEADER_LENGTH)) { + luaL_error(info->L, "invalid data"); + } + number_size = READ_VALUE(uint8_t); + if (number_size == 0) { + /* Old 64-bit versions of eris wrote '\0' and then three random bytes. */ + /* We skip them here for backwards compatibility. */ + char throw_away[3]; + READ_RAW(throw_away, 3); + + number_size = READ_VALUE(uint8_t); + } + if (number_size != sizeof(lua_Number)) { + luaL_error(info->L, "incompatible floating point type"); + } + /* In this case we really do want floating point equality. */ + if (READ_VALUE(lua_Number) != kHeaderNumber) { + luaL_error(info->L, "incompatible floating point representation"); + } + info->u.upi.sizeof_int = READ_VALUE(uint8_t); + info->u.upi.sizeof_size_t = READ_VALUE(uint8_t); +} + +static void +unchecked_persist(lua_State *L, lua_Writer writer, void *ud) { + Info info; /* perms buff rootobj */ + info.L = L; + info.level = 0; + info.refcount = 0; + info.maxComplexity = kMaxComplexity; + info.passIOToPersist = kPassIOToPersist; + info.generatePath = kGeneratePath; + info.u.pi.writer = writer; + info.u.pi.ud = ud; + info.u.pi.metafield = kPersistKey; + info.u.pi.writeDebugInfo = kWriteDebugInformation; + + eris_checkstack(L, 3); + + if (get_setting(L, (void*)&kSettingMaxComplexity)) { + /* perms buff rootobj value */ + info.maxComplexity = lua_tounsigned(L, -1); + lua_pop(L, 1); /* perms buff rootobj */ + } + if (get_setting(L, (void*)&kSettingGeneratePath)) { + /* perms buff rootobj value */ + info.generatePath = lua_toboolean(L, -1); + lua_pop(L, 1); /* perms buff rootobj */ + } + if (get_setting(L, (void*)&kSettingPassIOToPersist)) { + /* perms buff rootobj value */ + info.passIOToPersist = lua_toboolean(L, -1); + lua_pop(L, 1); /* perms buff rootobj */ + } + if (get_setting(L, (void*)&kSettingMetafield)) {/* perms buff rootobj value */ + info.u.pi.metafield = lua_tostring(L, -1); + lua_pop(L, 1); /* perms buff rootobj */ + } + if (get_setting(L, (void*)&kSettingWriteDebugInfo)) { + /* perms buff rootobj value */ + info.u.pi.writeDebugInfo = lua_toboolean(L, -1); + lua_pop(L, 1); /* perms buff rootobj */ + } + + lua_newtable(L); /* perms buff rootobj reftbl */ + lua_insert(L, REFTIDX); /* perms reftbl buff rootobj */ + if (info.generatePath) { + lua_newtable(L); /* perms reftbl buff rootobj path */ + lua_insert(L, PATHIDX); /* perms reftbl buff path rootobj */ + pushpath(&info, "root"); + } + + /* Populate perms table with Lua internals. */ + lua_pushvalue(L, PERMIDX); /* perms reftbl buff path? rootobj perms */ + populateperms(L, false); + lua_pop(L, 1); /* perms reftbl buff path? rootobj */ + + p_header(&info); + persist(&info); /* perms reftbl buff path? rootobj */ + + if (info.generatePath) { /* perms reftbl buff path rootobj */ + lua_remove(L, PATHIDX); /* perms reftbl buff rootobj */ + } /* perms reftbl buff rootobj */ + lua_remove(L, REFTIDX); /* perms buff rootobj */ +} + +static void +unchecked_unpersist(lua_State *L, lua_Reader reader, void *ud) {/* perms str? */ + Info info; + info.L = L; + info.level = 0; + info.refcount = 0; + info.maxComplexity = kMaxComplexity; + info.generatePath = kGeneratePath; + info.passIOToPersist = kPassIOToPersist; + eris_init(L, &info.u.upi.zio, reader, ud); + + eris_checkstack(L, 3); + + if (get_setting(L, (void*)&kSettingMaxComplexity)) { + /* perms buff rootobj value */ + info.maxComplexity = lua_tounsigned(L, -1); + lua_pop(L, 1); /* perms buff rootobj */ + } + if (get_setting(L, (void*)&kSettingGeneratePath)) { + /* perms buff? rootobj value */ + info.generatePath = lua_toboolean(L, -1); + lua_pop(L, 1); /* perms buff? rootobj */ + } + if (get_setting(L, (void*)&kSettingPassIOToPersist)) { /* perms str? value */ + info.passIOToPersist = lua_toboolean(L, -1); + lua_pop(L, 1); /* perms str? */ + } + + lua_newtable(L); /* perms str? reftbl */ + lua_insert(L, REFTIDX); /* perms reftbl str? */ + if (info.generatePath) { + /* Make sure the path is always at index 4, so that it's the same for + * persist and unpersist. */ + lua_pushnil(L); /* perms reftbl str? nil */ + lua_insert(L, BUFFIDX); /* perms reftbl nil str? */ + lua_newtable(L); /* perms reftbl nil str? path */ + lua_insert(L, PATHIDX); /* perms reftbl nil path str? */ + pushpath(&info, "root"); + } + + /* Populate perms table with Lua internals. */ + lua_pushvalue(L, PERMIDX); /* perms reftbl nil? path? str? perms */ + populateperms(L, true); + lua_pop(L, 1); /* perms reftbl nil? path? str? */ + + u_header(&info); + unpersist(&info); /* perms reftbl nil? path? str? rootobj */ + if (info.generatePath) { /* perms reftbl nil path str? rootobj */ + lua_remove(L, PATHIDX); /* perms reftbl nil str? rootobj */ + lua_remove(L, BUFFIDX); /* perms reftbl str? rootobj */ + } /* perms reftbl str? rootobj */ + lua_remove(L, REFTIDX); /* perms str? rootobj */ +} + +/** ======================================================================== */ + +static int +l_persist(lua_State *L) { /* perms? rootobj? ...? */ + Mbuffer buff; + + /* See if we have anything at all. */ + luaL_checkany(L, 1); + + /* If we only have one object we assume it is the root object and that there + * is no perms table, so we create an empty one for internal use. */ + if (lua_gettop(L) == 1) { /* rootobj */ + eris_checkstack(L, 1); + lua_newtable(L); /* rootobj perms */ + lua_insert(L, PERMIDX); /* perms rootobj */ + } + else { + luaL_checktype(L, 1, LUA_TTABLE); /* perms rootobj? ...? */ + luaL_checkany(L, 2); /* perms rootobj ...? */ + lua_settop(L, 2); /* perms rootobj */ + } + eris_checkstack(L, 1); + lua_pushnil(L); /* perms rootobj buff */ + lua_insert(L, 2); /* perms buff rootobj */ + + eris_initbuffer(L, &buff); + eris_bufflen(&buff) = 0; /* Not initialized by initbuffer... */ + + unchecked_persist(L, writer, &buff); /* perms buff rootobj */ + + /* Copy the buffer as the result string before removing it, to avoid the data + * being garbage collected. */ + lua_pushlstring(L, eris_buffer(&buff), eris_bufflen(&buff)); + /* perms buff rootobj str */ + + return 1; +} + +static int +l_unpersist(lua_State *L) { /* perms? str? ...? */ + RBuffer buff; + + /* See if we have anything at all. */ + luaL_checkany(L, 1); + + /* If we only have one object we assume it is the root object and that there + * is no perms table, so we create an empty one for internal use. */ + if (lua_gettop(L) == 1) { /* str? */ + eris_checkstack(L, 1); + lua_newtable(L); /* str? perms */ + lua_insert(L, PERMIDX); /* perms str? */ + } + else { + luaL_checktype(L, 1, LUA_TTABLE); /* perms str? ...? */ + } + eris_buffer(&buff) = luaL_checklstring(L, 2, &eris_bufflen(&buff)); + eris_sizebuffer(&buff) = eris_bufflen(&buff); /* perms str ...? */ + lua_settop(L, 2); /* perms str */ + + unchecked_unpersist(L, reader, &buff); /* perms str rootobj */ + + return 1; +} + +#define IS(s) strncmp(s, name, length < sizeof(s) ? length : sizeof(s)) == 0 + +static int +l_settings(lua_State *L) { /* name value? ...? */ + size_t length; + const char *name = luaL_checklstring(L, 1, &length); + if (lua_isnone(L, 2)) { /* name ...? */ + lua_settop(L, 1); /* name */ + /* Get the current setting value and return it. */ + if (IS(kSettingMetafield)) { + if (!get_setting(L, (void*)&kSettingMetafield)) { + lua_pushstring(L, kPersistKey); + } + } + else if (IS(kSettingPassIOToPersist)) { + if (!get_setting(L, (void*)&kSettingPassIOToPersist)) { + lua_pushboolean(L, kPassIOToPersist); + } + } + else if (IS(kSettingWriteDebugInfo)) { + if (!get_setting(L, (void*)&kSettingWriteDebugInfo)) { + lua_pushboolean(L, kWriteDebugInformation); + } + } + else if (IS(kSettingGeneratePath)) { + if (!get_setting(L, (void*)&kSettingGeneratePath)) { + lua_pushboolean(L, kGeneratePath); + } + } + else if (IS(kSettingMaxComplexity)) { + if (!get_setting(L, (void*)&kSettingMaxComplexity)) { + lua_pushunsigned(L, kMaxComplexity); + } + } + else { + return luaL_argerror(L, 1, "no such setting"); + } /* name value */ + return 1; + } + else { /* name value ...? */ + lua_settop(L, 2); /* name value */ + /* Set a new value for the setting. */ + if (IS(kSettingMetafield)) { + luaL_optstring(L, 2, NULL); + set_setting(L, (void*)&kSettingMetafield); + } + else if (IS(kSettingPassIOToPersist)) { + luaL_opt(L, checkboolean, 2, false); + set_setting(L, (void*)&kSettingPassIOToPersist); + } + else if (IS(kSettingWriteDebugInfo)) { + luaL_opt(L, checkboolean, 2, false); + set_setting(L, (void*)&kSettingWriteDebugInfo); + } + else if (IS(kSettingGeneratePath)) { + luaL_opt(L, checkboolean, 2, false); + set_setting(L, (void*)&kSettingGeneratePath); + } + else if (IS(kSettingMaxComplexity)) { + luaL_optunsigned(L, 2, 0); + set_setting(L, (void*)&kSettingMaxComplexity); + } + else { + return luaL_argerror(L, 1, "no such setting"); + } /* name */ + return 0; + } +} + +#undef IS + +/** ======================================================================== */ + +static luaL_Reg erislib[] = { + { "persist", l_persist }, + { "unpersist", l_unpersist }, + { "settings", l_settings }, + { NULL, NULL } +}; + +LUA_API int luaopen_eris(lua_State *L) { + luaL_newlib(L, erislib); + return 1; +} + +/* }======================================================================== */ + +/* +** {=========================================================================== +** Public API functions. +** ============================================================================ +*/ + +LUA_API void +eris_dump(lua_State *L, lua_Writer writer, void *ud) { /* perms? rootobj? */ + if (lua_gettop(L) > 2) { + luaL_error(L, "too many arguments"); + } + luaL_checktype(L, 1, LUA_TTABLE); /* perms rootobj? */ + luaL_checkany(L, 2); /* perms rootobj */ + lua_pushnil(L); /* perms rootobj nil */ + lua_insert(L, -2); /* perms nil rootobj */ + unchecked_persist(L, writer, ud); /* perms nil rootobj */ + lua_remove(L, -2); /* perms rootobj */ +} + +LUA_API void +eris_undump(lua_State *L, lua_Reader reader, void *ud) { /* perms? */ + if (lua_gettop(L) > 1) { + luaL_error(L, "too many arguments"); + } + luaL_checktype(L, 1, LUA_TTABLE); /* perms */ + unchecked_unpersist(L, reader, ud); /* perms rootobj */ +} + +/** ======================================================================== */ + +LUA_API void +eris_persist(lua_State *L, int perms, int value) { /* ...? */ + perms = lua_absindex(L, perms); + value = lua_absindex(L, value); + eris_checkstack(L, 3); + lua_pushcfunction(L, l_persist); /* ... l_persist */ + lua_pushvalue(L, perms); /* ... l_persist perms */ + lua_pushvalue(L, value); /* ... l_persist perms rootobj */ + lua_call(L, 2, 1); /* ... str */ +} + +LUA_API void +eris_unpersist(lua_State *L, int perms, int value) { /* ... */ + perms = lua_absindex(L, perms); + value = lua_absindex(L, value); + eris_checkstack(L, 3); + lua_pushcfunction(L, l_unpersist); /* ... l_unpersist */ + lua_pushvalue(L, perms); /* ... l_unpersist perms */ + lua_pushvalue(L, value); /* ... l_unpersist perms str */ + lua_call(L, 2, 1); /* ... rootobj */ +} + +LUA_API void +eris_get_setting(lua_State *L, const char *name) { /* ... */ + eris_checkstack(L, 2); + lua_pushcfunction(L, l_settings); /* ... l_settings */ + lua_pushstring(L, name); /* ... l_settings name */ + lua_call(L, 1, 1); /* ... value */ +} + +LUA_API void +eris_set_setting(lua_State *L, const char *name, int value) { /* ... */ + value = lua_absindex(L, value); + eris_checkstack(L, 3); + lua_pushcfunction(L, l_settings); /* ... l_settings */ + lua_pushstring(L, name); /* ... l_settings name */ + lua_pushvalue(L, value); /* ... l_settings name value */ + lua_call(L, 2, 0); /* ... */ +} + +/* }======================================================================== */ + diff --git a/luprex/ext/eris-master/src/eris.h b/luprex/ext/eris-master/src/eris.h new file mode 100644 index 00000000..0779d0d3 --- /dev/null +++ b/luprex/ext/eris-master/src/eris.h @@ -0,0 +1,153 @@ +/* +Eris - Heavy-duty persistence for Lua 5.2.4 - Based on Pluto +Copyright (c) 2013-2015 by Florian Nuecke. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +/* lua.h must be included before this file */ + +#ifndef ERIS_H +#define ERIS_H + +#define ERIS_VERSION_MAJOR "1" +#define ERIS_VERSION_MINOR "1" +#define ERIS_VERSION_NUM 101 +#define ERIS_VERSION_RELEASE "1" + +/* +** ================================================================== +** API +** ================================================================== +*/ + +/** + * This provides an interface to Eris' persist functionality for writing in + * an arbitrary way, using a writer. + * + * When called, the stack in 'L' must look like this: + * 1: perms:table + * 2: value:any + * + * 'writer' is the writer function used to store all data, 'ud' is passed to + * the writer function whenever it is called. + * + * [-0, +0, e] + */ +LUA_API void eris_dump(lua_State* L, lua_Writer writer, void* ud); + +/** + * This provides an interface to Eris' unpersist functionality for reading + * in an arbitrary way, using a reader. + * + * When called, the stack in 'L' must look like this: + * 1: perms:table + * + * 'reader' is the reader function used to read all data, 'ud' is passed to + * the reader function whenever it is called. + * + * The result of the operation will be pushed onto the stack. + * + * [-0, +1, e] + */ +LUA_API void eris_undump(lua_State* L, lua_Reader reader, void* ud); + +/** + * This is a stack-based alternative to eris_dump. + * + * It expects the perms table at the specified index 'perms' and the value to + * persist at the specified index 'value'. It will push the resulting string + * onto the stack on success. + * + * [-0, +1, e] + */ +LUA_API void eris_persist(lua_State* L, int perms, int value); + +/** + * This is a stack-based alternative to eris_undump. + * + * It expects the perms table at the specified index 'perms' and the string + * containing persisted data at the specified index 'value'. It will push the + * resulting value onto the stack on success. + * + * [-0, +1, e] + */ +LUA_API void eris_unpersist(lua_State* L, int perms, int value); + +/** + * Pushes the current value of a setting onto the stack. + * + * The name is the name of the setting to get the value for: + * - 'debug' whether to write debug information when persisting function + * prototypes (line numbers, local variable names, upvalue names). + * - 'maxrec' the maximum complexity of objects we support (the nesting level + * of tables, for example). This can be useful to avoid segmentation + * faults due to too deep recursion when working with user-provided + * data. + * - 'path' whether to generate a "path" used to indicate where in an object + * an error occurred. This adds considerable overhead and should + * only be used to debug errors as they appear. + * - 'spio' whether to pass IO objects along as light userdata to special + * persistence functions. When persisting this will pass along the + * lua_Writer and its void* in addition to the original object, when + * unpersisting it will pass along a ZIO*. + * - 'spkey' the name of the field in the metatable of tables and userdata + * used to control persistence (on/off or special persistence). + * + * If an unknown name is specified this will throw an error. + * + * [-0, +1, e] + */ +LUA_API void eris_get_setting(lua_State *L, const char *name); + +/** + * Changes the value of a setting to a value on the stack. + * + * For the available settings see eris_set_setting(). This will get the new + * value from the specified stack index 'value'. If the type does not match + * this will throw an error. Specify a nil value to reset the setting to its + * default value. + * + * [-0, +0, e] + */ +LUA_API void eris_set_setting(lua_State *L, const char *name, int value); + +/* +** ================================================================== +** Library installer +** ================================================================== +*/ + +/** + * This pushes a table with the two functions 'persist' and 'unpersist': + * persist([perms,] value) + * Where 'perms' is a table with "permanent" objects and 'value' is the + * value that should be persisted. Returns the string with persisted data. + * If only one value is given, the perms table is assumed to be empty. + * + * unpersist([perms,] value) + * Where 'perms' is a table with the inverse mapping as used when + * persisting the data via persist() and 'value' is the string with the + * persisted data returned by persist(). Returns the unpersisted value. + * If only one value is given, the perms table is assumed to be empty. + */ +LUA_API int luaopen_eris(lua_State* L); + +#endif + diff --git a/luprex/ext/eris-master/src/lapi.c b/luprex/ext/eris-master/src/lapi.c new file mode 100644 index 00000000..f8390b78 --- /dev/null +++ b/luprex/ext/eris-master/src/lapi.c @@ -0,0 +1,1397 @@ +/* +** $Id: lapi.c,v 2.171.1.1 2013/04/12 18:48:47 roberto Exp $ +** Lua API +** See Copyright Notice in lua.h +*/ + + +#include +#include + +#define lapi_c +#define LUA_CORE + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lundump.h" +#include "lvm.h" + + + +const char lua_ident[] = + "$LuaVersion: " LUA_COPYRIGHT " $" + "$LuaAuthors: " LUA_AUTHORS " $"; + + +/* value at a non-valid index */ +#define NONVALIDVALUE cast(TValue *, luaO_nilobject) + +/* corresponding test */ +#define isvalid(o) ((o) != luaO_nilobject) + +/* test for pseudo index */ +#define ispseudo(i) ((i) <= LUA_REGISTRYINDEX) + +/* test for valid but not pseudo index */ +#define isstackindex(i, o) (isvalid(o) && !ispseudo(i)) + +#define api_checkvalidindex(L, o) api_check(L, isvalid(o), "invalid index") + +#define api_checkstackindex(L, i, o) \ + api_check(L, isstackindex(i, o), "index not in the stack") + + +static TValue *index2addr (lua_State *L, int idx) { + CallInfo *ci = L->ci; + if (idx > 0) { + TValue *o = ci->func + idx; + api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index"); + if (o >= L->top) return NONVALIDVALUE; + else return o; + } + else if (!ispseudo(idx)) { /* negative index */ + api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index"); + return L->top + idx; + } + else if (idx == LUA_REGISTRYINDEX) + return &G(L)->l_registry; + else { /* upvalues */ + idx = LUA_REGISTRYINDEX - idx; + api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large"); + if (ttislcf(ci->func)) /* light C function? */ + return NONVALIDVALUE; /* it has no upvalues */ + else { + CClosure *func = clCvalue(ci->func); + return (idx <= func->nupvalues) ? &func->upvalue[idx-1] : NONVALIDVALUE; + } + } +} + + +/* +** to be called by 'lua_checkstack' in protected mode, to grow stack +** capturing memory errors +*/ +static void growstack (lua_State *L, void *ud) { + int size = *(int *)ud; + luaD_growstack(L, size); +} + + +LUA_API int lua_checkstack (lua_State *L, int size) { + int res; + CallInfo *ci = L->ci; + lua_lock(L); + if (L->stack_last - L->top > size) /* stack large enough? */ + res = 1; /* yes; check is OK */ + else { /* no; need to grow stack */ + int inuse = cast_int(L->top - L->stack) + EXTRA_STACK; + if (inuse > LUAI_MAXSTACK - size) /* can grow without overflow? */ + res = 0; /* no */ + else /* try to grow stack */ + res = (luaD_rawrunprotected(L, &growstack, &size) == LUA_OK); + } + if (res && ci->top < L->top + size) + ci->top = L->top + size; /* adjust frame top */ + lua_unlock(L); + return res; +} + + +LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { + int i; + if (from == to) return; + lua_lock(to); + api_checknelems(from, n); + api_check(from, G(from) == G(to), "moving among independent states"); + api_check(from, to->ci->top - to->top >= n, "not enough elements to move"); + from->top -= n; + for (i = 0; i < n; i++) { + setobj2s(to, to->top++, from->top + i); + } + lua_unlock(to); +} + + +LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) { + lua_CFunction old; + lua_lock(L); + old = G(L)->panic; + G(L)->panic = panicf; + lua_unlock(L); + return old; +} + + +LUA_API const lua_Number *lua_version (lua_State *L) { + static const lua_Number version = LUA_VERSION_NUM; + if (L == NULL) return &version; + else return G(L)->version; +} + + + +/* +** basic stack manipulation +*/ + + +/* +** convert an acceptable stack index into an absolute index +*/ +LUA_API int lua_absindex (lua_State *L, int idx) { + return (idx > 0 || ispseudo(idx)) + ? idx + : cast_int(L->top - L->ci->func + idx); +} + + +LUA_API int lua_gettop (lua_State *L) { + return cast_int(L->top - (L->ci->func + 1)); +} + + +LUA_API void lua_settop (lua_State *L, int idx) { + StkId func = L->ci->func; + lua_lock(L); + if (idx >= 0) { + api_check(L, idx <= L->stack_last - (func + 1), "new top too large"); + while (L->top < (func + 1) + idx) + setnilvalue(L->top++); + L->top = (func + 1) + idx; + } + else { + api_check(L, -(idx+1) <= (L->top - (func + 1)), "invalid new top"); + L->top += idx+1; /* `subtract' index (index is negative) */ + } + lua_unlock(L); +} + + +LUA_API void lua_remove (lua_State *L, int idx) { + StkId p; + lua_lock(L); + p = index2addr(L, idx); + api_checkstackindex(L, idx, p); + while (++p < L->top) setobjs2s(L, p-1, p); + L->top--; + lua_unlock(L); +} + + +LUA_API void lua_insert (lua_State *L, int idx) { + StkId p; + StkId q; + lua_lock(L); + p = index2addr(L, idx); + api_checkstackindex(L, idx, p); + for (q = L->top; q > p; q--) /* use L->top as a temporary */ + setobjs2s(L, q, q - 1); + setobjs2s(L, p, L->top); + lua_unlock(L); +} + + +// Insert specified number of 'nil' at base of the stack. +LUA_API void lua_insert_frame (lua_State *L, int count) { + StkId p, q; + if (count > 0) { + lua_lock(L); + api_check(L, L->top + count <= L->stack_last, "Not enough space to insert frame"); + p = L->ci->func + 1; + for (q = L->top - 1; q >= p; q--) + setobjs2s(L, q + count, q); + L->top += count; + for (q = p + count; p < q; p++) + setnilvalue(p); + lua_unlock(L); + } +} + + +static void moveto (lua_State *L, TValue *fr, int idx) { + TValue *to = index2addr(L, idx); + api_checkvalidindex(L, to); + setobj(L, to, fr); + if (idx < LUA_REGISTRYINDEX) /* function upvalue? */ + luaC_barrier(L, clCvalue(L->ci->func), fr); + /* LUA_REGISTRYINDEX does not need gc barrier + (collector revisits it before finishing collection) */ +} + + +LUA_API void lua_replace (lua_State *L, int idx) { + lua_lock(L); + api_checknelems(L, 1); + moveto(L, L->top - 1, idx); + L->top--; + lua_unlock(L); +} + + +LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) { + TValue *fr; + lua_lock(L); + fr = index2addr(L, fromidx); + moveto(L, fr, toidx); + lua_unlock(L); +} + + +LUA_API void lua_pushvalue (lua_State *L, int idx) { + lua_lock(L); + setobj2s(L, L->top, index2addr(L, idx)); + api_incr_top(L); + lua_unlock(L); +} + + + +/* +** access functions (stack -> C) +*/ + + +LUA_API int lua_type (lua_State *L, int idx) { + StkId o = index2addr(L, idx); + return (isvalid(o) ? ttypenv(o) : LUA_TNONE); +} + + +LUA_API const char *lua_typename (lua_State *L, int t) { + UNUSED(L); + return ttypename(t); +} + + +LUA_API int lua_iscfunction (lua_State *L, int idx) { + StkId o = index2addr(L, idx); + return (ttislcf(o) || (ttisCclosure(o))); +} + + +LUA_API int lua_isnumber (lua_State *L, int idx) { + TValue n; + const TValue *o = index2addr(L, idx); + return tonumber(o, &n); +} + + +LUA_API int lua_isstring (lua_State *L, int idx) { + int t = lua_type(L, idx); + return (t == LUA_TSTRING || t == LUA_TNUMBER); +} + + +LUA_API int lua_isuserdata (lua_State *L, int idx) { + const TValue *o = index2addr(L, idx); + return (ttisuserdata(o) || ttislightuserdata(o)); +} + + +LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { + StkId o1 = index2addr(L, index1); + StkId o2 = index2addr(L, index2); + return (isvalid(o1) && isvalid(o2)) ? luaV_rawequalobj(o1, o2) : 0; +} + + +LUA_API void lua_arith (lua_State *L, int op) { + StkId o1; /* 1st operand */ + StkId o2; /* 2nd operand */ + lua_lock(L); + if (op != LUA_OPUNM) /* all other operations expect two operands */ + api_checknelems(L, 2); + else { /* for unary minus, add fake 2nd operand */ + api_checknelems(L, 1); + setobjs2s(L, L->top, L->top - 1); + L->top++; + } + o1 = L->top - 2; + o2 = L->top - 1; + if (ttisnumber(o1) && ttisnumber(o2)) { + setnvalue(o1, luaO_arith(op, nvalue(o1), nvalue(o2))); + } + else + luaV_arith(L, o1, o1, o2, cast(TMS, op - LUA_OPADD + TM_ADD)); + L->top--; + lua_unlock(L); +} + +static int sortorder(int t) { + switch (t) { + case LUA_TNIL: return 0; + case LUA_TNUMBER: return 1; + case LUA_TSTRING: return 2; + case LUA_TBOOLEAN: return 3; + case LUA_TTABLE: return 1000000; + default: return t+1000; + } +} + +LUA_API int lua_genlt (lua_State *L, int index1, int index2) { + StkId o1, o2; + int i = 0; + lua_lock(L); /* may call tag method */ + o1 = index2addr(L, index1); + o2 = index2addr(L, index2); + int t1 = ttypenv(o1); + int t2 = ttypenv(o2); + if (t1 != t2) { + i = (sortorder(t1) < sortorder(t2)); + } else if (t1 == LUA_TNUMBER) { + i = luai_numlt(L, nvalue(o1), nvalue(o2)); + } else if (t1 == LUA_TSTRING) { + i = luaV_lessthan(L, o1, o2); + } else if (t1 == LUA_TBOOLEAN) { + i = bvalue(o1) < bvalue(o2); + } else { + return 0; + } + lua_unlock(L); + return i; +} + +LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { + StkId o1, o2; + int i = 0; + lua_lock(L); /* may call tag method */ + o1 = index2addr(L, index1); + o2 = index2addr(L, index2); + if (isvalid(o1) && isvalid(o2)) { + switch (op) { + case LUA_OPEQ: i = equalobj(L, o1, o2); break; + case LUA_OPLT: i = luaV_lessthan(L, o1, o2); break; + case LUA_OPLE: i = luaV_lessequal(L, o1, o2); break; + default: api_check(L, 0, "invalid option"); + } + } + lua_unlock(L); + return i; +} + + +LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *isnum) { + TValue n; + const TValue *o = index2addr(L, idx); + if (tonumber(o, &n)) { + if (isnum) *isnum = 1; + return nvalue(o); + } + else { + if (isnum) *isnum = 0; + return 0; + } +} + + +LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *isnum) { + TValue n; + const TValue *o = index2addr(L, idx); + if (tonumber(o, &n)) { + lua_Integer res; + lua_Number num = nvalue(o); + lua_number2integer(res, num); + if (isnum) *isnum = 1; + return res; + } + else { + if (isnum) *isnum = 0; + return 0; + } +} + + +LUA_API lua_Unsigned lua_tounsignedx (lua_State *L, int idx, int *isnum) { + TValue n; + const TValue *o = index2addr(L, idx); + if (tonumber(o, &n)) { + lua_Unsigned res; + lua_Number num = nvalue(o); + lua_number2unsigned(res, num); + if (isnum) *isnum = 1; + return res; + } + else { + if (isnum) *isnum = 0; + return 0; + } +} + + +LUA_API int lua_toboolean (lua_State *L, int idx) { + const TValue *o = index2addr(L, idx); + return !l_isfalse(o); +} + + +LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { + StkId o = index2addr(L, idx); + if (!ttisstring(o)) { + lua_lock(L); /* `luaV_tostring' may create a new string */ + if (!luaV_tostring(L, o)) { /* conversion failed? */ + if (len != NULL) *len = 0; + lua_unlock(L); + return NULL; + } + luaC_checkGC(L); + o = index2addr(L, idx); /* previous call may reallocate the stack */ + lua_unlock(L); + } + if (len != NULL) *len = tsvalue(o)->len; + return svalue(o); +} + + +LUA_API size_t lua_rawlen (lua_State *L, int idx) { + StkId o = index2addr(L, idx); + switch (ttypenv(o)) { + case LUA_TSTRING: return tsvalue(o)->len; + case LUA_TUSERDATA: return uvalue(o)->len; + case LUA_TTABLE: return luaH_getn(hvalue(o)); + default: return 0; + } +} + + +LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { + StkId o = index2addr(L, idx); + if (ttislcf(o)) return fvalue(o); + else if (ttisCclosure(o)) + return clCvalue(o)->f; + else return NULL; /* not a C function */ +} + + +LUA_API void *lua_touserdata (lua_State *L, int idx) { + StkId o = index2addr(L, idx); + switch (ttypenv(o)) { + case LUA_TUSERDATA: return (rawuvalue(o) + 1); + case LUA_TLIGHTUSERDATA: return pvalue(o); + default: return NULL; + } +} + + +LUA_API lua_State *lua_tothread (lua_State *L, int idx) { + StkId o = index2addr(L, idx); + return (!ttisthread(o)) ? NULL : thvalue(o); +} + + +LUA_API const void *lua_topointer (lua_State *L, int idx) { + StkId o = index2addr(L, idx); + switch (ttype(o)) { + case LUA_TTABLE: return hvalue(o); + case LUA_TLCL: return clLvalue(o); + case LUA_TCCL: return clCvalue(o); + case LUA_TLCF: return cast(void *, cast(size_t, fvalue(o))); + case LUA_TTHREAD: return thvalue(o); + case LUA_TUSERDATA: + case LUA_TLIGHTUSERDATA: + return lua_touserdata(L, idx); + default: return NULL; + } +} + + +LUA_API lua_Integer lua_getnextid (lua_State *L) { + return L->nextid; +} + + +LUA_API void lua_setnextid (lua_State *L, lua_Integer id) { + L->nextid = id; +} + + +/* +** push functions (C -> stack) +*/ + + +LUA_API void lua_pushnil (lua_State *L) { + lua_lock(L); + setnilvalue(L->top); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { + lua_lock(L); + setnvalue(L->top, n); + luai_checknum(L, L->top, + luaG_runerror(L, "C API - attempt to push a signaling NaN")); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { + lua_lock(L); + setnvalue(L->top, cast_num(n)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushunsigned (lua_State *L, lua_Unsigned u) { + lua_Number n; + lua_lock(L); + n = lua_unsigned2number(u); + setnvalue(L->top, n); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) { + TString *ts; + lua_lock(L); + luaC_checkGC(L); + ts = luaS_newlstr(L, s, len); + setsvalue2s(L, L->top, ts); + api_incr_top(L); + lua_unlock(L); + return getstr(ts); +} + + +LUA_API const char *lua_pushstring (lua_State *L, const char *s) { + if (s == NULL) { + lua_pushnil(L); + return NULL; + } + else { + TString *ts; + lua_lock(L); + luaC_checkGC(L); + ts = luaS_new(L, s); + setsvalue2s(L, L->top, ts); + api_incr_top(L); + lua_unlock(L); + return getstr(ts); + } +} + + +LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, + va_list argp) { + const char *ret; + lua_lock(L); + luaC_checkGC(L); + ret = luaO_pushvfstring(L, fmt, argp); + lua_unlock(L); + return ret; +} + + +LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { + const char *ret; + va_list argp; + lua_lock(L); + luaC_checkGC(L); + va_start(argp, fmt); + ret = luaO_pushvfstring(L, fmt, argp); + va_end(argp); + lua_unlock(L); + return ret; +} + + +LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { + lua_lock(L); + if (n == 0) { + setfvalue(L->top, fn); + } + else { + Closure *cl; + api_checknelems(L, n); + api_check(L, n <= MAXUPVAL, "upvalue index too large"); + luaC_checkGC(L); + cl = luaF_newCclosure(L, n); + cl->c.f = fn; + L->top -= n; + while (n--) + setobj2n(L, &cl->c.upvalue[n], L->top + n); + setclCvalue(L, L->top, cl); + } + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushboolean (lua_State *L, int b) { + lua_lock(L); + setbvalue(L->top, (b != 0)); /* ensure that true is 1 */ + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushlightuserdata (lua_State *L, void *p) { + lua_lock(L); + setpvalue(L->top, p); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API int lua_pushthread (lua_State *L) { + lua_lock(L); + setthvalue(L, L->top, L); + api_incr_top(L); + lua_unlock(L); + return (G(L)->mainthread == L); +} + + + +/* +** get functions (Lua -> stack) +*/ + + +LUA_API void lua_getglobal (lua_State *L, const char *var) { + Table *reg = hvalue(&G(L)->l_registry); + const TValue *gt; /* global table */ + lua_lock(L); + gt = luaH_getint(reg, LUA_RIDX_GLOBALS); + setsvalue2s(L, L->top++, luaS_new(L, var)); + luaV_gettable(L, gt, L->top - 1, L->top - 1); + lua_unlock(L); +} + + +LUA_API void lua_gettable (lua_State *L, int idx) { + StkId t; + lua_lock(L); + t = index2addr(L, idx); + luaV_gettable(L, t, L->top - 1, L->top - 1); + lua_unlock(L); +} + + +LUA_API void lua_getfield (lua_State *L, int idx, const char *k) { + StkId t; + lua_lock(L); + t = index2addr(L, idx); + setsvalue2s(L, L->top, luaS_new(L, k)); + api_incr_top(L); + luaV_gettable(L, t, L->top - 1, L->top - 1); + lua_unlock(L); +} + + +LUA_API void lua_rawget (lua_State *L, int idx) { + StkId t; + lua_lock(L); + t = index2addr(L, idx); + api_check(L, ttistable(t), "table expected"); + setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1)); + lua_unlock(L); +} + + +LUA_API void lua_rawgeti (lua_State *L, int idx, int n) { + StkId t; + lua_lock(L); + t = index2addr(L, idx); + api_check(L, ttistable(t), "table expected"); + setobj2s(L, L->top, luaH_getint(hvalue(t), n)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_rawgetp (lua_State *L, int idx, const void *p) { + StkId t; + TValue k; + lua_lock(L); + t = index2addr(L, idx); + api_check(L, ttistable(t), "table expected"); + setpvalue(&k, cast(void *, p)); + setobj2s(L, L->top, luaH_get(hvalue(t), &k)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { + Table *t; + lua_lock(L); + luaC_checkGC(L); + t = luaH_new(L); + sethvalue(L, L->top, t); + api_incr_top(L); + if (narray > 0 || nrec > 0) + luaH_resize(L, t, narray, nrec); + lua_unlock(L); +} + + +LUA_API int lua_getmetatable (lua_State *L, int objindex) { + const TValue *obj; + Table *mt = NULL; + int res; + lua_lock(L); + obj = index2addr(L, objindex); + switch (ttypenv(obj)) { + case LUA_TTABLE: + mt = hvalue(obj)->metatable; + break; + case LUA_TUSERDATA: + mt = uvalue(obj)->metatable; + break; + default: + mt = G(L)->mt[ttypenv(obj)]; + break; + } + if (mt == NULL) + res = 0; + else { + sethvalue(L, L->top, mt); + api_incr_top(L); + res = 1; + } + lua_unlock(L); + return res; +} + + +LUA_API void lua_getuservalue (lua_State *L, int idx) { + StkId o; + lua_lock(L); + o = index2addr(L, idx); + api_check(L, ttisuserdata(o), "userdata expected"); + if (uvalue(o)->env) { + sethvalue(L, L->top, uvalue(o)->env); + } else + setnilvalue(L->top); + api_incr_top(L); + lua_unlock(L); +} + + +/* +** set functions (stack -> Lua) +*/ + + +LUA_API void lua_setglobal (lua_State *L, const char *var) { + Table *reg = hvalue(&G(L)->l_registry); + const TValue *gt; /* global table */ + lua_lock(L); + api_checknelems(L, 1); + gt = luaH_getint(reg, LUA_RIDX_GLOBALS); + setsvalue2s(L, L->top++, luaS_new(L, var)); + luaV_settable(L, gt, L->top - 1, L->top - 2); + L->top -= 2; /* pop value and key */ + lua_unlock(L); +} + + +LUA_API void lua_settable (lua_State *L, int idx) { + StkId t; + lua_lock(L); + api_checknelems(L, 2); + t = index2addr(L, idx); + luaV_settable(L, t, L->top - 2, L->top - 1); + L->top -= 2; /* pop index and value */ + lua_unlock(L); +} + + +LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { + StkId t; + lua_lock(L); + api_checknelems(L, 1); + t = index2addr(L, idx); + setsvalue2s(L, L->top++, luaS_new(L, k)); + luaV_settable(L, t, L->top - 1, L->top - 2); + L->top -= 2; /* pop value and key */ + lua_unlock(L); +} + + +LUA_API void lua_rawset (lua_State *L, int idx) { + StkId t; + lua_lock(L); + api_checknelems(L, 2); + t = index2addr(L, idx); + api_check(L, ttistable(t), "table expected"); + luaH_setvalue(L, hvalue(t), L->top-2, L->top-1); + invalidateTMcache(hvalue(t)); + luaC_barrierback(L, gcvalue(t), L->top-1); + L->top -= 2; + lua_unlock(L); +} + + +LUA_API void lua_rawseti (lua_State *L, int idx, int n) { + StkId t; + lua_lock(L); + api_checknelems(L, 1); + t = index2addr(L, idx); + api_check(L, ttistable(t), "table expected"); + luaH_setint(L, hvalue(t), n, L->top - 1); + luaC_barrierback(L, gcvalue(t), L->top-1); + L->top--; + lua_unlock(L); +} + + +LUA_API void lua_rawsetp (lua_State *L, int idx, const void *p) { + StkId t; + TValue k; + lua_lock(L); + api_checknelems(L, 1); + t = index2addr(L, idx); + api_check(L, ttistable(t), "table expected"); + setpvalue(&k, cast(void *, p)); + luaH_setvalue(L, hvalue(t), &k, L->top-1); + luaC_barrierback(L, gcvalue(t), L->top - 1); + L->top--; + lua_unlock(L); +} + + +LUA_API int lua_setmetatable (lua_State *L, int objindex) { + TValue *obj; + Table *mt; + lua_lock(L); + api_checknelems(L, 1); + obj = index2addr(L, objindex); + if (ttisnil(L->top - 1)) + mt = NULL; + else { + api_check(L, ttistable(L->top - 1), "table expected"); + mt = hvalue(L->top - 1); + } + switch (ttypenv(obj)) { + case LUA_TTABLE: { + hvalue(obj)->metatable = mt; + if (mt) { + luaC_objbarrierback(L, gcvalue(obj), mt); + luaC_checkfinalizer(L, gcvalue(obj), mt); + } + break; + } + case LUA_TUSERDATA: { + uvalue(obj)->metatable = mt; + if (mt) { + luaC_objbarrier(L, rawuvalue(obj), mt); + luaC_checkfinalizer(L, gcvalue(obj), mt); + } + break; + } + default: { + G(L)->mt[ttypenv(obj)] = mt; + break; + } + } + L->top--; + lua_unlock(L); + return 1; +} + + +LUA_API void lua_setuservalue (lua_State *L, int idx) { + StkId o; + lua_lock(L); + api_checknelems(L, 1); + o = index2addr(L, idx); + api_check(L, ttisuserdata(o), "userdata expected"); + if (ttisnil(L->top - 1)) + uvalue(o)->env = NULL; + else { + api_check(L, ttistable(L->top - 1), "table expected"); + uvalue(o)->env = hvalue(L->top - 1); + luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1)); + } + L->top--; + lua_unlock(L); +} + + +LUA_API void lua_setflagbits (lua_State *L, int idx, unsigned short bits) { + TValue *obj; + Table *tab; + lua_lock(L); + obj = index2addr(L, idx); + api_check(L, ttistable(obj), "table expected"); + tab = hvalue(obj); + tab->flagbits = bits; + lua_unlock(L); +} + +LUA_API void lua_modflagbits (lua_State *L, int idx, unsigned short clear, unsigned short set) { + TValue *obj; + Table *tab; + lua_lock(L); + obj = index2addr(L, idx); + api_check(L, ttistable(obj), "table expected"); + tab = hvalue(obj); + tab->flagbits &= (~clear); + tab->flagbits |= set; + lua_unlock(L); +} + +LUA_API unsigned short lua_getflagbits (lua_State *L, int idx) { + TValue *obj; + Table *tab; + obj = index2addr(L, idx); + api_check(L, ttistable(obj), "table expected"); + tab = hvalue(obj); + return tab->flagbits; +} + +/* +** `load' and `call' functions (run Lua code) +*/ + + +#define checkresults(L,na,nr) \ + api_check(L, (nr) == LUA_MULTRET || (L->ci->top - L->top >= (nr) - (na)), \ + "results from function overflow current stack size") + + +LUA_API int lua_getctx (lua_State *L, int *ctx) { + if (L->ci->callstatus & CIST_YIELDED) { + if (ctx) *ctx = L->ci->u.c.ctx; + return L->ci->u.c.status; + } + else return LUA_OK; +} + + +LUA_API void lua_callk (lua_State *L, int nargs, int nresults, int ctx, + lua_CFunction k) { + StkId func; + lua_lock(L); + api_check(L, k == NULL || !isLua(L->ci), + "cannot use continuations inside hooks"); + api_checknelems(L, nargs+1); + api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); + checkresults(L, nargs, nresults); + func = L->top - (nargs+1); + if (k != NULL && L->nny == 0) { /* need to prepare continuation? */ + L->ci->u.c.k = k; /* save continuation */ + L->ci->u.c.ctx = ctx; /* save context */ + luaD_call(L, func, nresults, 1); /* do the call */ + } + else /* no continuation or no yieldable */ + luaD_call(L, func, nresults, 0); /* just do the call */ + adjustresults(L, nresults); + lua_unlock(L); +} + + + +/* +** Execute a protected call. +*/ +struct CallS { /* data to `f_call' */ + StkId func; + int nresults; +}; + + +static void f_call (lua_State *L, void *ud) { + struct CallS *c = cast(struct CallS *, ud); + luaD_call(L, c->func, c->nresults, 0); +} + + + +LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, + int ctx, lua_CFunction k) { + struct CallS c; + int status; + ptrdiff_t func; + lua_lock(L); + api_check(L, k == NULL || !isLua(L->ci), + "cannot use continuations inside hooks"); + api_checknelems(L, nargs+1); + api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); + checkresults(L, nargs, nresults); + if (errfunc == 0) + func = 0; + else { + StkId o = index2addr(L, errfunc); + api_checkstackindex(L, errfunc, o); + func = savestack(L, o); + } + c.func = L->top - (nargs+1); /* function to be called */ + if (k == NULL || L->nny > 0) { /* no continuation or no yieldable? */ + c.nresults = nresults; /* do a 'conventional' protected call */ + status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); + } + else { /* prepare continuation (call is already protected by 'resume') */ + CallInfo *ci = L->ci; + ci->u.c.k = k; /* save continuation */ + ci->u.c.ctx = ctx; /* save context */ + /* save information for error recovery */ + ci->extra = savestack(L, c.func); + ci->u.c.old_allowhook = L->allowhook; + ci->u.c.old_errfunc = L->errfunc; + L->errfunc = func; + /* mark that function may do error recovery */ + ci->callstatus |= CIST_YPCALL; + luaD_call(L, c.func, nresults, 1); /* do the call */ + ci->callstatus &= ~CIST_YPCALL; + L->errfunc = ci->u.c.old_errfunc; + status = LUA_OK; /* if it is here, there were no errors */ + } + adjustresults(L, nresults); + lua_unlock(L); + return status; +} + + +LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, + const char *chunkname, const char *mode) { + ZIO z; + int status; + lua_lock(L); + if (!chunkname) chunkname = "?"; + luaZ_init(L, &z, reader, data); + status = luaD_protectedparser(L, &z, chunkname, mode); + if (status == LUA_OK) { /* no errors? */ + LClosure *f = clLvalue(L->top - 1); /* get newly created function */ + if (f->nupvalues == 1) { /* does it have one upvalue? */ + /* get global table from registry */ + Table *reg = hvalue(&G(L)->l_registry); + const TValue *gt = luaH_getint(reg, LUA_RIDX_GLOBALS); + /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */ + setobj(L, f->upvals[0]->v, gt); + luaC_barrier(L, f->upvals[0], gt); + } + } + lua_unlock(L); + return status; +} + + +LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data) { + int status; + TValue *o; + lua_lock(L); + api_checknelems(L, 1); + o = L->top - 1; + if (isLfunction(o)) + status = luaU_dump(L, getproto(o), writer, data, 0); + else + status = 1; + lua_unlock(L); + return status; +} + + +LUA_API int lua_status (lua_State *L) { + return L->status; +} + + +/* +** Garbage-collection function +*/ + +LUA_API int lua_gc (lua_State *L, int what, int data) { + int res = 0; + global_State *g; + lua_lock(L); + g = G(L); + switch (what) { + case LUA_GCSTOP: { + g->gcrunning = 0; + break; + } + case LUA_GCRESTART: { + luaE_setdebt(g, 0); + g->gcrunning = 1; + break; + } + case LUA_GCCOLLECT: { + luaC_fullgc(L, 0); + break; + } + case LUA_GCCOUNT: { + /* GC values are expressed in Kbytes: #bytes/2^10 */ + res = cast_int(gettotalbytes(g) >> 10); + break; + } + case LUA_GCCOUNTB: { + res = cast_int(gettotalbytes(g) & 0x3ff); + break; + } + case LUA_GCSTEP: { + if (g->gckind == KGC_GEN) { /* generational mode? */ + res = (g->GCestimate == 0); /* true if it will do major collection */ + luaC_forcestep(L); /* do a single step */ + } + else { + lu_mem debt = cast(lu_mem, data) * 1024 - GCSTEPSIZE; + if (g->gcrunning) + debt += g->GCdebt; /* include current debt */ + luaE_setdebt(g, debt); + luaC_forcestep(L); + if (g->gcstate == GCSpause) /* end of cycle? */ + res = 1; /* signal it */ + } + break; + } + case LUA_GCSETPAUSE: { + res = g->gcpause; + g->gcpause = data; + break; + } + case LUA_GCSETMAJORINC: { + res = g->gcmajorinc; + g->gcmajorinc = data; + break; + } + case LUA_GCSETSTEPMUL: { + res = g->gcstepmul; + g->gcstepmul = data; + break; + } + case LUA_GCISRUNNING: { + res = g->gcrunning; + break; + } + case LUA_GCGEN: { /* change collector to generational mode */ + luaC_changemode(L, KGC_GEN); + break; + } + case LUA_GCINC: { /* change collector to incremental mode */ + luaC_changemode(L, KGC_NORMAL); + break; + } + default: res = -1; /* invalid option */ + } + lua_unlock(L); + return res; +} + + + +/* +** miscellaneous functions +*/ + + +LUA_API int lua_error (lua_State *L) { + lua_lock(L); + api_checknelems(L, 1); + luaG_errormsg(L); + /* code unreachable; will unlock when control actually leaves the kernel */ + return 0; /* to avoid warnings */ +} + +LUA_API int lua_next (lua_State *L, int idx) { + StkId t; + int more; + lua_lock(L); + t = index2addr(L, idx); + api_check(L, ttistable(t), "table expected"); + more = luaH_next(L, hvalue(t), L->top - 1); + if (more) { + api_incr_top(L); + } + else /* no more elements */ + L->top -= 1; /* remove key */ + lua_unlock(L); + return more; +} + +LUA_API int lua_nkeys (lua_State *L, int idx) { + StkId t; + lua_lock(L); + t = index2addr(L, idx); + api_check(L, ttistable(t), "table expected"); + int n = luaH_nkeys(hvalue(t)); + lua_unlock(L); + return n; +} + +LUA_API int lua_nthkey (lua_State *L, int idx, int n) { + StkId t; + lua_lock(L); + t = index2addr(L, idx); + api_check(L, ttistable(t), "table expected"); + api_incr_top(L); + api_incr_top(L); + int res = luaH_nthkey(L, hvalue(t), n, L->top - 2); + if (res == 0) + L->top -= 2; + lua_unlock(L); + return res; +} + +LUA_API void lua_concat (lua_State *L, int n) { + lua_lock(L); + api_checknelems(L, n); + if (n >= 2) { + luaC_checkGC(L); + luaV_concat(L, n); + } + else if (n == 0) { /* push empty string */ + setsvalue2s(L, L->top, luaS_newlstr(L, "", 0)); + api_incr_top(L); + } + /* else n == 1; nothing to do */ + lua_unlock(L); +} + + +LUA_API void lua_len (lua_State *L, int idx) { + StkId t; + lua_lock(L); + t = index2addr(L, idx); + luaV_objlen(L, L->top, t); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API lua_Alloc lua_getallocf (lua_State *L, void **ud) { + lua_Alloc f; + lua_lock(L); + if (ud) *ud = G(L)->ud; + f = G(L)->frealloc; + lua_unlock(L); + return f; +} + + +LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { + lua_lock(L); + G(L)->ud = ud; + G(L)->frealloc = f; + lua_unlock(L); +} + + +LUA_API void *lua_newuserdata (lua_State *L, size_t size) { + Udata *u; + lua_lock(L); + luaC_checkGC(L); + u = luaS_newudata(L, size, NULL); + setuvalue(L, L->top, u); + api_incr_top(L); + lua_unlock(L); + return u + 1; +} + + + +static const char *aux_upvalue (StkId fi, int n, TValue **val, + GCObject **owner) { + switch (ttype(fi)) { + case LUA_TCCL: { /* C closure */ + CClosure *f = clCvalue(fi); + if (!(1 <= n && n <= f->nupvalues)) return NULL; + *val = &f->upvalue[n-1]; + if (owner) *owner = obj2gco(f); + return ""; + } + case LUA_TLCL: { /* Lua closure */ + LClosure *f = clLvalue(fi); + TString *name; + Proto *p = f->p; + if (!(1 <= n && n <= p->sizeupvalues)) return NULL; + *val = f->upvals[n-1]->v; + if (owner) *owner = obj2gco(f->upvals[n - 1]); + name = p->upvalues[n-1].name; + return (name == NULL) ? "" : getstr(name); + } + default: return NULL; /* not a closure */ + } +} + + +LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { + const char *name; + TValue *val = NULL; /* to avoid warnings */ + lua_lock(L); + name = aux_upvalue(index2addr(L, funcindex), n, &val, NULL); + if (name) { + setobj2s(L, L->top, val); + api_incr_top(L); + } + lua_unlock(L); + return name; +} + + +LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { + const char *name; + TValue *val = NULL; /* to avoid warnings */ + GCObject *owner = NULL; /* to avoid warnings */ + StkId fi; + lua_lock(L); + fi = index2addr(L, funcindex); + api_checknelems(L, 1); + name = aux_upvalue(fi, n, &val, &owner); + if (name) { + L->top--; + setobj(L, val, L->top); + luaC_barrier(L, owner, L->top); + } + lua_unlock(L); + return name; +} + + +static UpVal **getupvalref (lua_State *L, int fidx, int n, LClosure **pf) { + LClosure *f; + StkId fi = index2addr(L, fidx); + api_check(L, ttisLclosure(fi), "Lua function expected"); + f = clLvalue(fi); + api_check(L, (1 <= n && n <= f->p->sizeupvalues), "invalid upvalue index"); + if (pf) *pf = f; + return &f->upvals[n - 1]; /* get its upvalue pointer */ +} + + +LUA_API void *lua_upvalueid (lua_State *L, int fidx, int n) { + StkId fi = index2addr(L, fidx); + switch (ttype(fi)) { + case LUA_TLCL: { /* lua closure */ + return *getupvalref(L, fidx, n, NULL); + } + case LUA_TCCL: { /* C closure */ + CClosure *f = clCvalue(fi); + api_check(L, 1 <= n && n <= f->nupvalues, "invalid upvalue index"); + return &f->upvalue[n - 1]; + } + default: { + api_check(L, 0, "closure expected"); + return NULL; + } + } +} + + +LUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1, + int fidx2, int n2) { + LClosure *f1; + UpVal **up1 = getupvalref(L, fidx1, n1, &f1); + UpVal **up2 = getupvalref(L, fidx2, n2, NULL); + *up1 = *up2; + luaC_objbarrier(L, f1, *up2); +} + diff --git a/luprex/ext/eris-master/src/lapi.h b/luprex/ext/eris-master/src/lapi.h new file mode 100644 index 00000000..c7d34ad8 --- /dev/null +++ b/luprex/ext/eris-master/src/lapi.h @@ -0,0 +1,24 @@ +/* +** $Id: lapi.h,v 2.7.1.1 2013/04/12 18:48:47 roberto Exp $ +** Auxiliary functions from Lua API +** See Copyright Notice in lua.h +*/ + +#ifndef lapi_h +#define lapi_h + + +#include "llimits.h" +#include "lstate.h" + +#define api_incr_top(L) {L->top++; api_check(L, L->top <= L->ci->top, \ + "stack overflow");} + +#define adjustresults(L,nres) \ + { if ((nres) == LUA_MULTRET && L->ci->top < L->top) L->ci->top = L->top; } + +#define api_checknelems(L,n) api_check(L, (n) < (L->top - L->ci->func), \ + "not enough elements in the stack") + + +#endif diff --git a/luprex/ext/eris-master/src/lauxlib.c b/luprex/ext/eris-master/src/lauxlib.c new file mode 100644 index 00000000..3b79bb53 --- /dev/null +++ b/luprex/ext/eris-master/src/lauxlib.c @@ -0,0 +1,962 @@ +/* +** $Id: lauxlib.c,v 1.248.1.1 2013/04/12 18:48:47 roberto Exp $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#include +#include +#include +#include +#include + + +/* This file uses only the official API of Lua. +** Any function declared here could be written as an application function. +*/ + +#define lauxlib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" + + +/* +** {====================================================== +** Traceback +** ======================================================= +*/ + + +#define LEVELS1 12 /* size of the first part of the stack */ +#define LEVELS2 10 /* size of the second part of the stack */ + + + +/* +** search for 'objidx' in table at index -1. +** return 1 + string at top if find a good name. +*/ +static int findfield (lua_State *L, int objidx, int level) { + if (level == 0 || !lua_istable(L, -1)) + return 0; /* not found */ + lua_pushnil(L); /* start 'next' loop */ + while (lua_next(L, -2)) { /* for each pair in table */ + if (lua_type(L, -2) == LUA_TSTRING) { /* ignore non-string keys */ + if (lua_rawequal(L, objidx, -1)) { /* found object? */ + lua_pop(L, 1); /* remove value (but keep name) */ + return 1; + } + else if (findfield(L, objidx, level - 1)) { /* try recursively */ + lua_remove(L, -2); /* remove table (but keep name) */ + lua_pushliteral(L, "."); + lua_insert(L, -2); /* place '.' between the two names */ + lua_concat(L, 3); + return 1; + } + } + lua_pop(L, 1); /* remove value */ + } + return 0; /* not found */ +} + + +static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { + int top = lua_gettop(L); + lua_getinfo(L, "f", ar); /* push function */ + lua_pushglobaltable(L); + if (findfield(L, top + 1, 2)) { + lua_copy(L, -1, top + 1); /* move name to proper place */ + lua_pop(L, 2); /* remove pushed values */ + return 1; + } + else { + lua_settop(L, top); /* remove function and global table */ + return 0; + } +} + + +static void pushfuncname (lua_State *L, lua_Debug *ar) { + if (*ar->namewhat != '\0') /* is there a name? */ + lua_pushfstring(L, "function " LUA_QS, ar->name); + else if (*ar->what == 'm') /* main? */ + lua_pushliteral(L, "main chunk"); + else if (*ar->what == 'C') { + if (pushglobalfuncname(L, ar)) { + lua_pushfstring(L, "function " LUA_QS, lua_tostring(L, -1)); + lua_remove(L, -2); /* remove name */ + } + else + lua_pushliteral(L, "?"); + } + else + lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined); +} + + +static int countlevels (lua_State *L) { + lua_Debug ar; + int li = 1, le = 1; + /* find an upper bound */ + while (lua_getstack(L, le, &ar)) { li = le; le *= 2; } + /* do a binary search */ + while (li < le) { + int m = (li + le)/2; + if (lua_getstack(L, m, &ar)) li = m + 1; + else le = m; + } + return le - 1; +} + + +LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, + const char *msg, int level) { + lua_Debug ar; + int top = lua_gettop(L); + int numlevels = countlevels(L1); + int mark = (numlevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0; + if (msg) lua_pushfstring(L, "%s\n", msg); + lua_pushliteral(L, "stack traceback:"); + while (lua_getstack(L1, level++, &ar)) { + if (level == mark) { /* too many levels? */ + lua_pushliteral(L, "\n\t..."); /* add a '...' */ + level = numlevels - LEVELS2; /* and skip to last ones */ + } + else { + lua_getinfo(L1, "Slnt", &ar); + lua_pushfstring(L, "\n\t%s:", ar.short_src); + if (ar.currentline > 0) + lua_pushfstring(L, "%d:", ar.currentline); + lua_pushliteral(L, " in "); + pushfuncname(L, &ar); + if (ar.istailcall) + lua_pushliteral(L, "\n\t(...tail calls...)"); + lua_concat(L, lua_gettop(L) - top); + } + } + lua_concat(L, lua_gettop(L) - top); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Error-report functions +** ======================================================= +*/ + +LUALIB_API int luaL_argerror (lua_State *L, int narg, const char *extramsg) { + lua_Debug ar; + if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ + return luaL_error(L, "bad argument #%d (%s)", narg, extramsg); + lua_getinfo(L, "n", &ar); + if (strcmp(ar.namewhat, "method") == 0) { + narg--; /* do not count `self' */ + if (narg == 0) /* error is in the self argument itself? */ + return luaL_error(L, "calling " LUA_QS " on bad self (%s)", + ar.name, extramsg); + } + if (ar.name == NULL) + ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?"; + return luaL_error(L, "bad argument #%d to " LUA_QS " (%s)", + narg, ar.name, extramsg); +} + + +static int typeerror (lua_State *L, int narg, const char *tname) { + const char *msg = lua_pushfstring(L, "%s expected, got %s", + tname, luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); +} + + +static void tag_error (lua_State *L, int narg, int tag) { + typeerror(L, narg, lua_typename(L, tag)); +} + + +LUALIB_API void luaL_where (lua_State *L, int level) { + lua_Debug ar; + if (lua_getstack(L, level, &ar)) { /* check function at level */ + lua_getinfo(L, "Sl", &ar); /* get info about it */ + if (ar.currentline > 0) { /* is there info? */ + lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline); + return; + } + } + lua_pushliteral(L, ""); /* else, no information available... */ +} + +LUALIB_API void luaL_no_where (lua_State *L, int level) { + lua_pushliteral(L, ""); /* else, no information available... */ +} + +LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { + va_list argp; + va_start(argp, fmt); + luaL_no_where(L, 1); + lua_pushvfstring(L, fmt, argp); + va_end(argp); + lua_concat(L, 2); + return lua_error(L); +} + + +LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { + int en = errno; /* calls to Lua API may change this value */ + if (stat) { + lua_pushboolean(L, 1); + return 1; + } + else { + lua_pushnil(L); + if (fname) + lua_pushfstring(L, "%s: %s", fname, strerror(en)); + else + lua_pushstring(L, strerror(en)); + lua_pushinteger(L, en); + return 3; + } +} + + +#if !defined(inspectstat) /* { */ + +#if defined(LUA_USE_POSIX) + +#include + +/* +** use appropriate macros to interpret 'pclose' return status +*/ +#define inspectstat(stat,what) \ + if (WIFEXITED(stat)) { stat = WEXITSTATUS(stat); } \ + else if (WIFSIGNALED(stat)) { stat = WTERMSIG(stat); what = "signal"; } + +#else + +#define inspectstat(stat,what) /* no op */ + +#endif + +#endif /* } */ + + +LUALIB_API int luaL_execresult (lua_State *L, int stat) { + const char *what = "exit"; /* type of termination */ + if (stat == -1) /* error? */ + return luaL_fileresult(L, 0, NULL); + else { + inspectstat(stat, what); /* interpret result */ + if (*what == 'e' && stat == 0) /* successful termination? */ + lua_pushboolean(L, 1); + else + lua_pushnil(L); + lua_pushstring(L, what); + lua_pushinteger(L, stat); + return 3; /* return true/nil,what,code */ + } +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Userdata's metatable manipulation +** ======================================================= +*/ + +LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { + luaL_getmetatable(L, tname); /* try to get metatable */ + if (!lua_isnil(L, -1)) /* name already in use? */ + return 0; /* leave previous value on top, but return 0 */ + lua_pop(L, 1); + lua_newtable(L); /* create metatable */ + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */ + return 1; +} + + +LUALIB_API void luaL_setmetatable (lua_State *L, const char *tname) { + luaL_getmetatable(L, tname); + lua_setmetatable(L, -2); +} + + +LUALIB_API void *luaL_testudata (lua_State *L, int ud, const char *tname) { + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + luaL_getmetatable(L, tname); /* get correct metatable */ + if (!lua_rawequal(L, -1, -2)) /* not the same? */ + p = NULL; /* value is a userdata with wrong metatable */ + lua_pop(L, 2); /* remove both metatables */ + return p; + } + } + return NULL; /* value is not a userdata with a metatable */ +} + + +LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { + void *p = luaL_testudata(L, ud, tname); + if (p == NULL) typeerror(L, ud, tname); + return p; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Argument check functions +** ======================================================= +*/ + +LUALIB_API int luaL_checkoption (lua_State *L, int narg, const char *def, + const char *const lst[]) { + const char *name = (def) ? luaL_optstring(L, narg, def) : + luaL_checkstring(L, narg); + int i; + for (i=0; lst[i]; i++) + if (strcmp(lst[i], name) == 0) + return i; + return luaL_argerror(L, narg, + lua_pushfstring(L, "invalid option " LUA_QS, name)); +} + + +LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { + /* keep some extra space to run error routines, if needed */ + const int extra = LUA_MINSTACK; + if (!lua_checkstack(L, space + extra)) { + if (msg) + luaL_error(L, "stack overflow (%s)", msg); + else + luaL_error(L, "stack overflow"); + } +} + + +LUALIB_API void luaL_checktype (lua_State *L, int narg, int t) { + if (lua_type(L, narg) != t) + tag_error(L, narg, t); +} + + +LUALIB_API void luaL_checkany (lua_State *L, int narg) { + if (lua_type(L, narg) == LUA_TNONE) + luaL_argerror(L, narg, "value expected"); +} + + +LUALIB_API const char *luaL_checklstring (lua_State *L, int narg, size_t *len) { + const char *s = lua_tolstring(L, narg, len); + if (!s) tag_error(L, narg, LUA_TSTRING); + return s; +} + + +LUALIB_API const char *luaL_optlstring (lua_State *L, int narg, + const char *def, size_t *len) { + if (lua_isnoneornil(L, narg)) { + if (len) + *len = (def ? strlen(def) : 0); + return def; + } + else return luaL_checklstring(L, narg, len); +} + + +LUALIB_API lua_Number luaL_checknumber (lua_State *L, int narg) { + int isnum; + lua_Number d = lua_tonumberx(L, narg, &isnum); + if (!isnum) + tag_error(L, narg, LUA_TNUMBER); + return d; +} + + +LUALIB_API lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number def) { + return luaL_opt(L, luaL_checknumber, narg, def); +} + + +LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int narg) { + int isnum; + lua_Integer d = lua_tointegerx(L, narg, &isnum); + if (!isnum) + tag_error(L, narg, LUA_TNUMBER); + return d; +} + + +LUALIB_API lua_Unsigned luaL_checkunsigned (lua_State *L, int narg) { + int isnum; + lua_Unsigned d = lua_tounsignedx(L, narg, &isnum); + if (!isnum) + tag_error(L, narg, LUA_TNUMBER); + return d; +} + + +LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int narg, + lua_Integer def) { + return luaL_opt(L, luaL_checkinteger, narg, def); +} + + +LUALIB_API lua_Unsigned luaL_optunsigned (lua_State *L, int narg, + lua_Unsigned def) { + return luaL_opt(L, luaL_checkunsigned, narg, def); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +/* +** check whether buffer is using a userdata on the stack as a temporary +** buffer +*/ +#define buffonstack(B) ((B)->b != (B)->initb) + + +/* +** returns a pointer to a free area with at least 'sz' bytes +*/ +LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { + lua_State *L = B->L; + if (B->size - B->n < sz) { /* not enough space? */ + char *newbuff; + size_t newsize = B->size * 2; /* double buffer size */ + if (newsize - B->n < sz) /* not big enough? */ + newsize = B->n + sz; + if (newsize < B->n || newsize - B->n < sz) + luaL_error(L, "buffer too large"); + /* create larger buffer */ + newbuff = (char *)lua_newuserdata(L, newsize * sizeof(char)); + /* move content to new buffer */ + memcpy(newbuff, B->b, B->n * sizeof(char)); + if (buffonstack(B)) + lua_remove(L, -2); /* remove old buffer */ + B->b = newbuff; + B->size = newsize; + } + return &B->b[B->n]; +} + + +LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { + char *b = luaL_prepbuffsize(B, l); + memcpy(b, s, l * sizeof(char)); + luaL_addsize(B, l); +} + + +LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { + luaL_addlstring(B, s, strlen(s)); +} + + +LUALIB_API void luaL_pushresult (luaL_Buffer *B) { + lua_State *L = B->L; + lua_pushlstring(L, B->b, B->n); + if (buffonstack(B)) + lua_remove(L, -2); /* remove old buffer */ +} + + +LUALIB_API void luaL_pushresultsize (luaL_Buffer *B, size_t sz) { + luaL_addsize(B, sz); + luaL_pushresult(B); +} + + +LUALIB_API void luaL_addvalue (luaL_Buffer *B) { + lua_State *L = B->L; + size_t l; + const char *s = lua_tolstring(L, -1, &l); + if (buffonstack(B)) + lua_insert(L, -2); /* put value below buffer */ + luaL_addlstring(B, s, l); + lua_remove(L, (buffonstack(B)) ? -2 : -1); /* remove value */ +} + + +LUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) { + B->L = L; + B->b = B->initb; + B->n = 0; + B->size = LUAL_BUFFERSIZE; +} + + +LUALIB_API char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) { + luaL_buffinit(L, B); + return luaL_prepbuffsize(B, sz); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Reference system +** ======================================================= +*/ + +/* index of free-list header */ +#define freelist 0 + + +LUALIB_API int luaL_ref (lua_State *L, int t) { + int ref; + if (lua_isnil(L, -1)) { + lua_pop(L, 1); /* remove from stack */ + return LUA_REFNIL; /* `nil' has a unique fixed reference */ + } + t = lua_absindex(L, t); + lua_rawgeti(L, t, freelist); /* get first free element */ + ref = (int)lua_tointeger(L, -1); /* ref = t[freelist] */ + lua_pop(L, 1); /* remove it from stack */ + if (ref != 0) { /* any free element? */ + lua_rawgeti(L, t, ref); /* remove it from list */ + lua_rawseti(L, t, freelist); /* (t[freelist] = t[ref]) */ + } + else /* no free elements */ + ref = (int)lua_rawlen(L, t) + 1; /* get a new reference */ + lua_rawseti(L, t, ref); + return ref; +} + + +LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { + if (ref >= 0) { + t = lua_absindex(L, t); + lua_rawgeti(L, t, freelist); + lua_rawseti(L, t, ref); /* t[ref] = t[freelist] */ + lua_pushinteger(L, ref); + lua_rawseti(L, t, freelist); /* t[freelist] = ref */ + } +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Load functions +** ======================================================= +*/ + +typedef struct LoadF { + int n; /* number of pre-read characters */ + FILE *f; /* file being read */ + char buff[LUAL_BUFFERSIZE]; /* area for reading file */ +} LoadF; + + +static const char *getF (lua_State *L, void *ud, size_t *size) { + LoadF *lf = (LoadF *)ud; + (void)L; /* not used */ + if (lf->n > 0) { /* are there pre-read characters to be read? */ + *size = lf->n; /* return them (chars already in buffer) */ + lf->n = 0; /* no more pre-read characters */ + } + else { /* read a block from file */ + /* 'fread' can return > 0 *and* set the EOF flag. If next call to + 'getF' called 'fread', it might still wait for user input. + The next check avoids this problem. */ + if (feof(lf->f)) return NULL; + *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); /* read block */ + } + return lf->buff; +} + + +static int errfile (lua_State *L, const char *what, int fnameindex) { + const char *serr = strerror(errno); + const char *filename = lua_tostring(L, fnameindex) + 1; + lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr); + lua_remove(L, fnameindex); + return LUA_ERRFILE; +} + + +static int skipBOM (LoadF *lf) { + const char *p = "\xEF\xBB\xBF"; /* Utf8 BOM mark */ + int c; + lf->n = 0; + do { + c = getc(lf->f); + if (c == EOF || c != *(const unsigned char *)p++) return c; + lf->buff[lf->n++] = c; /* to be read by the parser */ + } while (*p != '\0'); + lf->n = 0; /* prefix matched; discard it */ + return getc(lf->f); /* return next character */ +} + + +/* +** reads the first character of file 'f' and skips an optional BOM mark +** in its beginning plus its first line if it starts with '#'. Returns +** true if it skipped the first line. In any case, '*cp' has the +** first "valid" character of the file (after the optional BOM and +** a first-line comment). +*/ +static int skipcomment (LoadF *lf, int *cp) { + int c = *cp = skipBOM(lf); + if (c == '#') { /* first line is a comment (Unix exec. file)? */ + do { /* skip first line */ + c = getc(lf->f); + } while (c != EOF && c != '\n') ; + *cp = getc(lf->f); /* skip end-of-line, if present */ + return 1; /* there was a comment */ + } + else return 0; /* no comment */ +} + + +LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, + const char *mode) { + LoadF lf; + int status, readstatus; + int c; + int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */ + if (filename == NULL) { + lua_pushliteral(L, "=stdin"); + lf.f = stdin; + } + else { + lua_pushfstring(L, "@%s", filename); + lf.f = fopen(filename, "r"); + if (lf.f == NULL) return errfile(L, "open", fnameindex); + } + if (skipcomment(&lf, &c)) /* read initial portion */ + lf.buff[lf.n++] = '\n'; /* add line to correct line numbers */ + if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */ + lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ + if (lf.f == NULL) return errfile(L, "reopen", fnameindex); + skipcomment(&lf, &c); /* re-read initial portion */ + } + if (c != EOF) + lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */ + status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode); + readstatus = ferror(lf.f); + if (filename) fclose(lf.f); /* close file (even in case of errors) */ + if (readstatus) { + lua_settop(L, fnameindex); /* ignore results from `lua_load' */ + return errfile(L, "read", fnameindex); + } + lua_remove(L, fnameindex); + return status; +} + + +typedef struct LoadS { + const char *s; + size_t size; +} LoadS; + + +static const char *getS (lua_State *L, void *ud, size_t *size) { + LoadS *ls = (LoadS *)ud; + (void)L; /* not used */ + if (ls->size == 0) return NULL; + *size = ls->size; + ls->size = 0; + return ls->s; +} + + +LUALIB_API int luaL_loadbufferx (lua_State *L, const char *buff, size_t size, + const char *name, const char *mode) { + LoadS ls; + ls.s = buff; + ls.size = size; + return lua_load(L, getS, &ls, name, mode); +} + + +LUALIB_API int luaL_loadstring (lua_State *L, const char *s) { + return luaL_loadbuffer(L, s, strlen(s), s); +} + +/* }====================================================== */ + + + +LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) { + if (!lua_getmetatable(L, obj)) /* no metatable? */ + return 0; + lua_pushstring(L, event); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 2); /* remove metatable and metafield */ + return 0; + } + else { + lua_remove(L, -2); /* remove only metatable */ + return 1; + } +} + + +LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { + obj = lua_absindex(L, obj); + if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ + return 0; + lua_pushvalue(L, obj); + lua_call(L, 1, 1); + return 1; +} + + +LUALIB_API int luaL_len (lua_State *L, int idx) { + int l; + int isnum; + lua_len(L, idx); + l = (int)lua_tointegerx(L, -1, &isnum); + if (!isnum) + luaL_error(L, "object length is not a number"); + lua_pop(L, 1); /* remove object */ + return l; +} + + +LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { + if (!luaL_callmeta(L, idx, "__tostring")) { /* no metafield? */ + switch (lua_type(L, idx)) { + case LUA_TNUMBER: + case LUA_TSTRING: + lua_pushvalue(L, idx); + break; + case LUA_TBOOLEAN: + lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false")); + break; + case LUA_TNIL: + lua_pushliteral(L, "nil"); + break; + default: + lua_pushfstring(L, "%s: %p", luaL_typename(L, idx), + lua_topointer(L, idx)); + break; + } + } + return lua_tolstring(L, -1, len); +} + + +/* +** {====================================================== +** Compatibility with 5.1 module functions +** ======================================================= +*/ +#if defined(LUA_COMPAT_MODULE) + +static const char *luaL_findtable (lua_State *L, int idx, + const char *fname, int szhint) { + const char *e; + if (idx) lua_pushvalue(L, idx); + do { + e = strchr(fname, '.'); + if (e == NULL) e = fname + strlen(fname); + lua_pushlstring(L, fname, e - fname); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { /* no such field? */ + lua_pop(L, 1); /* remove this nil */ + lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */ + lua_pushlstring(L, fname, e - fname); + lua_pushvalue(L, -2); + lua_settable(L, -4); /* set new table into field */ + } + else if (!lua_istable(L, -1)) { /* field has a non-table value? */ + lua_pop(L, 2); /* remove table and value */ + return fname; /* return problematic part of the name */ + } + lua_remove(L, -2); /* remove previous table */ + fname = e + 1; + } while (*e == '.'); + return NULL; +} + + +/* +** Count number of elements in a luaL_Reg list. +*/ +static int libsize (const luaL_Reg *l) { + int size = 0; + for (; l && l->name; l++) size++; + return size; +} + + +/* +** Find or create a module table with a given name. The function +** first looks at the _LOADED table and, if that fails, try a +** global variable with that name. In any case, leaves on the stack +** the module table. +*/ +LUALIB_API void luaL_pushmodule (lua_State *L, const char *modname, + int sizehint) { + luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); /* get _LOADED table */ + lua_getfield(L, -1, modname); /* get _LOADED[modname] */ + if (!lua_istable(L, -1)) { /* not found? */ + lua_pop(L, 1); /* remove previous result */ + /* try global variable (and create one if it does not exist) */ + lua_pushglobaltable(L); + if (luaL_findtable(L, 0, modname, sizehint) != NULL) + luaL_error(L, "name conflict for module " LUA_QS, modname); + lua_pushvalue(L, -1); + lua_setfield(L, -3, modname); /* _LOADED[modname] = new table */ + } + lua_remove(L, -2); /* remove _LOADED table */ +} + + +LUALIB_API void luaL_openlib (lua_State *L, const char *libname, + const luaL_Reg *l, int nup) { + luaL_checkversion(L); + if (libname) { + luaL_pushmodule(L, libname, libsize(l)); /* get/create library table */ + lua_insert(L, -(nup + 1)); /* move library table to below upvalues */ + } + if (l) + luaL_setfuncs(L, l, nup); + else + lua_pop(L, nup); /* remove upvalues */ +} + +#endif +/* }====================================================== */ + +/* +** set functions from list 'l' into table at top - 'nup'; each +** function gets the 'nup' elements at the top as upvalues. +** Returns with only the table at the stack. +*/ +LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { + luaL_checkversion(L); + luaL_checkstack(L, nup, "too many upvalues"); + for (; l->name != NULL; l++) { /* fill the table with given functions */ + int i; + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -nup); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + lua_setfield(L, -(nup + 2), l->name); + } + lua_pop(L, nup); /* remove upvalues */ +} + + +/* +** ensure that stack[idx][fname] has a table and push that table +** into the stack +*/ +LUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) { + lua_getfield(L, idx, fname); + if (lua_istable(L, -1)) return 1; /* table already there */ + else { + lua_pop(L, 1); /* remove previous result */ + idx = lua_absindex(L, idx); + lua_newtable(L); + lua_pushvalue(L, -1); /* copy to be left at top */ + lua_setfield(L, idx, fname); /* assign new table to field */ + return 0; /* false, because did not find table there */ + } +} + + +/* +** stripped-down 'require'. Calls 'openf' to open a module, +** registers the result in 'package.loaded' table and, if 'glb' +** is true, also registers the result in the global table. +** Leaves resulting module on the top. +*/ +LUALIB_API void luaL_requiref (lua_State *L, const char *modname, + lua_CFunction openf, int glb) { + lua_pushcfunction(L, openf); + lua_pushstring(L, modname); /* argument to open function */ + lua_call(L, 1, 1); /* open module */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); + lua_pushvalue(L, -2); /* make copy of module (call result) */ + lua_setfield(L, -2, modname); /* _LOADED[modname] = module */ + lua_pop(L, 1); /* remove _LOADED table */ + if (glb) { + lua_pushvalue(L, -1); /* copy of 'mod' */ + lua_setglobal(L, modname); /* _G[modname] = module */ + } +} + + +LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, const char *p, + const char *r) { + const char *wild; + size_t l = strlen(p); + luaL_Buffer b; + luaL_buffinit(L, &b); + while ((wild = strstr(s, p)) != NULL) { + luaL_addlstring(&b, s, wild - s); /* push prefix */ + luaL_addstring(&b, r); /* push replacement in place of pattern */ + s = wild + l; /* continue after `p' */ + } + luaL_addstring(&b, s); /* push last suffix */ + luaL_pushresult(&b); + return lua_tostring(L, -1); +} + + +static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { + (void)ud; (void)osize; /* not used */ + if (nsize == 0) { + free(ptr); + return NULL; + } + else + return realloc(ptr, nsize); +} + + +static int panic (lua_State *L) { + luai_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", + lua_tostring(L, -1)); + return 0; /* return to Lua to abort */ +} + + +LUALIB_API lua_State *luaL_newstate (void) { + lua_State *L = lua_newstate(l_alloc, NULL); + if (L) lua_atpanic(L, &panic); + return L; +} + + +LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver) { + const lua_Number *v = lua_version(L); + if (v != lua_version(NULL)) + luaL_error(L, "multiple Lua VMs detected"); + else if (*v != ver) + luaL_error(L, "version mismatch: app. needs %f, Lua core provides %f", + ver, *v); + /* check conversions number -> integer types */ + lua_pushnumber(L, -(lua_Number)0x1234); + if (lua_tointeger(L, -1) != -0x1234 || + lua_tounsigned(L, -1) != (lua_Unsigned)-0x1234) + luaL_error(L, "bad conversion number->int;" + " must recompile Lua with proper settings"); + lua_pop(L, 1); +} + diff --git a/luprex/ext/eris-master/src/lauxlib.h b/luprex/ext/eris-master/src/lauxlib.h new file mode 100644 index 00000000..357566e9 --- /dev/null +++ b/luprex/ext/eris-master/src/lauxlib.h @@ -0,0 +1,213 @@ +/* +** $Id: lauxlib.h,v 1.120.1.1 2013/04/12 18:48:47 roberto Exp $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lauxlib_h +#define lauxlib_h + + +#include +#include + +#include "lua.h" + + + +/* extra error code for `luaL_load' */ +#define LUA_ERRFILE (LUA_ERRERR+1) + + +typedef struct luaL_Reg { + const char *name; + lua_CFunction func; +} luaL_Reg; + + +LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver); +#define luaL_checkversion(L) luaL_checkversion_(L, LUA_VERSION_NUM) + +LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); +LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); +LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg, + size_t *l); +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg, + const char *def, size_t *l); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def); + +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg, + lua_Integer def); +LUALIB_API lua_Unsigned (luaL_checkunsigned) (lua_State *L, int numArg); +LUALIB_API lua_Unsigned (luaL_optunsigned) (lua_State *L, int numArg, + lua_Unsigned def); + +LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); +LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int narg); + +LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); +LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); +LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname); +LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); + +LUALIB_API void (luaL_where) (lua_State *L, int lvl); +LUALIB_API void (luaL_no_where) (lua_State *L, int lvl); +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); + +LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def, + const char *const lst[]); + +LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); +LUALIB_API int (luaL_execresult) (lua_State *L, int stat); + +/* pre-defined references */ +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) + +LUALIB_API int (luaL_ref) (lua_State *L, int t); +LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); + +LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, + const char *mode); + +#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL) + +LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, + const char *name, const char *mode); +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); + +LUALIB_API lua_State *(luaL_newstate) (void); + +LUALIB_API int (luaL_len) (lua_State *L, int idx); + +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, + const char *r); + +LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); + +LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname); + +LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1, + const char *msg, int level); + +LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, + lua_CFunction openf, int glb); + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + + +#define luaL_newlibtable(L,l) \ + lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) + +#define luaL_newlib(L,l) (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) + +#define luaL_argcheck(L, cond,numarg,extramsg) \ + ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) +#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) + +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) + +#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + +#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL) + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +typedef struct luaL_Buffer { + char *b; /* buffer address */ + size_t size; /* buffer size */ + size_t n; /* number of characters in buffer */ + lua_State *L; + char initb[LUAL_BUFFERSIZE]; /* initial buffer */ +} luaL_Buffer; + + +#define luaL_addchar(B,c) \ + ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \ + ((B)->b[(B)->n++] = (c))) + +#define luaL_addsize(B,s) ((B)->n += (s)) + +LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz); +LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); +LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz); +LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz); + +#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE) + +/* }====================================================== */ + + + +/* +** {====================================================== +** File handles for IO library +** ======================================================= +*/ + +/* +** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and +** initial structure 'luaL_Stream' (it may contain other fields +** after that initial structure). +*/ + +#define LUA_FILEHANDLE "FILE*" + + +typedef struct luaL_Stream { + FILE *f; /* stream (NULL for incompletely created streams) */ + lua_CFunction closef; /* to close stream (NULL for closed streams) */ +} luaL_Stream; + +/* }====================================================== */ + + + +/* compatibility with old module system */ +#if defined(LUA_COMPAT_MODULE) + +LUALIB_API void (luaL_pushmodule) (lua_State *L, const char *modname, + int sizehint); +LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname, + const luaL_Reg *l, int nup); + +#define luaL_register(L,n,l) (luaL_openlib(L,(n),(l),0)) + +#endif + + +#endif + + diff --git a/luprex/ext/eris-master/src/lbaselib.c b/luprex/ext/eris-master/src/lbaselib.c new file mode 100644 index 00000000..e24c6572 --- /dev/null +++ b/luprex/ext/eris-master/src/lbaselib.c @@ -0,0 +1,503 @@ +/* +** $Id: lbaselib.c,v 1.276.1.1 2013/04/12 18:48:47 roberto Exp $ +** Basic library +** See Copyright Notice in lua.h +*/ + + + +#include +#include +#include +#include + +#define lbaselib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +static int luaB_print (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int i; + lua_getglobal(L, "tostring"); + for (i=1; i<=n; i++) { + const char *s; + size_t l; + lua_pushvalue(L, -1); /* function to be called */ + lua_pushvalue(L, i); /* value to print */ + lua_call(L, 1, 1); + s = lua_tolstring(L, -1, &l); /* get result */ + if (s == NULL) + return luaL_error(L, + LUA_QL("tostring") " must return a string to " LUA_QL("print")); + if (i>1) luai_writestring("\t", 1); + luai_writestring(s, l); + lua_pop(L, 1); /* pop result */ + } + luai_writeline(); + return 0; +} + + +#define SPACECHARS " \f\n\r\t\v" + +static int luaB_tonumber (lua_State *L) { + if (lua_isnoneornil(L, 2)) { /* standard conversion */ + int isnum; + lua_Number n = lua_tonumberx(L, 1, &isnum); + if (isnum) { + lua_pushnumber(L, n); + return 1; + } /* else not a number; must be something */ + luaL_checkany(L, 1); + } + else { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + const char *e = s + l; /* end point for 's' */ + int base = luaL_checkint(L, 2); + int neg = 0; + luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); + s += strspn(s, SPACECHARS); /* skip initial spaces */ + if (*s == '-') { s++; neg = 1; } /* handle signal */ + else if (*s == '+') s++; + if (isalnum((unsigned char)*s)) { + lua_Number n = 0; + do { + int digit = (isdigit((unsigned char)*s)) ? *s - '0' + : toupper((unsigned char)*s) - 'A' + 10; + if (digit >= base) break; /* invalid numeral; force a fail */ + n = n * (lua_Number)base + (lua_Number)digit; + s++; + } while (isalnum((unsigned char)*s)); + s += strspn(s, SPACECHARS); /* skip trailing spaces */ + if (s == e) { /* no invalid trailing characters? */ + lua_pushnumber(L, (neg) ? -n : n); + return 1; + } /* else not a number */ + } /* else not a number */ + } + lua_pushnil(L); /* not a number */ + return 1; +} + + +static int luaB_error (lua_State *L) { + int level = luaL_optint(L, 2, 1); + lua_settop(L, 1); + if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ + luaL_no_where(L, level); + lua_pushvalue(L, 1); + lua_concat(L, 2); + } + return lua_error(L); +} + + +static int luaB_getmetatable (lua_State *L) { + luaL_checkany(L, 1); + if (!lua_getmetatable(L, 1)) { + lua_pushnil(L); + return 1; /* no metatable */ + } + luaL_getmetafield(L, 1, "__metatable"); + return 1; /* returns either __metatable field (if present) or metatable */ +} + + +static int luaB_setmetatable (lua_State *L) { + int t = lua_type(L, 2); + luaL_checktype(L, 1, LUA_TTABLE); + luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, + "nil or table expected"); + if (luaL_getmetafield(L, 1, "__metatable")) + return luaL_error(L, "cannot change a protected metatable"); + lua_settop(L, 2); + lua_setmetatable(L, 1); + return 1; +} + + +static int luaB_rawequal (lua_State *L) { + luaL_checkany(L, 1); + luaL_checkany(L, 2); + lua_pushboolean(L, lua_rawequal(L, 1, 2)); + return 1; +} + + +static int luaB_rawlen (lua_State *L) { + int t = lua_type(L, 1); + luaL_argcheck(L, t == LUA_TTABLE || t == LUA_TSTRING, 1, + "table or string expected"); + lua_pushinteger(L, lua_rawlen(L, 1)); + return 1; +} + + +static int luaB_rawget (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checkany(L, 2); + lua_settop(L, 2); + lua_rawget(L, 1); + return 1; +} + +static int luaB_rawset (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checkany(L, 2); + luaL_checkany(L, 3); + lua_settop(L, 3); + lua_rawset(L, 1); + return 1; +} + + +static int luaB_collectgarbage (lua_State *L) { + static const char *const opts[] = {"stop", "restart", "collect", + "count", "step", "setpause", "setstepmul", + "setmajorinc", "isrunning", "generational", "incremental", NULL}; + static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, + LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL, + LUA_GCSETMAJORINC, LUA_GCISRUNNING, LUA_GCGEN, LUA_GCINC}; + int o = optsnum[luaL_checkoption(L, 1, "collect", opts)]; + int ex = luaL_optint(L, 2, 0); + int res = lua_gc(L, o, ex); + switch (o) { + case LUA_GCCOUNT: { + int b = lua_gc(L, LUA_GCCOUNTB, 0); + lua_pushnumber(L, res + ((lua_Number)b/1024)); + lua_pushinteger(L, b); + return 2; + } + case LUA_GCSTEP: case LUA_GCISRUNNING: { + lua_pushboolean(L, res); + return 1; + } + default: { + lua_pushinteger(L, res); + return 1; + } + } +} + + +static int luaB_type (lua_State *L) { + luaL_checkany(L, 1); + lua_pushstring(L, luaL_typename(L, 1)); + return 1; +} + + +static int pairsmeta (lua_State *L, const char *method, int iszero, + lua_CFunction iter) { + if (!luaL_getmetafield(L, 1, method)) { /* no metamethod? */ + luaL_checktype(L, 1, LUA_TTABLE); /* argument must be a table */ + lua_pushcfunction(L, iter); /* will return generator, */ + lua_pushvalue(L, 1); /* state, */ + if (iszero) lua_pushinteger(L, 0); /* and initial value */ + else lua_pushnil(L); + } + else { + lua_pushvalue(L, 1); /* argument 'self' to metamethod */ + lua_call(L, 1, 3); /* get 3 values from metamethod */ + } + return 3; +} + +static int luaB_next (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + if (lua_next(L, 1)) + return 2; + else { + lua_pushnil(L); + return 1; + } +} + + +static int luaB_pairs (lua_State *L) { + return pairsmeta(L, "__pairs", 0, luaB_next); +} + + +static int luaB_rawpairs (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); /* argument must be a table */ + lua_pushcfunction(L, luaB_next); /* will return generator, */ + lua_pushvalue(L, 1); /* table */ + lua_pushnil(L); + return 3; +} + + +static int ipairsaux (lua_State *L) { + int i = luaL_checkint(L, 2); + luaL_checktype(L, 1, LUA_TTABLE); + i++; /* next value */ + lua_pushinteger(L, i); + lua_rawgeti(L, 1, i); + return (lua_isnil(L, -1)) ? 1 : 2; +} + + +static int luaB_ipairs (lua_State *L) { + return pairsmeta(L, "__ipairs", 1, ipairsaux); +} + + +static int load_aux (lua_State *L, int status, int envidx) { + if (status == LUA_OK) { + if (envidx != 0) { /* 'env' parameter? */ + lua_pushvalue(L, envidx); /* environment for loaded function */ + if (!lua_setupvalue(L, -2, 1)) /* set it as 1st upvalue */ + lua_pop(L, 1); /* remove 'env' if not used by previous call */ + } + return 1; + } + else { /* error (message is on top of the stack) */ + lua_pushnil(L); + lua_insert(L, -2); /* put before error message */ + return 2; /* return nil plus error message */ + } +} + + +static int luaB_loadfile (lua_State *L) { + const char *fname = luaL_optstring(L, 1, NULL); + const char *mode = luaL_optstring(L, 2, NULL); + int env = (!lua_isnone(L, 3) ? 3 : 0); /* 'env' index or 0 if no 'env' */ + int status = luaL_loadfilex(L, fname, mode); + return load_aux(L, status, env); +} + + +/* +** {====================================================== +** Generic Read function +** ======================================================= +*/ + + +/* +** reserved slot, above all arguments, to hold a copy of the returned +** string to avoid it being collected while parsed. 'load' has four +** optional arguments (chunk, source name, mode, and environment). +*/ +#define RESERVEDSLOT 5 + + +/* +** Reader for generic `load' function: `lua_load' uses the +** stack for internal stuff, so the reader cannot change the +** stack top. Instead, it keeps its resulting string in a +** reserved slot inside the stack. +*/ +static const char *generic_reader (lua_State *L, void *ud, size_t *size) { + (void)(ud); /* not used */ + luaL_checkstack(L, 2, "too many nested functions"); + lua_pushvalue(L, 1); /* get function */ + lua_call(L, 0, 1); /* call it */ + if (lua_isnil(L, -1)) { + lua_pop(L, 1); /* pop result */ + *size = 0; + return NULL; + } + else if (!lua_isstring(L, -1)) + luaL_error(L, "reader function must return a string"); + lua_replace(L, RESERVEDSLOT); /* save string in reserved slot */ + return lua_tolstring(L, RESERVEDSLOT, size); +} + + +static int luaB_load (lua_State *L) { + int status; + size_t l; + const char *s = lua_tolstring(L, 1, &l); + const char *mode = luaL_optstring(L, 3, "bt"); + int env = (!lua_isnone(L, 4) ? 4 : 0); /* 'env' index or 0 if no 'env' */ + if (s != NULL) { /* loading a string? */ + const char *chunkname = luaL_optstring(L, 2, s); + status = luaL_loadbufferx(L, s, l, chunkname, mode); + } + else { /* loading from a reader function */ + const char *chunkname = luaL_optstring(L, 2, "=(load)"); + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_settop(L, RESERVEDSLOT); /* create reserved slot */ + status = lua_load(L, generic_reader, NULL, chunkname, mode); + } + return load_aux(L, status, env); +} + +/* }====================================================== */ + + +static int dofilecont (lua_State *L) { + return lua_gettop(L) - 1; +} + + +static int luaB_dofile (lua_State *L) { + const char *fname = luaL_optstring(L, 1, NULL); + lua_settop(L, 1); + if (luaL_loadfile(L, fname) != LUA_OK) + return lua_error(L); + lua_callk(L, 0, LUA_MULTRET, 0, dofilecont); + return dofilecont(L); +} + + +static int luaB_assert (lua_State *L) { + if (!lua_toboolean(L, 1)) + return luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!")); + return lua_gettop(L); +} + + +static int luaB_select (lua_State *L) { + int n = lua_gettop(L); + if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#') { + lua_pushinteger(L, n-1); + return 1; + } + else { + int i = luaL_checkint(L, 1); + if (i < 0) i = n + i; + else if (i > n) i = n; + luaL_argcheck(L, 1 <= i, 1, "index out of range"); + return n - i; + } +} + + +static int finishpcall (lua_State *L, int status) { + if (!lua_checkstack(L, 1)) { /* no space for extra boolean? */ + lua_settop(L, 0); /* create space for return values */ + lua_pushboolean(L, 0); + lua_pushstring(L, "stack overflow"); + return 2; /* return false, msg */ + } + lua_pushboolean(L, status); /* first result (status) */ + lua_replace(L, 1); /* put first result in first slot */ + return lua_gettop(L); +} + + +static int pcallcont (lua_State *L) { + int status = lua_getctx(L, NULL); + return finishpcall(L, (status == LUA_YIELD)); +} + + +static int luaB_pcall (lua_State *L) { + int status; + luaL_checkany(L, 1); + lua_pushnil(L); + lua_insert(L, 1); /* create space for status result */ + status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, pcallcont); + return finishpcall(L, (status == LUA_OK)); +} + + +static int luaB_xpcall (lua_State *L) { + int status; + int n = lua_gettop(L); + luaL_argcheck(L, n >= 2, 2, "value expected"); + lua_pushvalue(L, 1); /* exchange function... */ + lua_copy(L, 2, 1); /* ...and error handler */ + lua_replace(L, 2); + status = lua_pcallk(L, n - 2, LUA_MULTRET, 1, 0, pcallcont); + return finishpcall(L, (status == LUA_OK)); +} + + +static int luaB_tostring (lua_State *L) { + luaL_checkany(L, 1); + luaL_tolstring(L, 1, NULL); + return 1; +} + + +static const luaL_Reg base_funcs[] = { + {"assert", luaB_assert}, + {"collectgarbage", luaB_collectgarbage}, + {"dofile", luaB_dofile}, + {"error", luaB_error}, + {"getmetatable", luaB_getmetatable}, + {"ipairs", luaB_ipairs}, + {"loadfile", luaB_loadfile}, + {"load", luaB_load}, +#if defined(LUA_COMPAT_LOADSTRING) + {"loadstring", luaB_load}, +#endif + {"next", luaB_next}, + {"pairs", luaB_pairs}, + {"rawpairs", luaB_rawpairs}, + {"pcall", luaB_pcall}, + {"print", luaB_print}, + {"rawequal", luaB_rawequal}, + {"rawlen", luaB_rawlen}, + {"rawget", luaB_rawget}, + {"rawset", luaB_rawset}, + {"select", luaB_select}, + {"setmetatable", luaB_setmetatable}, + {"tonumber", luaB_tonumber}, + {"tostring", luaB_tostring}, + {"type", luaB_type}, + {"xpcall", luaB_xpcall}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_base (lua_State *L) { + /* set global _G */ + lua_pushglobaltable(L); + lua_pushglobaltable(L); + lua_setfield(L, -2, "_G"); + /* open lib into global table */ + luaL_setfuncs(L, base_funcs, 0); + lua_pushliteral(L, LUA_VERSION); + lua_setfield(L, -2, "_VERSION"); /* set global _VERSION */ + return 1; +} + + +void eris_permbaselib(lua_State *L, int forUnpersist) { + luaL_checktype(L, -1, LUA_TTABLE); + luaL_checkstack(L, 2, NULL); + + if (forUnpersist) { + lua_pushstring(L, "__eris.baselib_pcallcont"); + lua_pushcfunction(L, pcallcont); + } + else { + lua_pushcfunction(L, pcallcont); + lua_pushstring(L, "__eris.baselib_pcallcont"); + } + lua_rawset(L, -3); + + if (forUnpersist) { + lua_pushstring(L, "__eris.baselib_luaB_next"); + lua_pushcfunction(L, luaB_next); + } + else { + lua_pushcfunction(L, luaB_next); + lua_pushstring(L, "__eris.baselib_luaB_next"); + } + lua_rawset(L, -3); + + if (forUnpersist) { + lua_pushstring(L, "__eris.baselib_ipairsaux"); + lua_pushcfunction(L, ipairsaux); + } + else { + lua_pushcfunction(L, ipairsaux); + lua_pushstring(L, "__eris.baselib_ipairsaux"); + } + lua_rawset(L, -3); +} + diff --git a/luprex/ext/eris-master/src/lbitlib.c b/luprex/ext/eris-master/src/lbitlib.c new file mode 100644 index 00000000..31c7b66f --- /dev/null +++ b/luprex/ext/eris-master/src/lbitlib.c @@ -0,0 +1,212 @@ +/* +** $Id: lbitlib.c,v 1.18.1.2 2013/07/09 18:01:41 roberto Exp $ +** Standard library for bitwise operations +** See Copyright Notice in lua.h +*/ + +#define lbitlib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* number of bits to consider in a number */ +#if !defined(LUA_NBITS) +#define LUA_NBITS 32 +#endif + + +#define ALLONES (~(((~(lua_Unsigned)0) << (LUA_NBITS - 1)) << 1)) + +/* macro to trim extra bits */ +#define trim(x) ((x) & ALLONES) + + +/* builds a number with 'n' ones (1 <= n <= LUA_NBITS) */ +#define mask(n) (~((ALLONES << 1) << ((n) - 1))) + + +typedef lua_Unsigned b_uint; + + + +static b_uint andaux (lua_State *L) { + int i, n = lua_gettop(L); + b_uint r = ~(b_uint)0; + for (i = 1; i <= n; i++) + r &= luaL_checkunsigned(L, i); + return trim(r); +} + + +static int b_and (lua_State *L) { + b_uint r = andaux(L); + lua_pushunsigned(L, r); + return 1; +} + + +static int b_test (lua_State *L) { + b_uint r = andaux(L); + lua_pushboolean(L, r != 0); + return 1; +} + + +static int b_or (lua_State *L) { + int i, n = lua_gettop(L); + b_uint r = 0; + for (i = 1; i <= n; i++) + r |= luaL_checkunsigned(L, i); + lua_pushunsigned(L, trim(r)); + return 1; +} + + +static int b_xor (lua_State *L) { + int i, n = lua_gettop(L); + b_uint r = 0; + for (i = 1; i <= n; i++) + r ^= luaL_checkunsigned(L, i); + lua_pushunsigned(L, trim(r)); + return 1; +} + + +static int b_not (lua_State *L) { + b_uint r = ~luaL_checkunsigned(L, 1); + lua_pushunsigned(L, trim(r)); + return 1; +} + + +static int b_shift (lua_State *L, b_uint r, int i) { + if (i < 0) { /* shift right? */ + i = -i; + r = trim(r); + if (i >= LUA_NBITS) r = 0; + else r >>= i; + } + else { /* shift left */ + if (i >= LUA_NBITS) r = 0; + else r <<= i; + r = trim(r); + } + lua_pushunsigned(L, r); + return 1; +} + + +static int b_lshift (lua_State *L) { + return b_shift(L, luaL_checkunsigned(L, 1), luaL_checkint(L, 2)); +} + + +static int b_rshift (lua_State *L) { + return b_shift(L, luaL_checkunsigned(L, 1), -luaL_checkint(L, 2)); +} + + +static int b_arshift (lua_State *L) { + b_uint r = luaL_checkunsigned(L, 1); + int i = luaL_checkint(L, 2); + if (i < 0 || !(r & ((b_uint)1 << (LUA_NBITS - 1)))) + return b_shift(L, r, -i); + else { /* arithmetic shift for 'negative' number */ + if (i >= LUA_NBITS) r = ALLONES; + else + r = trim((r >> i) | ~(~(b_uint)0 >> i)); /* add signal bit */ + lua_pushunsigned(L, r); + return 1; + } +} + + +static int b_rot (lua_State *L, int i) { + b_uint r = luaL_checkunsigned(L, 1); + i &= (LUA_NBITS - 1); /* i = i % NBITS */ + r = trim(r); + if (i != 0) /* avoid undefined shift of LUA_NBITS when i == 0 */ + r = (r << i) | (r >> (LUA_NBITS - i)); + lua_pushunsigned(L, trim(r)); + return 1; +} + + +static int b_lrot (lua_State *L) { + return b_rot(L, luaL_checkint(L, 2)); +} + + +static int b_rrot (lua_State *L) { + return b_rot(L, -luaL_checkint(L, 2)); +} + + +/* +** get field and width arguments for field-manipulation functions, +** checking whether they are valid. +** ('luaL_error' called without 'return' to avoid later warnings about +** 'width' being used uninitialized.) +*/ +static int fieldargs (lua_State *L, int farg, int *width) { + int f = luaL_checkint(L, farg); + int w = luaL_optint(L, farg + 1, 1); + luaL_argcheck(L, 0 <= f, farg, "field cannot be negative"); + luaL_argcheck(L, 0 < w, farg + 1, "width must be positive"); + if (f + w > LUA_NBITS) + luaL_error(L, "trying to access non-existent bits"); + *width = w; + return f; +} + + +static int b_extract (lua_State *L) { + int w; + b_uint r = luaL_checkunsigned(L, 1); + int f = fieldargs(L, 2, &w); + r = (r >> f) & mask(w); + lua_pushunsigned(L, r); + return 1; +} + + +static int b_replace (lua_State *L) { + int w; + b_uint r = luaL_checkunsigned(L, 1); + b_uint v = luaL_checkunsigned(L, 2); + int f = fieldargs(L, 3, &w); + int m = mask(w); + v &= m; /* erase bits outside given width */ + r = (r & ~(m << f)) | (v << f); + lua_pushunsigned(L, r); + return 1; +} + + +static const luaL_Reg bitlib[] = { + {"arshift", b_arshift}, + {"band", b_and}, + {"bnot", b_not}, + {"bor", b_or}, + {"bxor", b_xor}, + {"btest", b_test}, + {"extract", b_extract}, + {"lrotate", b_lrot}, + {"lshift", b_lshift}, + {"replace", b_replace}, + {"rrotate", b_rrot}, + {"rshift", b_rshift}, + {NULL, NULL} +}; + + + +LUAMOD_API int luaopen_bit32 (lua_State *L) { + luaL_newlib(L, bitlib); + return 1; +} + diff --git a/luprex/ext/eris-master/src/lcode.c b/luprex/ext/eris-master/src/lcode.c new file mode 100644 index 00000000..d39de516 --- /dev/null +++ b/luprex/ext/eris-master/src/lcode.c @@ -0,0 +1,883 @@ +/* +** $Id: lcode.c,v 2.62.1.1 2013/04/12 18:48:47 roberto Exp $ +** Code generator for Lua +** See Copyright Notice in lua.h +*/ + + +#include + +#define lcode_c +#define LUA_CORE + +#include "lua.h" + +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "llex.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstring.h" +#include "ltable.h" +#include "lvm.h" + + +#define hasjumps(e) ((e)->t != (e)->f) + + +static int isnumeral(expdesc *e) { + return (e->k == VKNUM && e->t == NO_JUMP && e->f == NO_JUMP); +} + + +void luaK_nil (FuncState *fs, int from, int n) { + Instruction *previous; + int l = from + n - 1; /* last register to set nil */ + if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ + previous = &fs->f->code[fs->pc-1]; + if (GET_OPCODE(*previous) == OP_LOADNIL) { + int pfrom = GETARG_A(*previous); + int pl = pfrom + GETARG_B(*previous); + if ((pfrom <= from && from <= pl + 1) || + (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ + if (pfrom < from) from = pfrom; /* from = min(from, pfrom) */ + if (pl > l) l = pl; /* l = max(l, pl) */ + SETARG_A(*previous, from); + SETARG_B(*previous, l - from); + return; + } + } /* else go through */ + } + luaK_codeABC(fs, OP_LOADNIL, from, n - 1, 0); /* else no optimization */ +} + + +int luaK_jump (FuncState *fs) { + int jpc = fs->jpc; /* save list of jumps to here */ + int j; + fs->jpc = NO_JUMP; + j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP); + luaK_concat(fs, &j, jpc); /* keep them on hold */ + return j; +} + + +void luaK_ret (FuncState *fs, int first, int nret) { + luaK_codeABC(fs, OP_RETURN, first, nret+1, 0); +} + + +static int condjump (FuncState *fs, OpCode op, int A, int B, int C) { + luaK_codeABC(fs, op, A, B, C); + return luaK_jump(fs); +} + + +static void fixjump (FuncState *fs, int pc, int dest) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest-(pc+1); + lua_assert(dest != NO_JUMP); + if (abs(offset) > MAXARG_sBx) + luaX_syntaxerror(fs->ls, "control structure too long"); + SETARG_sBx(*jmp, offset); +} + + +/* +** returns current `pc' and marks it as a jump target (to avoid wrong +** optimizations with consecutive instructions not in the same basic block). +*/ +int luaK_getlabel (FuncState *fs) { + fs->lasttarget = fs->pc; + return fs->pc; +} + + +static int getjump (FuncState *fs, int pc) { + int offset = GETARG_sBx(fs->f->code[pc]); + if (offset == NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return (pc+1)+offset; /* turn offset into absolute position */ +} + + +static Instruction *getjumpcontrol (FuncState *fs, int pc) { + Instruction *pi = &fs->f->code[pc]; + if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) + return pi-1; + else + return pi; +} + + +/* +** check whether list has any jump that do not produce a value +** (or produce an inverted value) +*/ +static int need_value (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) { + Instruction i = *getjumpcontrol(fs, list); + if (GET_OPCODE(i) != OP_TESTSET) return 1; + } + return 0; /* not found */ +} + + +static int patchtestreg (FuncState *fs, int node, int reg) { + Instruction *i = getjumpcontrol(fs, node); + if (GET_OPCODE(*i) != OP_TESTSET) + return 0; /* cannot patch other instructions */ + if (reg != NO_REG && reg != GETARG_B(*i)) + SETARG_A(*i, reg); + else /* no register to put value or register already has the value */ + *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); + + return 1; +} + + +static void removevalues (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) + patchtestreg(fs, list, NO_REG); +} + + +static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, + int dtarget) { + while (list != NO_JUMP) { + int next = getjump(fs, list); + if (patchtestreg(fs, list, reg)) + fixjump(fs, list, vtarget); + else + fixjump(fs, list, dtarget); /* jump to default target */ + list = next; + } +} + + +static void dischargejpc (FuncState *fs) { + patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc); + fs->jpc = NO_JUMP; +} + + +void luaK_patchlist (FuncState *fs, int list, int target) { + if (target == fs->pc) + luaK_patchtohere(fs, list); + else { + lua_assert(target < fs->pc); + patchlistaux(fs, list, target, NO_REG, target); + } +} + + +LUAI_FUNC void luaK_patchclose (FuncState *fs, int list, int level) { + level++; /* argument is +1 to reserve 0 as non-op */ + while (list != NO_JUMP) { + int next = getjump(fs, list); + lua_assert(GET_OPCODE(fs->f->code[list]) == OP_JMP && + (GETARG_A(fs->f->code[list]) == 0 || + GETARG_A(fs->f->code[list]) >= level)); + SETARG_A(fs->f->code[list], level); + list = next; + } +} + + +void luaK_patchtohere (FuncState *fs, int list) { + luaK_getlabel(fs); + luaK_concat(fs, &fs->jpc, list); +} + + +void luaK_concat (FuncState *fs, int *l1, int l2) { + if (l2 == NO_JUMP) return; + else if (*l1 == NO_JUMP) + *l1 = l2; + else { + int list = *l1; + int next; + while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ + list = next; + fixjump(fs, list, l2); + } +} + + +static int luaK_code (FuncState *fs, Instruction i) { + Proto *f = fs->f; + dischargejpc(fs); /* `pc' will change */ + /* put new instruction in code array */ + luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction, + MAX_INT, "opcodes"); + f->code[fs->pc] = i; + /* save corresponding line information */ + luaM_growvector(fs->ls->L, f->lineinfo, fs->pc, f->sizelineinfo, int, + MAX_INT, "opcodes"); + f->lineinfo[fs->pc] = fs->ls->lastline; + return fs->pc++; +} + + +int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { + lua_assert(getOpMode(o) == iABC); + lua_assert(getBMode(o) != OpArgN || b == 0); + lua_assert(getCMode(o) != OpArgN || c == 0); + lua_assert(a <= MAXARG_A && b <= MAXARG_B && c <= MAXARG_C); + return luaK_code(fs, CREATE_ABC(o, a, b, c)); +} + + +int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { + lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); + lua_assert(getCMode(o) == OpArgN); + lua_assert(a <= MAXARG_A && bc <= MAXARG_Bx); + return luaK_code(fs, CREATE_ABx(o, a, bc)); +} + + +static int codeextraarg (FuncState *fs, int a) { + lua_assert(a <= MAXARG_Ax); + return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a)); +} + + +int luaK_codek (FuncState *fs, int reg, int k) { + if (k <= MAXARG_Bx) + return luaK_codeABx(fs, OP_LOADK, reg, k); + else { + int p = luaK_codeABx(fs, OP_LOADKX, reg, 0); + codeextraarg(fs, k); + return p; + } +} + + +void luaK_checkstack (FuncState *fs, int n) { + int newstack = fs->freereg + n; + if (newstack > fs->f->maxstacksize) { + if (newstack >= MAXSTACK) + luaX_syntaxerror(fs->ls, "function or expression too complex"); + fs->f->maxstacksize = cast_byte(newstack); + } +} + + +void luaK_reserveregs (FuncState *fs, int n) { + luaK_checkstack(fs, n); + fs->freereg += n; +} + + +static void freereg (FuncState *fs, int reg) { + if (!ISK(reg) && reg >= fs->nactvar) { + fs->freereg--; + lua_assert(reg == fs->freereg); + } +} + + +static void freeexp (FuncState *fs, expdesc *e) { + if (e->k == VNONRELOC) + freereg(fs, e->u.info); +} + + +static int addk (FuncState *fs, TValue *key, TValue *v) { + lua_State *L = fs->ls->L; + const TValue *idx = luaH_get(fs->h, key); + Proto *f = fs->f; + int k, oldsize; + if (ttisnumber(idx)) { + lua_Number n = nvalue(idx); + lua_number2int(k, n); + if (luaV_rawequalobj(&f->k[k], v)) + return k; + /* else may be a collision (e.g., between 0.0 and "\0\0\0\0\0\0\0\0"); + go through and create a new entry for this value */ + } + /* constant not found; create a new entry */ + oldsize = f->sizek; + k = fs->nk; + /* numerical value does not need GC barrier; + table has no metatable, so it does not need to invalidate cache */ + TValue kv; + setnvalue(&kv, cast_num(k)); + luaH_setvalue (L, fs->h, key, &kv); + luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, "constants"); + while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); + setobj(L, &f->k[k], v); + fs->nk++; + luaC_barrier(L, f, v); + return k; +} + + +int luaK_stringK (FuncState *fs, TString *s) { + TValue o; + setsvalue(fs->ls->L, &o, s); + return addk(fs, &o, &o); +} + + +int luaK_numberK (FuncState *fs, lua_Number r) { + int n; + lua_State *L = fs->ls->L; + TValue o; + setnvalue(&o, r); + if (r == 0 || luai_numisnan(NULL, r)) { /* handle -0 and NaN */ + /* use raw representation as key to avoid numeric problems */ + setsvalue(L, L->top++, luaS_newlstr(L, (char *)&r, sizeof(r))); + n = addk(fs, L->top - 1, &o); + L->top--; + } + else + n = addk(fs, &o, &o); /* regular case */ + return n; +} + + +static int boolK (FuncState *fs, int b) { + TValue o; + setbvalue(&o, b); + return addk(fs, &o, &o); +} + + +static int nilK (FuncState *fs) { + TValue k, v; + setnilvalue(&v); + /* cannot use nil as key; instead use table itself to represent nil */ + sethvalue(fs->ls->L, &k, fs->h); + return addk(fs, &k, &v); +} + + +void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { + if (e->k == VCALL) { /* expression is an open function call? */ + SETARG_C(getcode(fs, e), nresults+1); + } + else if (e->k == VVARARG) { + SETARG_B(getcode(fs, e), nresults+1); + SETARG_A(getcode(fs, e), fs->freereg); + luaK_reserveregs(fs, 1); + } +} + + +void luaK_setoneret (FuncState *fs, expdesc *e) { + if (e->k == VCALL) { /* expression is an open function call? */ + e->k = VNONRELOC; + e->u.info = GETARG_A(getcode(fs, e)); + } + else if (e->k == VVARARG) { + SETARG_B(getcode(fs, e), 2); + e->k = VRELOCABLE; /* can relocate its simple result */ + } +} + + +void luaK_dischargevars (FuncState *fs, expdesc *e) { + switch (e->k) { + case VLOCAL: { + e->k = VNONRELOC; + break; + } + case VUPVAL: { + e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0); + e->k = VRELOCABLE; + break; + } + case VINDEXED: { + OpCode op = OP_GETTABUP; /* assume 't' is in an upvalue */ + freereg(fs, e->u.ind.idx); + if (e->u.ind.vt == VLOCAL) { /* 't' is in a register? */ + freereg(fs, e->u.ind.t); + op = OP_GETTABLE; + } + e->u.info = luaK_codeABC(fs, op, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOCABLE; + break; + } + case VVARARG: + case VCALL: { + luaK_setoneret(fs, e); + break; + } + default: break; /* there is one value available (somewhere) */ + } +} + + +static int code_label (FuncState *fs, int A, int b, int jump) { + luaK_getlabel(fs); /* those instructions may be jump targets */ + return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); +} + + +static void discharge2reg (FuncState *fs, expdesc *e, int reg) { + luaK_dischargevars(fs, e); + switch (e->k) { + case VNIL: { + luaK_nil(fs, reg, 1); + break; + } + case VFALSE: case VTRUE: { + luaK_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, 0); + break; + } + case VK: { + luaK_codek(fs, reg, e->u.info); + break; + } + case VKNUM: { + luaK_codek(fs, reg, luaK_numberK(fs, e->u.nval)); + break; + } + case VRELOCABLE: { + Instruction *pc = &getcode(fs, e); + SETARG_A(*pc, reg); + break; + } + case VNONRELOC: { + if (reg != e->u.info) + luaK_codeABC(fs, OP_MOVE, reg, e->u.info, 0); + break; + } + default: { + lua_assert(e->k == VVOID || e->k == VJMP); + return; /* nothing to do... */ + } + } + e->u.info = reg; + e->k = VNONRELOC; +} + + +static void discharge2anyreg (FuncState *fs, expdesc *e) { + if (e->k != VNONRELOC) { + luaK_reserveregs(fs, 1); + discharge2reg(fs, e, fs->freereg-1); + } +} + + +static void exp2reg (FuncState *fs, expdesc *e, int reg) { + discharge2reg(fs, e, reg); + if (e->k == VJMP) + luaK_concat(fs, &e->t, e->u.info); /* put this jump in `t' list */ + if (hasjumps(e)) { + int final; /* position after whole expression */ + int p_f = NO_JUMP; /* position of an eventual LOAD false */ + int p_t = NO_JUMP; /* position of an eventual LOAD true */ + if (need_value(fs, e->t) || need_value(fs, e->f)) { + int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); + p_f = code_label(fs, reg, 0, 1); + p_t = code_label(fs, reg, 1, 0); + luaK_patchtohere(fs, fj); + } + final = luaK_getlabel(fs); + patchlistaux(fs, e->f, final, reg, p_f); + patchlistaux(fs, e->t, final, reg, p_t); + } + e->f = e->t = NO_JUMP; + e->u.info = reg; + e->k = VNONRELOC; +} + + +void luaK_exp2nextreg (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + freeexp(fs, e); + luaK_reserveregs(fs, 1); + exp2reg(fs, e, fs->freereg - 1); +} + + +int luaK_exp2anyreg (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + if (e->k == VNONRELOC) { + if (!hasjumps(e)) return e->u.info; /* exp is already in a register */ + if (e->u.info >= fs->nactvar) { /* reg. is not a local? */ + exp2reg(fs, e, e->u.info); /* put value on it */ + return e->u.info; + } + } + luaK_exp2nextreg(fs, e); /* default */ + return e->u.info; +} + + +void luaK_exp2anyregup (FuncState *fs, expdesc *e) { + if (e->k != VUPVAL || hasjumps(e)) + luaK_exp2anyreg(fs, e); +} + + +void luaK_exp2val (FuncState *fs, expdesc *e) { + if (hasjumps(e)) + luaK_exp2anyreg(fs, e); + else + luaK_dischargevars(fs, e); +} + + +int luaK_exp2RK (FuncState *fs, expdesc *e) { + luaK_exp2val(fs, e); + switch (e->k) { + case VTRUE: + case VFALSE: + case VNIL: { + if (fs->nk <= MAXINDEXRK) { /* constant fits in RK operand? */ + e->u.info = (e->k == VNIL) ? nilK(fs) : boolK(fs, (e->k == VTRUE)); + e->k = VK; + return RKASK(e->u.info); + } + else break; + } + case VKNUM: { + e->u.info = luaK_numberK(fs, e->u.nval); + e->k = VK; + /* go through */ + } + case VK: { + if (e->u.info <= MAXINDEXRK) /* constant fits in argC? */ + return RKASK(e->u.info); + else break; + } + default: break; + } + /* not a constant in the right range: put it in a register */ + return luaK_exp2anyreg(fs, e); +} + + +void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { + switch (var->k) { + case VLOCAL: { + freeexp(fs, ex); + exp2reg(fs, ex, var->u.info); + return; + } + case VUPVAL: { + int e = luaK_exp2anyreg(fs, ex); + luaK_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0); + break; + } + case VINDEXED: { + OpCode op = (var->u.ind.vt == VLOCAL) ? OP_SETTABLE : OP_SETTABUP; + int e = luaK_exp2RK(fs, ex); + luaK_codeABC(fs, op, var->u.ind.t, var->u.ind.idx, e); + break; + } + default: { + lua_assert(0); /* invalid var kind to store */ + break; + } + } + freeexp(fs, ex); +} + + +void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { + int ereg; + luaK_exp2anyreg(fs, e); + ereg = e->u.info; /* register where 'e' was placed */ + freeexp(fs, e); + e->u.info = fs->freereg; /* base register for op_self */ + e->k = VNONRELOC; + luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ + luaK_codeABC(fs, OP_SELF, e->u.info, ereg, luaK_exp2RK(fs, key)); + freeexp(fs, key); +} + + +static void invertjump (FuncState *fs, expdesc *e) { + Instruction *pc = getjumpcontrol(fs, e->u.info); + lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && + GET_OPCODE(*pc) != OP_TEST); + SETARG_A(*pc, !(GETARG_A(*pc))); +} + + +static int jumponcond (FuncState *fs, expdesc *e, int cond) { + if (e->k == VRELOCABLE) { + Instruction ie = getcode(fs, e); + if (GET_OPCODE(ie) == OP_NOT) { + fs->pc--; /* remove previous OP_NOT */ + return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond); + } + /* else go through */ + } + discharge2anyreg(fs, e); + freeexp(fs, e); + return condjump(fs, OP_TESTSET, NO_REG, e->u.info, cond); +} + + +void luaK_goiftrue (FuncState *fs, expdesc *e) { + int pc; /* pc of last jump */ + luaK_dischargevars(fs, e); + switch (e->k) { + case VJMP: { + invertjump(fs, e); + pc = e->u.info; + break; + } + case VK: case VKNUM: case VTRUE: { + pc = NO_JUMP; /* always true; do nothing */ + break; + } + default: { + pc = jumponcond(fs, e, 0); + break; + } + } + luaK_concat(fs, &e->f, pc); /* insert last jump in `f' list */ + luaK_patchtohere(fs, e->t); + e->t = NO_JUMP; +} + + +void luaK_goiffalse (FuncState *fs, expdesc *e) { + int pc; /* pc of last jump */ + luaK_dischargevars(fs, e); + switch (e->k) { + case VJMP: { + pc = e->u.info; + break; + } + case VNIL: case VFALSE: { + pc = NO_JUMP; /* always false; do nothing */ + break; + } + default: { + pc = jumponcond(fs, e, 1); + break; + } + } + luaK_concat(fs, &e->t, pc); /* insert last jump in `t' list */ + luaK_patchtohere(fs, e->f); + e->f = NO_JUMP; +} + + +static void codenot (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + switch (e->k) { + case VNIL: case VFALSE: { + e->k = VTRUE; + break; + } + case VK: case VKNUM: case VTRUE: { + e->k = VFALSE; + break; + } + case VJMP: { + invertjump(fs, e); + break; + } + case VRELOCABLE: + case VNONRELOC: { + discharge2anyreg(fs, e); + freeexp(fs, e); + e->u.info = luaK_codeABC(fs, OP_NOT, 0, e->u.info, 0); + e->k = VRELOCABLE; + break; + } + default: { + lua_assert(0); /* cannot happen */ + break; + } + } + /* interchange true and false lists */ + { int temp = e->f; e->f = e->t; e->t = temp; } + removevalues(fs, e->f); + removevalues(fs, e->t); +} + + +void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { + lua_assert(!hasjumps(t)); + t->u.ind.t = t->u.info; + t->u.ind.idx = luaK_exp2RK(fs, k); + t->u.ind.vt = (t->k == VUPVAL) ? VUPVAL + : check_exp(vkisinreg(t->k), VLOCAL); + t->k = VINDEXED; +} + + +static int constfolding (OpCode op, expdesc *e1, expdesc *e2) { + lua_Number r; + if (!isnumeral(e1) || !isnumeral(e2)) return 0; + if ((op == OP_DIV || op == OP_MOD) && e2->u.nval == 0) + return 0; /* do not attempt to divide by 0 */ + r = luaO_arith(op - OP_ADD + LUA_OPADD, e1->u.nval, e2->u.nval); + e1->u.nval = r; + return 1; +} + + +static void codearith (FuncState *fs, OpCode op, + expdesc *e1, expdesc *e2, int line) { + if (constfolding(op, e1, e2)) + return; + else { + int o2 = (op != OP_UNM && op != OP_LEN) ? luaK_exp2RK(fs, e2) : 0; + int o1 = luaK_exp2RK(fs, e1); + if (o1 > o2) { + freeexp(fs, e1); + freeexp(fs, e2); + } + else { + freeexp(fs, e2); + freeexp(fs, e1); + } + e1->u.info = luaK_codeABC(fs, op, 0, o1, o2); + e1->k = VRELOCABLE; + luaK_fixline(fs, line); + } +} + + +static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1, + expdesc *e2) { + int o1 = luaK_exp2RK(fs, e1); + int o2 = luaK_exp2RK(fs, e2); + freeexp(fs, e2); + freeexp(fs, e1); + if (cond == 0 && op != OP_EQ) { + int temp; /* exchange args to replace by `<' or `<=' */ + temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */ + cond = 1; + } + e1->u.info = condjump(fs, op, cond, o1, o2); + e1->k = VJMP; +} + + +void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e, int line) { + expdesc e2; + e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0; + switch (op) { + case OPR_MINUS: { + if (isnumeral(e)) /* minus constant? */ + e->u.nval = luai_numunm(NULL, e->u.nval); /* fold it */ + else { + luaK_exp2anyreg(fs, e); + codearith(fs, OP_UNM, e, &e2, line); + } + break; + } + case OPR_NOT: codenot(fs, e); break; + case OPR_LEN: { + luaK_exp2anyreg(fs, e); /* cannot operate on constants */ + codearith(fs, OP_LEN, e, &e2, line); + break; + } + default: lua_assert(0); + } +} + + +void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { + switch (op) { + case OPR_AND: { + luaK_goiftrue(fs, v); + break; + } + case OPR_OR: { + luaK_goiffalse(fs, v); + break; + } + case OPR_CONCAT: { + luaK_exp2nextreg(fs, v); /* operand must be on the `stack' */ + break; + } + case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: + case OPR_MOD: case OPR_POW: { + if (!isnumeral(v)) luaK_exp2RK(fs, v); + break; + } + default: { + luaK_exp2RK(fs, v); + break; + } + } +} + + +void luaK_posfix (FuncState *fs, BinOpr op, + expdesc *e1, expdesc *e2, int line) { + switch (op) { + case OPR_AND: { + lua_assert(e1->t == NO_JUMP); /* list must be closed */ + luaK_dischargevars(fs, e2); + luaK_concat(fs, &e2->f, e1->f); + *e1 = *e2; + break; + } + case OPR_OR: { + lua_assert(e1->f == NO_JUMP); /* list must be closed */ + luaK_dischargevars(fs, e2); + luaK_concat(fs, &e2->t, e1->t); + *e1 = *e2; + break; + } + case OPR_CONCAT: { + luaK_exp2val(fs, e2); + if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) { + lua_assert(e1->u.info == GETARG_B(getcode(fs, e2))-1); + freeexp(fs, e1); + SETARG_B(getcode(fs, e2), e1->u.info); + e1->k = VRELOCABLE; e1->u.info = e2->u.info; + } + else { + luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ + codearith(fs, OP_CONCAT, e1, e2, line); + } + break; + } + case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: + case OPR_MOD: case OPR_POW: { + codearith(fs, cast(OpCode, op - OPR_ADD + OP_ADD), e1, e2, line); + break; + } + case OPR_EQ: case OPR_LT: case OPR_LE: { + codecomp(fs, cast(OpCode, op - OPR_EQ + OP_EQ), 1, e1, e2); + break; + } + case OPR_NE: case OPR_GT: case OPR_GE: { + codecomp(fs, cast(OpCode, op - OPR_NE + OP_EQ), 0, e1, e2); + break; + } + default: lua_assert(0); + } +} + + +void luaK_fixline (FuncState *fs, int line) { + fs->f->lineinfo[fs->pc - 1] = line; +} + + +void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { + int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1; + int b = (tostore == LUA_MULTRET) ? 0 : tostore; + lua_assert(tostore != 0); + if (c <= MAXARG_C) + luaK_codeABC(fs, OP_SETLIST, base, b, c); + else if (c <= MAXARG_Ax) { + luaK_codeABC(fs, OP_SETLIST, base, b, 0); + codeextraarg(fs, c); + } + else + luaX_syntaxerror(fs->ls, "constructor too long"); + fs->freereg = base + 1; /* free registers with list values */ +} + diff --git a/luprex/ext/eris-master/src/lcode.h b/luprex/ext/eris-master/src/lcode.h new file mode 100644 index 00000000..6a1424cf --- /dev/null +++ b/luprex/ext/eris-master/src/lcode.h @@ -0,0 +1,83 @@ +/* +** $Id: lcode.h,v 1.58.1.1 2013/04/12 18:48:47 roberto Exp $ +** Code generator for Lua +** See Copyright Notice in lua.h +*/ + +#ifndef lcode_h +#define lcode_h + +#include "llex.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" + + +/* +** Marks the end of a patch list. It is an invalid value both as an absolute +** address, and as a list link (would link an element to itself). +*/ +#define NO_JUMP (-1) + + +/* +** grep "ORDER OPR" if you change these enums (ORDER OP) +*/ +typedef enum BinOpr { + OPR_ADD, OPR_SUB, OPR_MUL, OPR_DIV, OPR_MOD, OPR_POW, + OPR_CONCAT, + OPR_EQ, OPR_LT, OPR_LE, + OPR_NE, OPR_GT, OPR_GE, + OPR_AND, OPR_OR, + OPR_NOBINOPR +} BinOpr; + + +typedef enum UnOpr { OPR_MINUS, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; + + +#define getcode(fs,e) ((fs)->f->code[(e)->u.info]) + +#define luaK_codeAsBx(fs,o,A,sBx) luaK_codeABx(fs,o,A,(sBx)+MAXARG_sBx) + +#define luaK_setmultret(fs,e) luaK_setreturns(fs, e, LUA_MULTRET) + +#define luaK_jumpto(fs,t) luaK_patchlist(fs, luaK_jump(fs), t) + +LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx); +LUAI_FUNC int luaK_codeABC (FuncState *fs, OpCode o, int A, int B, int C); +LUAI_FUNC int luaK_codek (FuncState *fs, int reg, int k); +LUAI_FUNC void luaK_fixline (FuncState *fs, int line); +LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); +LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); +LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); +LUAI_FUNC int luaK_stringK (FuncState *fs, TString *s); +LUAI_FUNC int luaK_numberK (FuncState *fs, lua_Number r); +LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key); +LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k); +LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_goiffalse (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e); +LUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults); +LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_jump (FuncState *fs); +LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret); +LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target); +LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list); +LUAI_FUNC void luaK_patchclose (FuncState *fs, int list, int level); +LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2); +LUAI_FUNC int luaK_getlabel (FuncState *fs); +LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v, int line); +LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v); +LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1, + expdesc *v2, int line); +LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore); + + +#endif diff --git a/luprex/ext/eris-master/src/lcorolib.c b/luprex/ext/eris-master/src/lcorolib.c new file mode 100644 index 00000000..3e103f13 --- /dev/null +++ b/luprex/ext/eris-master/src/lcorolib.c @@ -0,0 +1,171 @@ +/* +** $Id: lcorolib.c,v 1.5.1.1 2013/04/12 18:48:47 roberto Exp $ +** Coroutine Library +** See Copyright Notice in lua.h +*/ + + +#include + + +#define lcorolib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +static int auxresume (lua_State *L, lua_State *co, int narg) { + int status; + if (!lua_checkstack(co, narg)) { + lua_pushliteral(L, "too many arguments to resume"); + return -1; /* error flag */ + } + if (lua_status(co) == LUA_OK && lua_gettop(co) == 0) { + lua_pushliteral(L, "cannot resume dead coroutine"); + return -1; /* error flag */ + } + lua_xmove(L, co, narg); + status = lua_resume(co, L, narg); + if (status == LUA_OK || status == LUA_YIELD) { + int nres = lua_gettop(co); + if (!lua_checkstack(L, nres + 1)) { + lua_pop(co, nres); /* remove results anyway */ + lua_pushliteral(L, "too many results to resume"); + return -1; /* error flag */ + } + lua_xmove(co, L, nres); /* move yielded values */ + return nres; + } + else { + lua_xmove(co, L, 1); /* move error message */ + return -1; /* error flag */ + } +} + + +static int luaB_coresume (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + int r; + luaL_argcheck(L, co, 1, "coroutine expected"); + r = auxresume(L, co, lua_gettop(L) - 1); + if (r < 0) { + lua_pushboolean(L, 0); + lua_insert(L, -2); + return 2; /* return false + error message */ + } + else { + lua_pushboolean(L, 1); + lua_insert(L, -(r + 1)); + return r + 1; /* return true + `resume' returns */ + } +} + + +static int luaB_auxwrap (lua_State *L) { + lua_State *co = lua_tothread(L, lua_upvalueindex(1)); + int r = auxresume(L, co, lua_gettop(L)); + if (r < 0) { + if (lua_isstring(L, -1)) { /* error object is a string? */ + luaL_no_where(L, 1); /* add extra info */ + lua_insert(L, -2); + lua_concat(L, 2); + } + return lua_error(L); /* propagate error */ + } + return r; +} + + +static int luaB_cocreate (lua_State *L) { + lua_State *NL; + luaL_checktype(L, 1, LUA_TFUNCTION); + NL = lua_newthread(L); + lua_pushvalue(L, 1); /* move function to top */ + lua_xmove(L, NL, 1); /* move function from L to NL */ + return 1; +} + + +static int luaB_cowrap (lua_State *L) { + luaB_cocreate(L); + lua_pushcclosure(L, luaB_auxwrap, 1); + return 1; +} + + +static int luaB_yield (lua_State *L) { + return lua_yield(L, lua_gettop(L)); +} + + +static int luaB_costatus (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argcheck(L, co, 1, "coroutine expected"); + if (L == co) lua_pushliteral(L, "running"); + else { + switch (lua_status(co)) { + case LUA_YIELD: + lua_pushliteral(L, "suspended"); + break; + case LUA_OK: { + lua_Debug ar; + if (lua_getstack(co, 0, &ar) > 0) /* does it have frames? */ + lua_pushliteral(L, "normal"); /* it is running */ + else if (lua_gettop(co) == 0) + lua_pushliteral(L, "dead"); + else + lua_pushliteral(L, "suspended"); /* initial state */ + break; + } + default: /* some error occurred */ + lua_pushliteral(L, "dead"); + break; + } + } + return 1; +} + + +static int luaB_corunning (lua_State *L) { + int ismain = lua_pushthread(L); + lua_pushboolean(L, ismain); + return 2; +} + + +static const luaL_Reg co_funcs[] = { + {"create", luaB_cocreate}, + {"resume", luaB_coresume}, + {"running", luaB_corunning}, + {"status", luaB_costatus}, + {"wrap", luaB_cowrap}, + {"yield", luaB_yield}, + {NULL, NULL} +}; + + + +LUAMOD_API int luaopen_coroutine (lua_State *L) { + luaL_newlib(L, co_funcs); + return 1; +} + + +void eris_permcorolib(lua_State *L, int forUnpersist) { + luaL_checktype(L, -1, LUA_TTABLE); + luaL_checkstack(L, 2, NULL); + + if (forUnpersist) { + lua_pushstring(L, "__eris.corolib_luaB_auxwrap"); + lua_pushcfunction(L, luaB_auxwrap); + } + else { + lua_pushcfunction(L, luaB_auxwrap); + lua_pushstring(L, "__eris.corolib_luaB_auxwrap"); + } + lua_rawset(L, -3); +} + diff --git a/luprex/ext/eris-master/src/lctype.c b/luprex/ext/eris-master/src/lctype.c new file mode 100644 index 00000000..93f8cadc --- /dev/null +++ b/luprex/ext/eris-master/src/lctype.c @@ -0,0 +1,52 @@ +/* +** $Id: lctype.c,v 1.11.1.1 2013/04/12 18:48:47 roberto Exp $ +** 'ctype' functions for Lua +** See Copyright Notice in lua.h +*/ + +#define lctype_c +#define LUA_CORE + +#include "lctype.h" + +#if !LUA_USE_CTYPE /* { */ + +#include + +LUAI_DDEF const lu_byte luai_ctype_[UCHAR_MAX + 2] = { + 0x00, /* EOZ */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0. */ + 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, /* 2. */ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, /* 3. */ + 0x16, 0x16, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x05, /* 4. */ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, /* 5. */ + 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x05, + 0x04, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x05, /* 6. */ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, /* 7. */ + 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 8. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 9. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* a. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* d. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* e. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* f. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#endif /* } */ diff --git a/luprex/ext/eris-master/src/lctype.h b/luprex/ext/eris-master/src/lctype.h new file mode 100644 index 00000000..b09b21a3 --- /dev/null +++ b/luprex/ext/eris-master/src/lctype.h @@ -0,0 +1,95 @@ +/* +** $Id: lctype.h,v 1.12.1.1 2013/04/12 18:48:47 roberto Exp $ +** 'ctype' functions for Lua +** See Copyright Notice in lua.h +*/ + +#ifndef lctype_h +#define lctype_h + +#include "lua.h" + + +/* +** WARNING: the functions defined here do not necessarily correspond +** to the similar functions in the standard C ctype.h. They are +** optimized for the specific needs of Lua +*/ + +#if !defined(LUA_USE_CTYPE) + +#if 'A' == 65 && '0' == 48 +/* ASCII case: can use its own tables; faster and fixed */ +#define LUA_USE_CTYPE 0 +#else +/* must use standard C ctype */ +#define LUA_USE_CTYPE 1 +#endif + +#endif + + +#if !LUA_USE_CTYPE /* { */ + +#include + +#include "llimits.h" + + +#define ALPHABIT 0 +#define DIGITBIT 1 +#define PRINTBIT 2 +#define SPACEBIT 3 +#define XDIGITBIT 4 + + +#define MASK(B) (1 << (B)) + + +/* +** add 1 to char to allow index -1 (EOZ) +*/ +#define testprop(c,p) (luai_ctype_[(c)+1] & (p)) + +/* +** 'lalpha' (Lua alphabetic) and 'lalnum' (Lua alphanumeric) both include '_' +*/ +#define lislalpha(c) testprop(c, MASK(ALPHABIT)) +#define lislalnum(c) testprop(c, (MASK(ALPHABIT) | MASK(DIGITBIT))) +#define lisdigit(c) testprop(c, MASK(DIGITBIT)) +#define lisspace(c) testprop(c, MASK(SPACEBIT)) +#define lisprint(c) testprop(c, MASK(PRINTBIT)) +#define lisxdigit(c) testprop(c, MASK(XDIGITBIT)) + +/* +** this 'ltolower' only works for alphabetic characters +*/ +#define ltolower(c) ((c) | ('A' ^ 'a')) + + +/* two more entries for 0 and -1 (EOZ) */ +LUAI_DDEC const lu_byte luai_ctype_[UCHAR_MAX + 2]; + + +#else /* }{ */ + +/* +** use standard C ctypes +*/ + +#include + + +#define lislalpha(c) (isalpha(c) || (c) == '_') +#define lislalnum(c) (isalnum(c) || (c) == '_') +#define lisdigit(c) (isdigit(c)) +#define lisspace(c) (isspace(c)) +#define lisprint(c) (isprint(c)) +#define lisxdigit(c) (isxdigit(c)) + +#define ltolower(c) (tolower(c)) + +#endif /* } */ + +#endif + diff --git a/luprex/ext/eris-master/src/ldblib.c b/luprex/ext/eris-master/src/ldblib.c new file mode 100644 index 00000000..bd8df9da --- /dev/null +++ b/luprex/ext/eris-master/src/ldblib.c @@ -0,0 +1,408 @@ +/* +** $Id: ldblib.c,v 1.132.1.2 2015/02/19 17:16:55 roberto Exp $ +** Interface from Lua to its debug API +** See Copyright Notice in lua.h +*/ + + +#include +#include +#include + +#define ldblib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#define HOOKKEY "_HKEY" + + +static void checkstack (lua_State *L, lua_State *L1, int n) { + if (L != L1 && !lua_checkstack(L1, n)) + luaL_error(L, "stack overflow"); +} + + +static int db_getregistry (lua_State *L) { + lua_pushvalue(L, LUA_REGISTRYINDEX); + return 1; +} + + +static int db_getmetatable (lua_State *L) { + luaL_checkany(L, 1); + if (!lua_getmetatable(L, 1)) { + lua_pushnil(L); /* no metatable */ + } + return 1; +} + + +static int db_setmetatable (lua_State *L) { + int t = lua_type(L, 2); + luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, + "nil or table expected"); + lua_settop(L, 2); + lua_setmetatable(L, 1); + return 1; /* return 1st argument */ +} + + +static int db_getuservalue (lua_State *L) { + if (lua_type(L, 1) != LUA_TUSERDATA) + lua_pushnil(L); + else + lua_getuservalue(L, 1); + return 1; +} + + +static int db_setuservalue (lua_State *L) { + if (lua_type(L, 1) == LUA_TLIGHTUSERDATA) + luaL_argerror(L, 1, "full userdata expected, got light userdata"); + luaL_checktype(L, 1, LUA_TUSERDATA); + if (!lua_isnoneornil(L, 2)) + luaL_checktype(L, 2, LUA_TTABLE); + lua_settop(L, 2); + lua_setuservalue(L, 1); + return 1; +} + + +static void settabss (lua_State *L, const char *i, const char *v) { + lua_pushstring(L, v); + lua_setfield(L, -2, i); +} + + +static void settabsi (lua_State *L, const char *i, int v) { + lua_pushinteger(L, v); + lua_setfield(L, -2, i); +} + + +static void settabsb (lua_State *L, const char *i, int v) { + lua_pushboolean(L, v); + lua_setfield(L, -2, i); +} + + +static lua_State *getthread (lua_State *L, int *arg) { + if (lua_isthread(L, 1)) { + *arg = 1; + return lua_tothread(L, 1); + } + else { + *arg = 0; + return L; + } +} + + +static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) { + if (L == L1) { + lua_pushvalue(L, -2); + lua_remove(L, -3); + } + else + lua_xmove(L1, L, 1); + lua_setfield(L, -2, fname); +} + + +static int db_getinfo (lua_State *L) { + lua_Debug ar; + int arg; + lua_State *L1 = getthread(L, &arg); + const char *options = luaL_optstring(L, arg+2, "flnStu"); + checkstack(L, L1, 3); + if (lua_isnumber(L, arg+1)) { + if (!lua_getstack(L1, (int)lua_tointeger(L, arg+1), &ar)) { + lua_pushnil(L); /* level out of range */ + return 1; + } + } + else if (lua_isfunction(L, arg+1)) { + lua_pushfstring(L, ">%s", options); + options = lua_tostring(L, -1); + lua_pushvalue(L, arg+1); + lua_xmove(L, L1, 1); + } + else + return luaL_argerror(L, arg+1, "function or level expected"); + if (!lua_getinfo(L1, options, &ar)) + return luaL_argerror(L, arg+2, "invalid option"); + lua_createtable(L, 0, 2); + if (strchr(options, 'S')) { + settabss(L, "source", ar.source); + settabss(L, "short_src", ar.short_src); + settabsi(L, "linedefined", ar.linedefined); + settabsi(L, "lastlinedefined", ar.lastlinedefined); + settabss(L, "what", ar.what); + } + if (strchr(options, 'l')) + settabsi(L, "currentline", ar.currentline); + if (strchr(options, 'u')) { + settabsi(L, "nups", ar.nups); + settabsi(L, "nparams", ar.nparams); + settabsb(L, "isvararg", ar.isvararg); + } + if (strchr(options, 'n')) { + settabss(L, "name", ar.name); + settabss(L, "namewhat", ar.namewhat); + } + if (strchr(options, 't')) + settabsb(L, "istailcall", ar.istailcall); + if (strchr(options, 'L')) + treatstackoption(L, L1, "activelines"); + if (strchr(options, 'f')) + treatstackoption(L, L1, "func"); + return 1; /* return table */ +} + + +static int db_getlocal (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + lua_Debug ar; + const char *name; + int nvar = luaL_checkint(L, arg+2); /* local-variable index */ + if (lua_isfunction(L, arg + 1)) { /* function argument? */ + lua_pushvalue(L, arg + 1); /* push function */ + lua_pushstring(L, lua_getlocal(L, NULL, nvar)); /* push local name */ + return 1; + } + else { /* stack-level argument */ + if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ + return luaL_argerror(L, arg+1, "level out of range"); + checkstack(L, L1, 1); + name = lua_getlocal(L1, &ar, nvar); + if (name) { + lua_xmove(L1, L, 1); /* push local value */ + lua_pushstring(L, name); /* push name */ + lua_pushvalue(L, -2); /* re-order */ + return 2; + } + else { + lua_pushnil(L); /* no name (nor value) */ + return 1; + } + } +} + + +static int db_setlocal (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + lua_Debug ar; + if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ + return luaL_argerror(L, arg+1, "level out of range"); + luaL_checkany(L, arg+3); + lua_settop(L, arg+3); + checkstack(L, L1, 1); + lua_xmove(L, L1, 1); + lua_pushstring(L, lua_setlocal(L1, &ar, luaL_checkint(L, arg+2))); + return 1; +} + + +static int auxupvalue (lua_State *L, int get) { + const char *name; + int n = luaL_checkint(L, 2); + luaL_checktype(L, 1, LUA_TFUNCTION); + name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n); + if (name == NULL) return 0; + lua_pushstring(L, name); + lua_insert(L, -(get+1)); + return get + 1; +} + + +static int db_getupvalue (lua_State *L) { + return auxupvalue(L, 1); +} + + +static int db_setupvalue (lua_State *L) { + luaL_checkany(L, 3); + return auxupvalue(L, 0); +} + + +static int checkupval (lua_State *L, int argf, int argnup) { + lua_Debug ar; + int nup = luaL_checkint(L, argnup); + luaL_checktype(L, argf, LUA_TFUNCTION); + lua_pushvalue(L, argf); + lua_getinfo(L, ">u", &ar); + luaL_argcheck(L, 1 <= nup && nup <= ar.nups, argnup, "invalid upvalue index"); + return nup; +} + + +static int db_upvalueid (lua_State *L) { + int n = checkupval(L, 1, 2); + lua_pushlightuserdata(L, lua_upvalueid(L, 1, n)); + return 1; +} + + +static int db_upvaluejoin (lua_State *L) { + int n1 = checkupval(L, 1, 2); + int n2 = checkupval(L, 3, 4); + luaL_argcheck(L, !lua_iscfunction(L, 1), 1, "Lua function expected"); + luaL_argcheck(L, !lua_iscfunction(L, 3), 3, "Lua function expected"); + lua_upvaluejoin(L, 1, n1, 3, n2); + return 0; +} + + +#define gethooktable(L) luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY) + + +static void hookf (lua_State *L, lua_Debug *ar) { + static const char *const hooknames[] = + {"call", "return", "line", "count", "tail call"}; + gethooktable(L); + lua_pushthread(L); + lua_rawget(L, -2); + if (lua_isfunction(L, -1)) { + lua_pushstring(L, hooknames[(int)ar->event]); + if (ar->currentline >= 0) + lua_pushinteger(L, ar->currentline); + else lua_pushnil(L); + lua_assert(lua_getinfo(L, "lS", ar)); + lua_call(L, 2, 0); + } +} + + +static int makemask (const char *smask, int count) { + int mask = 0; + if (strchr(smask, 'c')) mask |= LUA_MASKCALL; + if (strchr(smask, 'r')) mask |= LUA_MASKRET; + if (strchr(smask, 'l')) mask |= LUA_MASKLINE; + if (count > 0) mask |= LUA_MASKCOUNT; + return mask; +} + + +static char *unmakemask (int mask, char *smask) { + int i = 0; + if (mask & LUA_MASKCALL) smask[i++] = 'c'; + if (mask & LUA_MASKRET) smask[i++] = 'r'; + if (mask & LUA_MASKLINE) smask[i++] = 'l'; + smask[i] = '\0'; + return smask; +} + + +static int db_sethook (lua_State *L) { + int arg, mask, count; + lua_Hook func; + lua_State *L1 = getthread(L, &arg); + if (lua_isnoneornil(L, arg+1)) { + lua_settop(L, arg+1); + func = NULL; mask = 0; count = 0; /* turn off hooks */ + } + else { + const char *smask = luaL_checkstring(L, arg+2); + luaL_checktype(L, arg+1, LUA_TFUNCTION); + count = luaL_optint(L, arg+3, 0); + func = hookf; mask = makemask(smask, count); + } + if (gethooktable(L) == 0) { /* creating hook table? */ + lua_pushstring(L, "k"); + lua_setfield(L, -2, "__mode"); /** hooktable.__mode = "k" */ + lua_pushvalue(L, -1); + lua_setmetatable(L, -2); /* setmetatable(hooktable) = hooktable */ + } + checkstack(L, L1, 1); + lua_pushthread(L1); lua_xmove(L1, L, 1); + lua_pushvalue(L, arg+1); + lua_rawset(L, -3); /* set new hook */ + lua_sethook(L1, func, mask, count); /* set hooks */ + return 0; +} + + +static int db_gethook (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + char buff[5]; + int mask = lua_gethookmask(L1); + lua_Hook hook = lua_gethook(L1); + if (hook != NULL && hook != hookf) /* external hook? */ + lua_pushliteral(L, "external hook"); + else { + gethooktable(L); + checkstack(L, L1, 1); + lua_pushthread(L1); lua_xmove(L1, L, 1); + lua_rawget(L, -2); /* get hook */ + lua_remove(L, -2); /* remove hook table */ + } + lua_pushstring(L, unmakemask(mask, buff)); + lua_pushinteger(L, lua_gethookcount(L1)); + return 3; +} + + +static int db_debug (lua_State *L) { + for (;;) { + char buffer[250]; + luai_writestringerror("%s", "lua_debug> "); + if (fgets(buffer, sizeof(buffer), stdin) == 0 || + strcmp(buffer, "cont\n") == 0) + return 0; + if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") || + lua_pcall(L, 0, 0, 0)) + luai_writestringerror("%s\n", lua_tostring(L, -1)); + lua_settop(L, 0); /* remove eventual returns */ + } +} + + +static int db_traceback (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + const char *msg = lua_tostring(L, arg + 1); + if (msg == NULL && !lua_isnoneornil(L, arg + 1)) /* non-string 'msg'? */ + lua_pushvalue(L, arg + 1); /* return it untouched */ + else { + int level = luaL_optint(L, arg + 2, (L == L1) ? 1 : 0); + luaL_traceback(L, L1, msg, level); + } + return 1; +} + + +static const luaL_Reg dblib[] = { + {"debug", db_debug}, + {"getuservalue", db_getuservalue}, + {"gethook", db_gethook}, + {"getinfo", db_getinfo}, + {"getlocal", db_getlocal}, + {"getregistry", db_getregistry}, + {"getmetatable", db_getmetatable}, + {"getupvalue", db_getupvalue}, + {"upvaluejoin", db_upvaluejoin}, + {"upvalueid", db_upvalueid}, + {"setuservalue", db_setuservalue}, + {"sethook", db_sethook}, + {"setlocal", db_setlocal}, + {"setmetatable", db_setmetatable}, + {"setupvalue", db_setupvalue}, + {"traceback", db_traceback}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_debug (lua_State *L) { + luaL_newlib(L, dblib); + return 1; +} + diff --git a/luprex/ext/eris-master/src/ldebug.c b/luprex/ext/eris-master/src/ldebug.c new file mode 100644 index 00000000..651d710b --- /dev/null +++ b/luprex/ext/eris-master/src/ldebug.c @@ -0,0 +1,610 @@ +/* +** $Id: ldebug.c,v 2.90.1.4 2015/02/19 17:05:13 roberto Exp $ +** Debug Interface +** See Copyright Notice in lua.h +*/ + + +#include +#include +#include + + +#define ldebug_c +#define LUA_CORE + +#include "lua.h" + +#include "lapi.h" +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + + +#define noLuaClosure(f) ((f) == NULL || (f)->c.tt == LUA_TCCL) + + +static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name); + + +static int currentpc (CallInfo *ci) { + lua_assert(isLua(ci)); + return pcRel(ci->u.l.savedpc, ci_func(ci)->p); +} + + +static int currentline (CallInfo *ci) { + return getfuncline(ci_func(ci)->p, currentpc(ci)); +} + + +static void swapextra (lua_State *L) { + if (L->status == LUA_YIELD) { + CallInfo *ci = L->ci; /* get function that yielded */ + StkId temp = ci->func; /* exchange its 'func' and 'extra' values */ + ci->func = restorestack(L, ci->extra); + ci->extra = savestack(L, temp); + } +} + + +/* +** this function can be called asynchronous (e.g. during a signal) +*/ +LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { + if (func == NULL || mask == 0) { /* turn off hooks? */ + mask = 0; + func = NULL; + } + if (isLua(L->ci)) + L->oldpc = L->ci->u.l.savedpc; + L->hook = func; + L->basehookcount = count; + resethookcount(L); + L->hookmask = cast_byte(mask); + return 1; +} + + +LUA_API lua_Hook lua_gethook (lua_State *L) { + return L->hook; +} + + +LUA_API int lua_gethookmask (lua_State *L) { + return L->hookmask; +} + + +LUA_API int lua_gethookcount (lua_State *L) { + return L->basehookcount; +} + + +LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) { + int status; + CallInfo *ci; + if (level < 0) return 0; /* invalid (negative) level */ + lua_lock(L); + for (ci = L->ci; level > 0 && ci != &L->base_ci; ci = ci->previous) + level--; + if (level == 0 && ci != &L->base_ci) { /* level found? */ + status = 1; + ar->i_ci = ci; + } + else status = 0; /* no such level */ + lua_unlock(L); + return status; +} + + +static const char *upvalname (Proto *p, int uv) { + TString *s = check_exp(uv < p->sizeupvalues, p->upvalues[uv].name); + if (s == NULL) return "?"; + else return getstr(s); +} + + +static const char *findvararg (CallInfo *ci, int n, StkId *pos) { + int nparams = clLvalue(ci->func)->p->numparams; + if (n >= ci->u.l.base - ci->func - nparams) + return NULL; /* no such vararg */ + else { + *pos = ci->func + nparams + n; + return "(*vararg)"; /* generic name for any vararg */ + } +} + + +static const char *findlocal (lua_State *L, CallInfo *ci, int n, + StkId *pos) { + const char *name = NULL; + StkId base; + if (isLua(ci)) { + if (n < 0) /* access to vararg values? */ + return findvararg(ci, -n, pos); + else { + base = ci->u.l.base; + name = luaF_getlocalname(ci_func(ci)->p, n, currentpc(ci)); + } + } + else + base = ci->func + 1; + if (name == NULL) { /* no 'standard' name? */ + StkId limit = (ci == L->ci) ? L->top : ci->next->func; + if (limit - base >= n && n > 0) /* is 'n' inside 'ci' stack? */ + name = "(*temporary)"; /* generic name for any valid slot */ + else + return NULL; /* no name */ + } + *pos = base + (n - 1); + return name; +} + + +LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { + const char *name; + lua_lock(L); + swapextra(L); + if (ar == NULL) { /* information about non-active function? */ + if (!isLfunction(L->top - 1)) /* not a Lua function? */ + name = NULL; + else /* consider live variables at function start (parameters) */ + name = luaF_getlocalname(clLvalue(L->top - 1)->p, n, 0); + } + else { /* active function; get information through 'ar' */ + StkId pos = 0; /* to avoid warnings */ + name = findlocal(L, ar->i_ci, n, &pos); + if (name) { + setobj2s(L, L->top, pos); + api_incr_top(L); + } + } + swapextra(L); + lua_unlock(L); + return name; +} + + +LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { + StkId pos = 0; /* to avoid warnings */ + const char *name; + lua_lock(L); + swapextra(L); + name = findlocal(L, ar->i_ci, n, &pos); + if (name) + setobjs2s(L, pos, L->top - 1); + L->top--; /* pop value */ + swapextra(L); + lua_unlock(L); + return name; +} + + +static void funcinfo (lua_Debug *ar, Closure *cl) { + if (noLuaClosure(cl)) { + ar->source = "=[C]"; + ar->linedefined = -1; + ar->lastlinedefined = -1; + ar->what = "C"; + } + else { + Proto *p = cl->l.p; + ar->source = p->source ? getstr(p->source) : "=?"; + ar->linedefined = p->linedefined; + ar->lastlinedefined = p->lastlinedefined; + ar->what = (ar->linedefined == 0) ? "main" : "Lua"; + } + luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE); +} + + +static void collectvalidlines (lua_State *L, Closure *f) { + if (noLuaClosure(f)) { + setnilvalue(L->top); + api_incr_top(L); + } + else { + int i; + TValue v; + int *lineinfo = f->l.p->lineinfo; + Table *t = luaH_new(L); /* new table to store active lines */ + sethvalue(L, L->top, t); /* push it on stack */ + api_incr_top(L); + setbvalue(&v, 1); /* boolean 'true' to be the value of all indices */ + for (i = 0; i < f->l.p->sizelineinfo; i++) /* for all lines with code */ + luaH_setint(L, t, lineinfo[i], &v); /* table[line] = true */ + } +} + + +static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, + Closure *f, CallInfo *ci) { + int status = 1; + for (; *what; what++) { + switch (*what) { + case 'S': { + funcinfo(ar, f); + break; + } + case 'l': { + ar->currentline = (ci && isLua(ci)) ? currentline(ci) : -1; + break; + } + case 'u': { + ar->nups = (f == NULL) ? 0 : f->c.nupvalues; + if (noLuaClosure(f)) { + ar->isvararg = 1; + ar->nparams = 0; + } + else { + ar->isvararg = f->l.p->is_vararg; + ar->nparams = f->l.p->numparams; + } + break; + } + case 't': { + ar->istailcall = (ci) ? ci->callstatus & CIST_TAIL : 0; + break; + } + case 'n': { + /* calling function is a known Lua function? */ + if (ci && !(ci->callstatus & CIST_TAIL) && isLua(ci->previous)) + ar->namewhat = getfuncname(L, ci->previous, &ar->name); + else + ar->namewhat = NULL; + if (ar->namewhat == NULL) { + ar->namewhat = ""; /* not found */ + ar->name = NULL; + } + break; + } + case 'L': + case 'f': /* handled by lua_getinfo */ + break; + default: status = 0; /* invalid option */ + } + } + return status; +} + + +LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { + int status; + Closure *cl; + CallInfo *ci; + StkId func; + lua_lock(L); + swapextra(L); + if (*what == '>') { + ci = NULL; + func = L->top - 1; + api_check(L, ttisfunction(func), "function expected"); + what++; /* skip the '>' */ + L->top--; /* pop function */ + } + else { + ci = ar->i_ci; + func = ci->func; + lua_assert(ttisfunction(ci->func)); + } + cl = ttisclosure(func) ? clvalue(func) : NULL; + status = auxgetinfo(L, what, ar, cl, ci); + if (strchr(what, 'f')) { + setobjs2s(L, L->top, func); + api_incr_top(L); + } + swapextra(L); + if (strchr(what, 'L')) + collectvalidlines(L, cl); + lua_unlock(L); + return status; +} + + +/* +** {====================================================== +** Symbolic Execution +** ======================================================= +*/ + +static const char *getobjname (Proto *p, int lastpc, int reg, + const char **name); + + +/* +** find a "name" for the RK value 'c' +*/ +static void kname (Proto *p, int pc, int c, const char **name) { + if (ISK(c)) { /* is 'c' a constant? */ + TValue *kvalue = &p->k[INDEXK(c)]; + if (ttisstring(kvalue)) { /* literal constant? */ + *name = svalue(kvalue); /* it is its own name */ + return; + } + /* else no reasonable name found */ + } + else { /* 'c' is a register */ + const char *what = getobjname(p, pc, c, name); /* search for 'c' */ + if (what && *what == 'c') { /* found a constant name? */ + return; /* 'name' already filled */ + } + /* else no reasonable name found */ + } + *name = "?"; /* no reasonable name found */ +} + + +static int filterpc (int pc, int jmptarget) { + if (pc < jmptarget) /* is code conditional (inside a jump)? */ + return -1; /* cannot know who sets that register */ + else return pc; /* current position sets that register */ +} + + +/* +** try to find last instruction before 'lastpc' that modified register 'reg' +*/ +static int findsetreg (Proto *p, int lastpc, int reg) { + int pc; + int setreg = -1; /* keep last instruction that changed 'reg' */ + int jmptarget = 0; /* any code before this address is conditional */ + for (pc = 0; pc < lastpc; pc++) { + Instruction i = p->code[pc]; + OpCode op = GET_OPCODE(i); + int a = GETARG_A(i); + switch (op) { + case OP_LOADNIL: { + int b = GETARG_B(i); + if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */ + setreg = filterpc(pc, jmptarget); + break; + } + case OP_TFORCALL: { + if (reg >= a + 2) /* affect all regs above its base */ + setreg = filterpc(pc, jmptarget); + break; + } + case OP_CALL: + case OP_TAILCALL: { + if (reg >= a) /* affect all registers above base */ + setreg = filterpc(pc, jmptarget); + break; + } + case OP_JMP: { + int b = GETARG_sBx(i); + int dest = pc + 1 + b; + /* jump is forward and do not skip `lastpc'? */ + if (pc < dest && dest <= lastpc) { + if (dest > jmptarget) + jmptarget = dest; /* update 'jmptarget' */ + } + break; + } + case OP_TEST: { + if (reg == a) /* jumped code can change 'a' */ + setreg = filterpc(pc, jmptarget); + break; + } + default: + if (testAMode(op) && reg == a) /* any instruction that set A */ + setreg = filterpc(pc, jmptarget); + break; + } + } + return setreg; +} + + +static const char *getobjname (Proto *p, int lastpc, int reg, + const char **name) { + int pc; + *name = luaF_getlocalname(p, reg + 1, lastpc); + if (*name) /* is a local? */ + return "local"; + /* else try symbolic execution */ + pc = findsetreg(p, lastpc, reg); + if (pc != -1) { /* could find instruction? */ + Instruction i = p->code[pc]; + OpCode op = GET_OPCODE(i); + switch (op) { + case OP_MOVE: { + int b = GETARG_B(i); /* move from 'b' to 'a' */ + if (b < GETARG_A(i)) + return getobjname(p, pc, b, name); /* get name for 'b' */ + break; + } + case OP_GETTABUP: + case OP_GETTABLE: { + int k = GETARG_C(i); /* key index */ + int t = GETARG_B(i); /* table index */ + const char *vn = (op == OP_GETTABLE) /* name of indexed variable */ + ? luaF_getlocalname(p, t + 1, pc) + : upvalname(p, t); + kname(p, pc, k, name); + return (vn && strcmp(vn, LUA_ENV) == 0) ? "global" : "field"; + } + case OP_GETUPVAL: { + *name = upvalname(p, GETARG_B(i)); + return "upvalue"; + } + case OP_LOADK: + case OP_LOADKX: { + int b = (op == OP_LOADK) ? GETARG_Bx(i) + : GETARG_Ax(p->code[pc + 1]); + if (ttisstring(&p->k[b])) { + *name = svalue(&p->k[b]); + return "constant"; + } + break; + } + case OP_SELF: { + int k = GETARG_C(i); /* key index */ + kname(p, pc, k, name); + return "method"; + } + default: break; /* go through to return NULL */ + } + } + return NULL; /* could not find reasonable name */ +} + + +static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { + TMS tm; + Proto *p = ci_func(ci)->p; /* calling function */ + int pc = currentpc(ci); /* calling instruction index */ + Instruction i = p->code[pc]; /* calling instruction */ + switch (GET_OPCODE(i)) { + case OP_CALL: + case OP_TAILCALL: /* get function name */ + return getobjname(p, pc, GETARG_A(i), name); + case OP_TFORCALL: { /* for iterator */ + *name = "for iterator"; + return "for iterator"; + } + /* all other instructions can call only through metamethods */ + case OP_SELF: + case OP_GETTABUP: + case OP_GETTABLE: tm = TM_INDEX; break; + case OP_SETTABUP: + case OP_SETTABLE: tm = TM_NEWINDEX; break; + case OP_EQ: tm = TM_EQ; break; + case OP_ADD: tm = TM_ADD; break; + case OP_SUB: tm = TM_SUB; break; + case OP_MUL: tm = TM_MUL; break; + case OP_DIV: tm = TM_DIV; break; + case OP_MOD: tm = TM_MOD; break; + case OP_POW: tm = TM_POW; break; + case OP_UNM: tm = TM_UNM; break; + case OP_LEN: tm = TM_LEN; break; + case OP_LT: tm = TM_LT; break; + case OP_LE: tm = TM_LE; break; + case OP_CONCAT: tm = TM_CONCAT; break; + default: + return NULL; /* else no useful name can be found */ + } + *name = getstr(G(L)->tmname[tm]); + return "metamethod"; +} + +/* }====================================================== */ + + + +/* +** only ANSI way to check whether a pointer points to an array +** (used only for error messages, so efficiency is not a big concern) +*/ +static int isinstack (CallInfo *ci, const TValue *o) { + StkId p; + for (p = ci->u.l.base; p < ci->top; p++) + if (o == p) return 1; + return 0; +} + + +static const char *getupvalname (CallInfo *ci, const TValue *o, + const char **name) { + LClosure *c = ci_func(ci); + int i; + for (i = 0; i < c->nupvalues; i++) { + if (c->upvals[i]->v == o) { + *name = upvalname(c->p, i); + return "upvalue"; + } + } + return NULL; +} + + +l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { + CallInfo *ci = L->ci; + const char *name = NULL; + const char *t = objtypename(o); + const char *kind = NULL; + if (isLua(ci)) { + kind = getupvalname(ci, o, &name); /* check whether 'o' is an upvalue */ + if (!kind && isinstack(ci, o)) /* no? try a register */ + kind = getobjname(ci_func(ci)->p, currentpc(ci), + cast_int(o - ci->u.l.base), &name); + } + if (kind) + luaG_runerror(L, "attempt to %s %s " LUA_QS " (a %s value)", + op, kind, name, t); + else + luaG_runerror(L, "attempt to %s a %s value", op, t); +} + + +l_noret luaG_concaterror (lua_State *L, StkId p1, StkId p2) { + if (ttisstring(p1) || ttisnumber(p1)) p1 = p2; + lua_assert(!ttisstring(p1) && !ttisnumber(p1)); + luaG_typeerror(L, p1, "concatenate"); +} + + +l_noret luaG_aritherror (lua_State *L, const TValue *p1, const TValue *p2) { + TValue temp; + if (luaV_tonumber(p1, &temp) == NULL) + p2 = p1; /* first operand is wrong */ + luaG_typeerror(L, p2, "perform arithmetic on"); +} + + +l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { + const char *t1 = objtypename(p1); + const char *t2 = objtypename(p2); + if (t1 == t2) + luaG_runerror(L, "attempt to compare two %s values", t1); + else + luaG_runerror(L, "attempt to compare %s with %s", t1, t2); +} + + +// static void addinfo (lua_State *L, const char *msg) { +// CallInfo *ci = L->ci; +// if (isLua(ci)) { /* is Lua code? */ +// char buff[LUA_IDSIZE]; /* add file:line information */ +// int line = currentline(ci); +// TString *src = ci_func(ci)->p->source; +// if (src) +// luaO_chunkid(buff, getstr(src), LUA_IDSIZE); +// else { /* no source available; use "?" instead */ +// buff[0] = '?'; buff[1] = '\0'; +// } +// luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); +// } +// } + +l_noret luaG_errormsg (lua_State *L) { + if (L->errfunc != 0) { /* is there an error handling function? */ + StkId errfunc = restorestack(L, L->errfunc); + if (!ttisfunction(errfunc)) luaD_throw(L, LUA_ERRERR); + setobjs2s(L, L->top, L->top - 1); /* move argument */ + setobjs2s(L, L->top - 1, errfunc); /* push function */ + L->top++; + luaD_call(L, L->top - 2, 1, 0); /* call it */ + } + luaD_throw(L, LUA_ERRRUN); +} + + +l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { + va_list argp; + va_start(argp, fmt); + luaO_pushvfstring(L, fmt, argp); + // addinfo(L, luaO_pushvfstring(L, fmt, argp)); + va_end(argp); + luaG_errormsg(L); +} + diff --git a/luprex/ext/eris-master/src/ldebug.h b/luprex/ext/eris-master/src/ldebug.h new file mode 100644 index 00000000..6445c763 --- /dev/null +++ b/luprex/ext/eris-master/src/ldebug.h @@ -0,0 +1,34 @@ +/* +** $Id: ldebug.h,v 2.7.1.1 2013/04/12 18:48:47 roberto Exp $ +** Auxiliary functions from Debug Interface module +** See Copyright Notice in lua.h +*/ + +#ifndef ldebug_h +#define ldebug_h + + +#include "lstate.h" + + +#define pcRel(pc, p) (cast(int, (pc) - (p)->code) - 1) + +#define getfuncline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : 0) + +#define resethookcount(L) (L->hookcount = L->basehookcount) + +/* Active Lua function (given call info) */ +#define ci_func(ci) (clLvalue((ci)->func)) + + +LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o, + const char *opname); +LUAI_FUNC l_noret luaG_concaterror (lua_State *L, StkId p1, StkId p2); +LUAI_FUNC l_noret luaG_aritherror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_runerror (lua_State *L, const char *fmt, ...); +LUAI_FUNC l_noret luaG_errormsg (lua_State *L); + +#endif diff --git a/luprex/ext/eris-master/src/ldo.c b/luprex/ext/eris-master/src/ldo.c new file mode 100644 index 00000000..7cd523a4 --- /dev/null +++ b/luprex/ext/eris-master/src/ldo.c @@ -0,0 +1,690 @@ +/* +** $Id: ldo.c,v 2.108.1.3 2013/11/08 18:22:50 roberto Exp $ +** Stack and Call structure of Lua +** See Copyright Notice in lua.h +*/ + + +#include +#include +#include + +#define ldo_c +#define LUA_CORE + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lundump.h" +#include "lvm.h" +#include "lzio.h" + + + + +/* +** {====================================================== +** Error-recovery functions +** ======================================================= +*/ + +/* +** LUAI_THROW/LUAI_TRY define how Lua does exception handling. By +** default, Lua handles errors with exceptions when compiling as +** C++ code, with _longjmp/_setjmp when asked to use them, and with +** longjmp/setjmp otherwise. +*/ +#if !defined(LUAI_THROW) + +#if defined(__cplusplus) && !defined(LUA_USE_LONGJMP) +/* C++ exceptions */ +class LuaException {}; +#define LUAI_THROW(L,c) throw(LuaException()) +// Do not call the lua interpreter inside a try-catch block. If you do, +// the intepreter state could be corrupted. +#define LUAI_TRY(L,c,a) \ +try { a } catch(LuaException e) { if ((c)->status == 0) (c)->status = -1; } +#define luai_jmpbuf int /* dummy variable */ + +#elif defined(LUA_USE_ULONGJMP) +/* in Unix, try _longjmp/_setjmp (more efficient) */ +#define LUAI_THROW(L,c) _longjmp((c)->b, 1) +#define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a } +#define luai_jmpbuf jmp_buf + +#else +/* default handling with long jumps */ +#define LUAI_THROW(L,c) longjmp((c)->b, 1) +#define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } +#define luai_jmpbuf jmp_buf + +#endif + +#endif + + + +/* chain list of long jump buffers */ +struct lua_longjmp { + struct lua_longjmp *previous; + luai_jmpbuf b; + volatile int status; /* error code */ +}; + + +static void seterrorobj (lua_State *L, int errcode, StkId oldtop) { + switch (errcode) { + case LUA_ERRMEM: { /* memory error? */ + setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ + break; + } + case LUA_ERRERR: { + setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); + break; + } + default: { + setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ + break; + } + } + L->top = oldtop + 1; +} + +LUA_API int lua_isthrowing (lua_State *L) { + return ((L->errorJmp != NULL) && (L->errorJmp->status != LUA_OK)); +} + +l_noret luaD_throw (lua_State *L, int errcode) { + if (L->errorJmp) { /* thread has an error handler? */ + L->errorJmp->status = errcode; /* set status */ + LUAI_THROW(L, L->errorJmp); /* jump to it */ + } + else { /* thread has no error handler */ + L->status = cast_byte(errcode); /* mark it as dead */ + if (G(L)->mainthread->errorJmp) { /* main thread has a handler? */ + setobjs2s(L, G(L)->mainthread->top++, L->top - 1); /* copy error obj. */ + luaD_throw(G(L)->mainthread, errcode); /* re-throw in main thread */ + } + else { /* no handler at all; abort */ + if (G(L)->panic) { /* panic function? */ + lua_unlock(L); + G(L)->panic(L); /* call it (last chance to jump out) */ + } + abort(); + } + } +} + + +int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { + unsigned short oldnCcalls = L->nCcalls; + struct lua_longjmp lj; + lj.status = LUA_OK; + lj.previous = L->errorJmp; /* chain new error handler */ + L->errorJmp = &lj; + LUAI_TRY(L, &lj, + (*f)(L, ud); + ); + L->errorJmp = lj.previous; /* restore old error handler */ + L->nCcalls = oldnCcalls; + return lj.status; +} + +/* }====================================================== */ + + +static void correctstack (lua_State *L, TValue *oldstack) { + CallInfo *ci; + GCObject *up; + L->top = (L->top - oldstack) + L->stack; + for (up = L->openupval; up != NULL; up = up->gch.next) + gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack; + for (ci = L->ci; ci != NULL; ci = ci->previous) { + ci->top = (ci->top - oldstack) + L->stack; + ci->func = (ci->func - oldstack) + L->stack; + if (isLua(ci)) + ci->u.l.base = (ci->u.l.base - oldstack) + L->stack; + } +} + + +/* some space for error handling */ +#define ERRORSTACKSIZE (LUAI_MAXSTACK + 200) + + +void luaD_reallocstack (lua_State *L, int newsize) { + TValue *oldstack = L->stack; + int lim = L->stacksize; + lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE); + lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK); + luaM_reallocvector(L, L->stack, L->stacksize, newsize, TValue); + for (; lim < newsize; lim++) + setnilvalue(L->stack + lim); /* erase new segment */ + L->stacksize = newsize; + L->stack_last = L->stack + newsize - EXTRA_STACK; + correctstack(L, oldstack); +} + + +void luaD_growstack (lua_State *L, int n) { + int size = L->stacksize; + if (size > LUAI_MAXSTACK) /* error after extra size? */ + luaD_throw(L, LUA_ERRERR); + else { + int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK; + int newsize = 2 * size; + if (newsize > LUAI_MAXSTACK) newsize = LUAI_MAXSTACK; + if (newsize < needed) newsize = needed; + if (newsize > LUAI_MAXSTACK) { /* stack overflow? */ + luaD_reallocstack(L, ERRORSTACKSIZE); + luaG_runerror(L, "stack overflow"); + } + else + luaD_reallocstack(L, newsize); + } +} + + +static int stackinuse (lua_State *L) { + CallInfo *ci; + StkId lim = L->top; + for (ci = L->ci; ci != NULL; ci = ci->previous) { + lua_assert(ci->top <= L->stack_last); + if (lim < ci->top) lim = ci->top; + } + return cast_int(lim - L->stack) + 1; /* part of stack in use */ +} + + +void luaD_shrinkstack (lua_State *L) { + int inuse = stackinuse(L); + int goodsize = inuse + (inuse / 8) + 2*EXTRA_STACK; + if (goodsize > LUAI_MAXSTACK) goodsize = LUAI_MAXSTACK; + if (inuse > LUAI_MAXSTACK || /* handling stack overflow? */ + goodsize >= L->stacksize) /* would grow instead of shrink? */ + condmovestack(L); /* don't change stack (change only for debugging) */ + else + luaD_reallocstack(L, goodsize); /* shrink it */ +} + + +void luaD_hook (lua_State *L, int event, int line) { + lua_Hook hook = L->hook; + if (hook && L->allowhook) { + CallInfo *ci = L->ci; + ptrdiff_t top = savestack(L, L->top); + ptrdiff_t ci_top = savestack(L, ci->top); + lua_Debug ar; + ar.event = event; + ar.currentline = line; + ar.i_ci = ci; + luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ + ci->top = L->top + LUA_MINSTACK; + lua_assert(ci->top <= L->stack_last); + L->allowhook = 0; /* cannot call hooks inside a hook */ + ci->callstatus |= CIST_HOOKED; + lua_unlock(L); + (*hook)(L, &ar); + lua_lock(L); + lua_assert(!L->allowhook); + L->allowhook = 1; + ci->top = restorestack(L, ci_top); + L->top = restorestack(L, top); + ci->callstatus &= ~CIST_HOOKED; + } +} + + +static void callhook (lua_State *L, CallInfo *ci) { + int hook = LUA_HOOKCALL; + ci->u.l.savedpc++; /* hooks assume 'pc' is already incremented */ + if (isLua(ci->previous) && + GET_OPCODE(*(ci->previous->u.l.savedpc - 1)) == OP_TAILCALL) { + ci->callstatus |= CIST_TAIL; + hook = LUA_HOOKTAILCALL; + } + luaD_hook(L, hook, -1); + ci->u.l.savedpc--; /* correct 'pc' */ +} + + +static StkId adjust_varargs (lua_State *L, Proto *p, int actual) { + int i; + int nfixargs = p->numparams; + StkId base, fixed; + lua_assert(actual >= nfixargs); + /* move fixed parameters to final position */ + luaD_checkstack(L, p->maxstacksize); /* check again for new 'base' */ + fixed = L->top - actual; /* first fixed argument */ + base = L->top; /* final position of first argument */ + for (i=0; itop++, fixed + i); + setnilvalue(fixed + i); + } + return base; +} + + +static StkId tryfuncTM (lua_State *L, StkId func) { + const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL); + StkId p; + ptrdiff_t funcr = savestack(L, func); + if (!ttisfunction(tm)) + luaG_typeerror(L, func, "call"); + /* Open a hole inside the stack at `func' */ + for (p = L->top; p > func; p--) setobjs2s(L, p, p-1); + incr_top(L); + func = restorestack(L, funcr); /* previous call may change stack */ + setobj2s(L, func, tm); /* tag method is the new function to be called */ + return func; +} + + + +#define next_ci(L) (L->ci = (L->ci->next ? L->ci->next : luaE_extendCI(L))) + + +/* +** returns true if function has been executed (C function) +*/ +int luaD_precall (lua_State *L, StkId func, int nresults) { + lua_CFunction f; + CallInfo *ci; + int n; /* number of arguments (Lua) or returns (C) */ + ptrdiff_t funcr = savestack(L, func); + switch (ttype(func)) { + case LUA_TLCF: /* light C function */ + f = fvalue(func); + goto Cfunc; + case LUA_TCCL: { /* C closure */ + f = clCvalue(func)->f; + Cfunc: + luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ + ci = next_ci(L); /* now 'enter' new function */ + ci->nresults = nresults; + ci->func = restorestack(L, funcr); + ci->top = L->top + LUA_MINSTACK; + lua_assert(ci->top <= L->stack_last); + ci->callstatus = 0; + luaC_checkGC(L); /* stack grow uses memory */ + if (L->hookmask & LUA_MASKCALL) + luaD_hook(L, LUA_HOOKCALL, -1); + lua_unlock(L); + n = (*f)(L); /* do the actual call */ + lua_lock(L); + api_checknelems(L, n); + luaD_poscall(L, L->top - n); + return 1; + } + case LUA_TLCL: { /* Lua function: prepare its call */ + StkId base; + Proto *p = clLvalue(func)->p; + n = cast_int(L->top - func) - 1; /* number of real arguments */ + luaD_checkstack(L, p->maxstacksize); + for (; n < p->numparams; n++) + setnilvalue(L->top++); /* complete missing arguments */ + if (!p->is_vararg) { + func = restorestack(L, funcr); + base = func + 1; + } + else { + base = adjust_varargs(L, p, n); + func = restorestack(L, funcr); /* previous call can change stack */ + } + ci = next_ci(L); /* now 'enter' new function */ + ci->nresults = nresults; + ci->func = func; + ci->u.l.base = base; + ci->top = base + p->maxstacksize; + lua_assert(ci->top <= L->stack_last); + ci->u.l.savedpc = p->code; /* starting point */ + ci->callstatus = CIST_LUA; + L->top = ci->top; + luaC_checkGC(L); /* stack grow uses memory */ + if (L->hookmask & LUA_MASKCALL) + callhook(L, ci); + return 0; + } + default: { /* not a function */ + func = tryfuncTM(L, func); /* retry with 'function' tag method */ + return luaD_precall(L, func, nresults); /* now it must be a function */ + } + } +} + + +int luaD_poscall (lua_State *L, StkId firstResult) { + StkId res; + int wanted, i; + CallInfo *ci = L->ci; + if (L->hookmask & (LUA_MASKRET | LUA_MASKLINE)) { + if (L->hookmask & LUA_MASKRET) { + ptrdiff_t fr = savestack(L, firstResult); /* hook may change stack */ + luaD_hook(L, LUA_HOOKRET, -1); + firstResult = restorestack(L, fr); + } + L->oldpc = ci->previous->u.l.savedpc; /* 'oldpc' for caller function */ + } + res = ci->func; /* res == final position of 1st result */ + wanted = ci->nresults; + L->ci = ci = ci->previous; /* back to caller */ + /* move results to correct place */ + for (i = wanted; i != 0 && firstResult < L->top; i--) + setobjs2s(L, res++, firstResult++); + while (i-- > 0) + setnilvalue(res++); + L->top = res; + return (wanted - LUA_MULTRET); /* 0 iff wanted == LUA_MULTRET */ +} + + +/* +** Call a function (C or Lua). The function to be called is at *func. +** The arguments are on the stack, right after the function. +** When returns, all the results are on the stack, starting at the original +** function position. +*/ +void luaD_call (lua_State *L, StkId func, int nResults, int allowyield) { + if (++L->nCcalls >= LUAI_MAXCCALLS) { + if (L->nCcalls == LUAI_MAXCCALLS) + luaG_runerror(L, "C stack overflow"); + else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) + luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ + } + if (!allowyield) L->nny++; + if (!luaD_precall(L, func, nResults)) /* is a Lua function? */ + luaV_execute(L); /* call it */ + if (!allowyield) L->nny--; + L->nCcalls--; +} + + +static void finishCcall (lua_State *L) { + CallInfo *ci = L->ci; + int n; + lua_assert(ci->u.c.k != NULL); /* must have a continuation */ + lua_assert(L->nny == 0); + if (ci->callstatus & CIST_YPCALL) { /* was inside a pcall? */ + ci->callstatus &= ~CIST_YPCALL; /* finish 'lua_pcall' */ + L->errfunc = ci->u.c.old_errfunc; + } + /* finish 'lua_callk'/'lua_pcall' */ + adjustresults(L, ci->nresults); + /* call continuation function */ + if (!(ci->callstatus & CIST_STAT)) /* no call status? */ + ci->u.c.status = LUA_YIELD; /* 'default' status */ + lua_assert(ci->u.c.status != LUA_OK); + ci->callstatus = (ci->callstatus & ~(CIST_YPCALL | CIST_STAT)) | CIST_YIELDED; + lua_unlock(L); + n = (*ci->u.c.k)(L); + lua_lock(L); + api_checknelems(L, n); + /* finish 'luaD_precall' */ + luaD_poscall(L, L->top - n); +} + + +static void unroll (lua_State *L, void *ud) { + UNUSED(ud); + for (;;) { + if (L->ci == &L->base_ci) /* stack is empty? */ + return; /* coroutine finished normally */ + if (!isLua(L->ci)) /* C function? */ + finishCcall(L); + else { /* Lua function */ + luaV_finishOp(L); /* finish interrupted instruction */ + luaV_execute(L); /* execute down to higher C 'boundary' */ + } + } +} + + +/* +** check whether thread has a suspended protected call +*/ +static CallInfo *findpcall (lua_State *L) { + CallInfo *ci; + for (ci = L->ci; ci != NULL; ci = ci->previous) { /* search for a pcall */ + if (ci->callstatus & CIST_YPCALL) + return ci; + } + return NULL; /* no pending pcall */ +} + + +static int recover (lua_State *L, int status) { + StkId oldtop; + CallInfo *ci = findpcall(L); + if (ci == NULL) return 0; /* no recovery point */ + /* "finish" luaD_pcall */ + oldtop = restorestack(L, ci->extra); + luaF_close(L, oldtop); + seterrorobj(L, status, oldtop); + L->ci = ci; + L->allowhook = ci->u.c.old_allowhook; + L->nny = 0; /* should be zero to be yieldable */ + luaD_shrinkstack(L); + L->errfunc = ci->u.c.old_errfunc; + ci->callstatus |= CIST_STAT; /* call has error status */ + ci->u.c.status = status; /* (here it is) */ + return 1; /* continue running the coroutine */ +} + + +/* +** signal an error in the call to 'resume', not in the execution of the +** coroutine itself. (Such errors should not be handled by any coroutine +** error handler and should not kill the coroutine.) +*/ +static l_noret resume_error (lua_State *L, const char *msg, StkId firstArg) { + L->top = firstArg; /* remove args from the stack */ + setsvalue2s(L, L->top, luaS_new(L, msg)); /* push error message */ + api_incr_top(L); + luaD_throw(L, -1); /* jump back to 'lua_resume' */ +} + + +/* +** do the work for 'lua_resume' in protected mode +*/ +static void resume (lua_State *L, void *ud) { + int nCcalls = L->nCcalls; + StkId firstArg = cast(StkId, ud); + CallInfo *ci = L->ci; + if (nCcalls >= LUAI_MAXCCALLS) + resume_error(L, "C stack overflow", firstArg); + if (L->status == LUA_OK) { /* may be starting a coroutine */ + if (ci != &L->base_ci) /* not in base level? */ + resume_error(L, "cannot resume non-suspended coroutine", firstArg); + /* coroutine is in base level; start running it */ + if (!luaD_precall(L, firstArg - 1, LUA_MULTRET)) /* Lua function? */ + luaV_execute(L); /* call it */ + } + else if (L->status != LUA_YIELD) + resume_error(L, "cannot resume dead coroutine", firstArg); + else { /* resuming from previous yield */ + L->status = LUA_OK; + ci->func = restorestack(L, ci->extra); + if (isLua(ci)) /* yielded inside a hook? */ + luaV_execute(L); /* just continue running Lua code */ + else { /* 'common' yield */ + if (ci->u.c.k != NULL) { /* does it have a continuation? */ + int n; + ci->u.c.status = LUA_YIELD; /* 'default' status */ + ci->callstatus |= CIST_YIELDED; + lua_unlock(L); + n = (*ci->u.c.k)(L); /* call continuation */ + lua_lock(L); + api_checknelems(L, n); + firstArg = L->top - n; /* yield results come from continuation */ + } + luaD_poscall(L, firstArg); /* finish 'luaD_precall' */ + } + unroll(L, NULL); + } + lua_assert(nCcalls == L->nCcalls); +} + + +LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) { + int status; + int oldnny = L->nny; /* save 'nny' */ + lua_lock(L); + luai_userstateresume(L, nargs); + L->nCcalls = (from) ? from->nCcalls + 1 : 1; + L->nny = 0; /* allow yields */ + api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); + status = luaD_rawrunprotected(L, resume, L->top - nargs); + if (status == -1) /* error calling 'lua_resume'? */ + status = LUA_ERRRUN; + else { /* yield or regular error */ + while (status != LUA_OK && status != LUA_YIELD) { /* error? */ + if (recover(L, status)) /* recover point? */ + status = luaD_rawrunprotected(L, unroll, NULL); /* run continuation */ + else { /* unrecoverable error */ + L->status = cast_byte(status); /* mark thread as `dead' */ + seterrorobj(L, status, L->top); + L->ci->top = L->top; + break; + } + } + lua_assert(status == L->status); + } + L->nny = oldnny; /* restore 'nny' */ + L->nCcalls--; + lua_assert(L->nCcalls == ((from) ? from->nCcalls : 0)); + lua_unlock(L); + return status; +} + + +LUA_API int lua_yieldk (lua_State *L, int nresults, int ctx, lua_CFunction k) { + CallInfo *ci = L->ci; + luai_userstateyield(L, nresults); + lua_lock(L); + api_checknelems(L, nresults); + if (L->nny > 0) { + if (L != G(L)->mainthread) + luaG_runerror(L, "attempt to yield across a C-call boundary"); + else + luaG_runerror(L, "attempt to yield from outside a coroutine"); + } + L->status = LUA_YIELD; + ci->extra = savestack(L, ci->func); /* save current 'func' */ + if (isLua(ci)) { /* inside a hook? */ + api_check(L, k == NULL, "hooks cannot continue after yielding"); + } + else { + if ((ci->u.c.k = k) != NULL) /* is there a continuation? */ + ci->u.c.ctx = ctx; /* save context */ + ci->func = L->top - nresults - 1; /* protect stack below results */ + luaD_throw(L, LUA_YIELD); + } + lua_assert(ci->callstatus & CIST_HOOKED); /* must be inside a hook */ + lua_unlock(L); + return 0; /* return to 'luaD_hook' */ +} + +LUA_API int lua_isyieldable (lua_State *L) { + return (L->nny == 0); +} + +int luaD_pcall (lua_State *L, Pfunc func, void *u, + ptrdiff_t old_top, ptrdiff_t ef) { + int status; + CallInfo *old_ci = L->ci; + lu_byte old_allowhooks = L->allowhook; + unsigned short old_nny = L->nny; + ptrdiff_t old_errfunc = L->errfunc; + L->errfunc = ef; + status = luaD_rawrunprotected(L, func, u); + if (status != LUA_OK) { /* an error occurred? */ + StkId oldtop = restorestack(L, old_top); + luaF_close(L, oldtop); /* close possible pending closures */ + seterrorobj(L, status, oldtop); + L->ci = old_ci; + L->allowhook = old_allowhooks; + L->nny = old_nny; + luaD_shrinkstack(L); + } + L->errfunc = old_errfunc; + return status; +} + + + +/* +** Execute a protected parser. +*/ +struct SParser { /* data to `f_parser' */ + ZIO *z; + Mbuffer buff; /* dynamic structure used by the scanner */ + Dyndata dyd; /* dynamic structures used by the parser */ + const char *mode; + const char *name; +}; + + +static void checkmode (lua_State *L, const char *mode, const char *x) { + if (mode && strchr(mode, x[0]) == NULL) { + luaO_pushfstring(L, + "attempt to load a %s chunk (mode is " LUA_QS ")", x, mode); + luaD_throw(L, LUA_ERRSYNTAX); + } +} + + +static void f_parser (lua_State *L, void *ud) { + int i; + Closure *cl; + struct SParser *p = cast(struct SParser *, ud); + int c = zgetc(p->z); /* read first character */ + if (c == LUA_SIGNATURE[0]) { + checkmode(L, p->mode, "binary"); + cl = luaU_undump(L, p->z, &p->buff, p->name); + } + else { + checkmode(L, p->mode, "text"); + cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c); + } + lua_assert(cl->l.nupvalues == cl->l.p->sizeupvalues); + for (i = 0; i < cl->l.nupvalues; i++) { /* initialize upvalues */ + UpVal *up = luaF_newupval(L); + cl->l.upvals[i] = up; + luaC_objbarrier(L, cl, up); + } +} + + +int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, + const char *mode) { + struct SParser p; + int status; + L->nny++; /* cannot yield during parsing */ + p.z = z; p.name = name; p.mode = mode; + p.dyd.actvar.arr = NULL; p.dyd.actvar.size = 0; + p.dyd.gt.arr = NULL; p.dyd.gt.size = 0; + p.dyd.label.arr = NULL; p.dyd.label.size = 0; + luaZ_initbuffer(L, &p.buff); + status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc); + luaZ_freebuffer(L, &p.buff); + luaM_freearray(L, p.dyd.actvar.arr, p.dyd.actvar.size); + luaM_freearray(L, p.dyd.gt.arr, p.dyd.gt.size); + luaM_freearray(L, p.dyd.label.arr, p.dyd.label.size); + L->nny--; + return status; +} + + diff --git a/luprex/ext/eris-master/src/ldo.h b/luprex/ext/eris-master/src/ldo.h new file mode 100644 index 00000000..d3d3082c --- /dev/null +++ b/luprex/ext/eris-master/src/ldo.h @@ -0,0 +1,46 @@ +/* +** $Id: ldo.h,v 2.20.1.1 2013/04/12 18:48:47 roberto Exp $ +** Stack and Call structure of Lua +** See Copyright Notice in lua.h +*/ + +#ifndef ldo_h +#define ldo_h + + +#include "lobject.h" +#include "lstate.h" +#include "lzio.h" + + +#define luaD_checkstack(L,n) if (L->stack_last - L->top <= (n)) \ + luaD_growstack(L, n); else condmovestack(L); + + +#define incr_top(L) {L->top++; luaD_checkstack(L,0);} + +#define savestack(L,p) ((char *)(p) - (char *)L->stack) +#define restorestack(L,n) ((TValue *)((char *)L->stack + (n))) + + +/* type of protected functions, to be ran by `runprotected' */ +typedef void (*Pfunc) (lua_State *L, void *ud); + +LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, + const char *mode); +LUAI_FUNC void luaD_hook (lua_State *L, int event, int line); +LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults); +LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults, + int allowyield); +LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, + ptrdiff_t oldtop, ptrdiff_t ef); +LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult); +LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize); +LUAI_FUNC void luaD_growstack (lua_State *L, int n); +LUAI_FUNC void luaD_shrinkstack (lua_State *L); + +LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode); +LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); + +#endif + diff --git a/luprex/ext/eris-master/src/ldump.c b/luprex/ext/eris-master/src/ldump.c new file mode 100644 index 00000000..61fa2cd8 --- /dev/null +++ b/luprex/ext/eris-master/src/ldump.c @@ -0,0 +1,173 @@ +/* +** $Id: ldump.c,v 2.17.1.1 2013/04/12 18:48:47 roberto Exp $ +** save precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#include + +#define ldump_c +#define LUA_CORE + +#include "lua.h" + +#include "lobject.h" +#include "lstate.h" +#include "lundump.h" + +typedef struct { + lua_State* L; + lua_Writer writer; + void* data; + int strip; + int status; +} DumpState; + +#define DumpMem(b,n,size,D) DumpBlock(b,(n)*(size),D) +#define DumpVar(x,D) DumpMem(&x,1,sizeof(x),D) + +static void DumpBlock(const void* b, size_t size, DumpState* D) +{ + if (D->status==0) + { + lua_unlock(D->L); + D->status=(*D->writer)(D->L,b,size,D->data); + lua_lock(D->L); + } +} + +static void DumpChar(int y, DumpState* D) +{ + char x=(char)y; + DumpVar(x,D); +} + +static void DumpInt(int x, DumpState* D) +{ + DumpVar(x,D); +} + +static void DumpNumber(lua_Number x, DumpState* D) +{ + DumpVar(x,D); +} + +static void DumpVector(const void* b, int n, size_t size, DumpState* D) +{ + DumpInt(n,D); + DumpMem(b,n,size,D); +} + +static void DumpString(const TString* s, DumpState* D) +{ + if (s==NULL) + { + size_t size=0; + DumpVar(size,D); + } + else + { + size_t size=s->tsv.len+1; /* include trailing '\0' */ + DumpVar(size,D); + DumpBlock(getstr(s),size*sizeof(char),D); + } +} + +#define DumpCode(f,D) DumpVector(f->code,f->sizecode,sizeof(Instruction),D) + +static void DumpFunction(const Proto* f, DumpState* D); + +static void DumpConstants(const Proto* f, DumpState* D) +{ + int i,n=f->sizek; + DumpInt(n,D); + for (i=0; ik[i]; + DumpChar(ttypenv(o),D); + switch (ttypenv(o)) + { + case LUA_TNIL: + break; + case LUA_TBOOLEAN: + DumpChar(bvalue(o),D); + break; + case LUA_TNUMBER: + DumpNumber(nvalue(o),D); + break; + case LUA_TSTRING: + DumpString(rawtsvalue(o),D); + break; + default: lua_assert(0); + } + } + n=f->sizep; + DumpInt(n,D); + for (i=0; ip[i],D); +} + +static void DumpUpvalues(const Proto* f, DumpState* D) +{ + int i,n=f->sizeupvalues; + DumpInt(n,D); + for (i=0; iupvalues[i].instack,D); + DumpChar(f->upvalues[i].idx,D); + } +} + +static void DumpDebug(const Proto* f, DumpState* D) +{ + int i,n; + DumpString((D->strip) ? NULL : f->source,D); + n= (D->strip) ? 0 : f->sizelineinfo; + DumpVector(f->lineinfo,n,sizeof(int),D); + n= (D->strip) ? 0 : f->sizelocvars; + DumpInt(n,D); + for (i=0; ilocvars[i].varname,D); + DumpInt(f->locvars[i].startpc,D); + DumpInt(f->locvars[i].endpc,D); + } + n= (D->strip) ? 0 : f->sizeupvalues; + DumpInt(n,D); + for (i=0; iupvalues[i].name,D); +} + +static void DumpFunction(const Proto* f, DumpState* D) +{ + DumpInt(f->linedefined,D); + DumpInt(f->lastlinedefined,D); + DumpChar(f->numparams,D); + DumpChar(f->is_vararg,D); + DumpChar(f->maxstacksize,D); + DumpCode(f,D); + DumpConstants(f,D); + DumpUpvalues(f,D); + DumpDebug(f,D); +} + +static void DumpHeader(DumpState* D) +{ + lu_byte h[LUAC_HEADERSIZE]; + luaU_header(h); + DumpBlock(h,LUAC_HEADERSIZE,D); +} + +/* +** dump Lua function as precompiled chunk +*/ +int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip) +{ + DumpState D; + D.L=L; + D.writer=w; + D.data=data; + D.strip=strip; + D.status=0; + DumpHeader(&D); + DumpFunction(f,&D); + return D.status; +} diff --git a/luprex/ext/eris-master/src/lfunc.c b/luprex/ext/eris-master/src/lfunc.c new file mode 100644 index 00000000..e90e1520 --- /dev/null +++ b/luprex/ext/eris-master/src/lfunc.c @@ -0,0 +1,161 @@ +/* +** $Id: lfunc.c,v 2.30.1.1 2013/04/12 18:48:47 roberto Exp $ +** Auxiliary functions to manipulate prototypes and closures +** See Copyright Notice in lua.h +*/ + + +#include + +#define lfunc_c +#define LUA_CORE + +#include "lua.h" + +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" + + + +Closure *luaF_newCclosure (lua_State *L, int n) { + Closure *c = &luaC_newobj(L, LUA_TCCL, sizeCclosure(n), NULL, 0)->cl; + c->c.nupvalues = cast_byte(n); + return c; +} + + +Closure *luaF_newLclosure (lua_State *L, int n) { + Closure *c = &luaC_newobj(L, LUA_TLCL, sizeLclosure(n), NULL, 0)->cl; + c->l.p = NULL; + c->l.nupvalues = cast_byte(n); + while (n--) c->l.upvals[n] = NULL; + return c; +} + + +UpVal *luaF_newupval (lua_State *L) { + UpVal *uv = &luaC_newobj(L, LUA_TUPVAL, sizeof(UpVal), NULL, 0)->uv; + uv->v = &uv->u.value; + setnilvalue(uv->v); + return uv; +} + + +UpVal *luaF_findupval (lua_State *L, StkId level) { + global_State *g = G(L); + GCObject **pp = &L->openupval; + UpVal *p; + UpVal *uv; + while (*pp != NULL && (p = gco2uv(*pp))->v >= level) { + GCObject *o = obj2gco(p); + lua_assert(p->v != &p->u.value); + lua_assert(!isold(o) || isold(obj2gco(L))); + if (p->v == level) { /* found a corresponding upvalue? */ + if (isdead(g, o)) /* is it dead? */ + changewhite(o); /* resurrect it */ + return p; + } + pp = &p->next; + } + /* not found: create a new one */ + uv = &luaC_newobj(L, LUA_TUPVAL, sizeof(UpVal), pp, 0)->uv; + uv->v = level; /* current value lives in the stack */ + uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */ + uv->u.l.next = g->uvhead.u.l.next; + uv->u.l.next->u.l.prev = uv; + g->uvhead.u.l.next = uv; + lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + return uv; +} + + +static void unlinkupval (UpVal *uv) { + lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + uv->u.l.next->u.l.prev = uv->u.l.prev; /* remove from `uvhead' list */ + uv->u.l.prev->u.l.next = uv->u.l.next; +} + + +void luaF_freeupval (lua_State *L, UpVal *uv) { + if (uv->v != &uv->u.value) /* is it open? */ + unlinkupval(uv); /* remove from open list */ + luaM_free(L, uv); /* free upvalue */ +} + + +void luaF_close (lua_State *L, StkId level) { + UpVal *uv; + global_State *g = G(L); + while (L->openupval != NULL && (uv = gco2uv(L->openupval))->v >= level) { + GCObject *o = obj2gco(uv); + lua_assert(!isblack(o) && uv->v != &uv->u.value); + L->openupval = uv->next; /* remove from `open' list */ + if (isdead(g, o)) + luaF_freeupval(L, uv); /* free upvalue */ + else { + unlinkupval(uv); /* remove upvalue from 'uvhead' list */ + setobj(L, &uv->u.value, uv->v); /* move value to upvalue slot */ + uv->v = &uv->u.value; /* now current value lives here */ + gch(o)->next = g->allgc; /* link upvalue into 'allgc' list */ + g->allgc = o; + luaC_checkupvalcolor(g, uv); + } + } +} + + +Proto *luaF_newproto (lua_State *L) { + Proto *f = &luaC_newobj(L, LUA_TPROTO, sizeof(Proto), NULL, 0)->p; + f->k = NULL; + f->sizek = 0; + f->p = NULL; + f->sizep = 0; + f->code = NULL; + f->cache = NULL; + f->sizecode = 0; + f->lineinfo = NULL; + f->sizelineinfo = 0; + f->upvalues = NULL; + f->sizeupvalues = 0; + f->numparams = 0; + f->is_vararg = 0; + f->maxstacksize = 0; + f->locvars = NULL; + f->sizelocvars = 0; + f->linedefined = 0; + f->lastlinedefined = 0; + f->source = NULL; + return f; +} + + +void luaF_freeproto (lua_State *L, Proto *f) { + luaM_freearray(L, f->code, f->sizecode); + luaM_freearray(L, f->p, f->sizep); + luaM_freearray(L, f->k, f->sizek); + luaM_freearray(L, f->lineinfo, f->sizelineinfo); + luaM_freearray(L, f->locvars, f->sizelocvars); + luaM_freearray(L, f->upvalues, f->sizeupvalues); + luaM_free(L, f); +} + + +/* +** Look for n-th local variable at line `line' in function `func'. +** Returns NULL if not found. +*/ +const char *luaF_getlocalname (const Proto *f, int local_number, int pc) { + int i; + for (i = 0; isizelocvars && f->locvars[i].startpc <= pc; i++) { + if (pc < f->locvars[i].endpc) { /* is variable active? */ + local_number--; + if (local_number == 0) + return getstr(f->locvars[i].varname); + } + } + return NULL; /* not found */ +} + diff --git a/luprex/ext/eris-master/src/lfunc.h b/luprex/ext/eris-master/src/lfunc.h new file mode 100644 index 00000000..ca0d3a3e --- /dev/null +++ b/luprex/ext/eris-master/src/lfunc.h @@ -0,0 +1,33 @@ +/* +** $Id: lfunc.h,v 2.8.1.1 2013/04/12 18:48:47 roberto Exp $ +** Auxiliary functions to manipulate prototypes and closures +** See Copyright Notice in lua.h +*/ + +#ifndef lfunc_h +#define lfunc_h + + +#include "lobject.h" + + +#define sizeCclosure(n) (cast(int, sizeof(CClosure)) + \ + cast(int, sizeof(TValue)*((n)-1))) + +#define sizeLclosure(n) (cast(int, sizeof(LClosure)) + \ + cast(int, sizeof(TValue *)*((n)-1))) + + +LUAI_FUNC Proto *luaF_newproto (lua_State *L); +LUAI_FUNC Closure *luaF_newCclosure (lua_State *L, int nelems); +LUAI_FUNC Closure *luaF_newLclosure (lua_State *L, int nelems); +LUAI_FUNC UpVal *luaF_newupval (lua_State *L); +LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); +LUAI_FUNC void luaF_close (lua_State *L, StkId level); +LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); +LUAI_FUNC void luaF_freeupval (lua_State *L, UpVal *uv); +LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, + int pc); + + +#endif diff --git a/luprex/ext/eris-master/src/lgc.c b/luprex/ext/eris-master/src/lgc.c new file mode 100644 index 00000000..153ff24a --- /dev/null +++ b/luprex/ext/eris-master/src/lgc.c @@ -0,0 +1,1220 @@ +/* +** $Id: lgc.c,v 2.140.1.3 2014/09/01 16:55:08 roberto Exp $ +** Garbage Collector +** See Copyright Notice in lua.h +*/ + +#include + +#define lgc_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + + +/* +** cost of sweeping one element (the size of a small object divided +** by some adjust for the sweep speed) +*/ +#define GCSWEEPCOST ((sizeof(TString) + 4) / 4) + +/* maximum number of elements to sweep in each single step */ +#define GCSWEEPMAX (cast_int((GCSTEPSIZE / GCSWEEPCOST) / 4)) + +/* maximum number of finalizers to call in each GC step */ +#define GCFINALIZENUM 4 + + +/* +** macro to adjust 'stepmul': 'stepmul' is actually used like +** 'stepmul / STEPMULADJ' (value chosen by tests) +*/ +#define STEPMULADJ 200 + + +/* +** macro to adjust 'pause': 'pause' is actually used like +** 'pause / PAUSEADJ' (value chosen by tests) +*/ +#define PAUSEADJ 100 + + +/* +** 'makewhite' erases all color bits plus the old bit and then +** sets only the current white bit +*/ +#define maskcolors (~(bit2mask(BLACKBIT, OLDBIT) | WHITEBITS)) +#define makewhite(g,x) \ + (gch(x)->marked = cast_byte((gch(x)->marked & maskcolors) | luaC_white(g))) + +#define white2gray(x) resetbits(gch(x)->marked, WHITEBITS) +#define black2gray(x) resetbit(gch(x)->marked, BLACKBIT) + + +#define isfinalized(x) testbit(gch(x)->marked, FINALIZEDBIT) + +#define checkdeadkey(n) lua_assert(!ttisdeadkey(gkey(n)) || ttisnil(gval(n))) + + +#define checkconsistency(obj) \ + lua_longassert(!iscollectable(obj) || righttt(obj)) + + +#define markvalue(g,o) { checkconsistency(o); \ + if (valiswhite(o)) reallymarkobject(g,gcvalue(o)); } + +#define markobject(g,t) { if ((t) && iswhite(obj2gco(t))) \ + reallymarkobject(g, obj2gco(t)); } + +static void reallymarkobject (global_State *g, GCObject *o); + + +/* +** {====================================================== +** Generic functions +** ======================================================= +*/ + + +/* +** one after last element in a hash array +*/ +#define gnodelast(h) gnode(h, cast(size_t, sizenode(h))) + + +/* +** link table 'h' into list pointed by 'p' +*/ +#define linktable(h,p) ((h)->gclist = *(p), *(p) = obj2gco(h)) + + +/* +** if key is not marked, mark its entry as dead (therefore removing it +** from the table) +*/ +static void removeentry (Node *n) { + lua_assert(ttisnil(gval(n))); + if (valiswhite(gkey(n))) + setdeadvalue(gkey(n)); /* unused and unmarked key; remove it */ +} + + +/* +** tells whether a key or value can be cleared from a weak +** table. Non-collectable objects are never removed from weak +** tables. Strings behave as `values', so are never removed too. for +** other objects: if really collected, cannot keep them; for objects +** being finalized, keep them in keys, but not in values +*/ +static int iscleared (global_State *g, const TValue *o) { + if (!iscollectable(o)) return 0; + else if (ttisstring(o)) { + markobject(g, rawtsvalue(o)); /* strings are `values', so are never weak */ + return 0; + } + else return iswhite(gcvalue(o)); +} + + +/* +** barrier that moves collector forward, that is, mark the white object +** being pointed by a black object. +*/ +void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { + global_State *g = G(L); + lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); + lua_assert(g->gcstate != GCSpause); + lua_assert(gch(o)->tt != LUA_TTABLE); + if (keepinvariantout(g)) /* must keep invariant? */ + reallymarkobject(g, v); /* restore invariant */ + else { /* sweep phase */ + lua_assert(issweepphase(g)); + makewhite(g, o); /* mark main obj. as white to avoid other barriers */ + } +} + + +/* +** barrier that moves collector backward, that is, mark the black object +** pointing to a white object as gray again. (Current implementation +** only works for tables; access to 'gclist' is not uniform across +** different types.) +*/ +void luaC_barrierback_ (lua_State *L, GCObject *o) { + global_State *g = G(L); + lua_assert(isblack(o) && !isdead(g, o) && gch(o)->tt == LUA_TTABLE); + black2gray(o); /* make object gray (again) */ + gco2t(o)->gclist = g->grayagain; + g->grayagain = o; +} + + +/* +** barrier for prototypes. When creating first closure (cache is +** NULL), use a forward barrier; this may be the only closure of the +** prototype (if it is a "regular" function, with a single instance) +** and the prototype may be big, so it is better to avoid traversing +** it again. Otherwise, use a backward barrier, to avoid marking all +** possible instances. +*/ +LUAI_FUNC void luaC_barrierproto_ (lua_State *L, Proto *p, Closure *c) { + global_State *g = G(L); + lua_assert(isblack(obj2gco(p))); + if (p->cache == NULL) { /* first time? */ + luaC_objbarrier(L, p, c); + } + else { /* use a backward barrier */ + black2gray(obj2gco(p)); /* make prototype gray (again) */ + p->gclist = g->grayagain; + g->grayagain = obj2gco(p); + } +} + + +/* +** check color (and invariants) for an upvalue that was closed, +** i.e., moved into the 'allgc' list +*/ +void luaC_checkupvalcolor (global_State *g, UpVal *uv) { + GCObject *o = obj2gco(uv); + lua_assert(!isblack(o)); /* open upvalues are never black */ + if (isgray(o)) { + if (keepinvariant(g)) { + resetoldbit(o); /* see MOVE OLD rule */ + gray2black(o); /* it is being visited now */ + markvalue(g, uv->v); + } + else { + lua_assert(issweepphase(g)); + makewhite(g, o); + } + } +} + + +/* +** create a new collectable object (with given type and size) and link +** it to '*list'. 'offset' tells how many bytes to allocate before the +** object itself (used only by states). +*/ +GCObject *luaC_newobj (lua_State *L, int tt, size_t sz, GCObject **list, + int offset) { + global_State *g = G(L); + char *raw = cast(char *, luaM_newobject(L, novariant(tt), sz)); + GCObject *o = obj2gco(raw + offset); + if (list == NULL) + list = &g->allgc; /* standard list for collectable objects */ + gch(o)->marked = luaC_white(g); + gch(o)->tt = tt; + gch(o)->next = *list; + *list = o; + return o; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Mark functions +** ======================================================= +*/ + + +/* +** mark an object. Userdata, strings, and closed upvalues are visited +** and turned black here. Other objects are marked gray and added +** to appropriate list to be visited (and turned black) later. (Open +** upvalues are already linked in 'headuv' list.) +*/ +static void reallymarkobject (global_State *g, GCObject *o) { + lu_mem size; + white2gray(o); + switch (gch(o)->tt) { + case LUA_TSHRSTR: + case LUA_TLNGSTR: { + size = sizestring(gco2ts(o)); + break; /* nothing else to mark; make it black */ + } + case LUA_TUSERDATA: { + Table *mt = gco2u(o)->metatable; + markobject(g, mt); + markobject(g, gco2u(o)->env); + size = sizeudata(gco2u(o)); + break; + } + case LUA_TUPVAL: { + UpVal *uv = gco2uv(o); + markvalue(g, uv->v); + if (uv->v != &uv->u.value) /* open? */ + return; /* open upvalues remain gray */ + size = sizeof(UpVal); + break; + } + case LUA_TLCL: { + gco2lcl(o)->gclist = g->gray; + g->gray = o; + return; + } + case LUA_TCCL: { + gco2ccl(o)->gclist = g->gray; + g->gray = o; + return; + } + case LUA_TTABLE: { + linktable(gco2t(o), &g->gray); + return; + } + case LUA_TTHREAD: { + gco2th(o)->gclist = g->gray; + g->gray = o; + return; + } + case LUA_TPROTO: { + gco2p(o)->gclist = g->gray; + g->gray = o; + return; + } + default: lua_assert(0); return; + } + gray2black(o); + g->GCmemtrav += size; +} + + +/* +** mark metamethods for basic types +*/ +static void markmt (global_State *g) { + int i; + for (i=0; i < LUA_NUMTAGS; i++) + markobject(g, g->mt[i]); +} + + +/* +** mark all objects in list of being-finalized +*/ +static void markbeingfnz (global_State *g) { + GCObject *o; + for (o = g->tobefnz; o != NULL; o = gch(o)->next) { + makewhite(g, o); + reallymarkobject(g, o); + } +} + + +/* +** mark all values stored in marked open upvalues. (See comment in +** 'lstate.h'.) +*/ +static void remarkupvals (global_State *g) { + UpVal *uv; + for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) { + if (isgray(obj2gco(uv))) + markvalue(g, uv->v); + } +} + + +/* +** mark root set and reset all gray lists, to start a new +** incremental (or full) collection +*/ +static void restartcollection (global_State *g) { + g->gray = g->grayagain = NULL; + g->weak = g->allweak = g->ephemeron = NULL; + markobject(g, g->mainthread); + markvalue(g, &g->l_registry); + markmt(g); + markbeingfnz(g); /* mark any finalizing object left from previous cycle */ +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Traverse functions +** ======================================================= +*/ + +static void traverseweakvalue (global_State *g, Table *h) { + Node *n, *limit = gnodelast(h); + /* if there is array part, assume it may have white values (do not + traverse it just to check) */ + int hasclears = (h->sizearray > 0); + for (n = gnode(h, 0); n < limit; n++) { + checkdeadkey(n); + if (ttisnil(gval(n))) /* entry is empty? */ + removeentry(n); /* remove it */ + else { + lua_assert(!ttisnil(gkey(n))); + markvalue(g, gkey(n)); /* mark key */ + if (!hasclears && iscleared(g, gval(n))) /* is there a white value? */ + hasclears = 1; /* table will have to be cleared */ + } + } + if (hasclears) + linktable(h, &g->weak); /* has to be cleared later */ + else /* no white values */ + linktable(h, &g->grayagain); /* no need to clean */ +} + + +static int traverseephemeron (global_State *g, Table *h) { + int marked = 0; /* true if an object is marked in this traversal */ + int hasclears = 0; /* true if table has white keys */ + int prop = 0; /* true if table has entry "white-key -> white-value" */ + Node *n, *limit = gnodelast(h); + int i; + /* traverse array part (numeric keys are 'strong') */ + for (i = 0; i < h->sizearray; i++) { + if (valiswhite(&h->array[i].i_val)) { + marked = 1; + reallymarkobject(g, gcvalue(&h->array[i].i_val)); + } + } + /* traverse hash part */ + for (n = gnode(h, 0); n < limit; n++) { + checkdeadkey(n); + if (ttisnil(gval(n))) /* entry is empty? */ + removeentry(n); /* remove it */ + else if (iscleared(g, gkey(n))) { /* key is not marked (yet)? */ + hasclears = 1; /* table must be cleared */ + if (valiswhite(gval(n))) /* value not marked yet? */ + prop = 1; /* must propagate again */ + } + else if (valiswhite(gval(n))) { /* value not marked yet? */ + marked = 1; + reallymarkobject(g, gcvalue(gval(n))); /* mark it now */ + } + } + if (g->gcstate != GCSatomic || prop) + linktable(h, &g->ephemeron); /* have to propagate again */ + else if (hasclears) /* does table have white keys? */ + linktable(h, &g->allweak); /* may have to clean white keys */ + else /* no white keys */ + linktable(h, &g->grayagain); /* no need to clean */ + return marked; +} + + +static void traversestrongtable (global_State *g, Table *h) { + Node *n, *limit = gnodelast(h); + int i; + for (i = 0; i < h->sizearray; i++) /* traverse array part */ + markvalue(g, &h->array[i].i_val); + for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ + checkdeadkey(n); + if (ttisnil(gval(n))) /* entry is empty? */ + removeentry(n); /* remove it */ + else { + lua_assert(!ttisnil(gkey(n))); + markvalue(g, gkey(n)); /* mark key */ + markvalue(g, gval(n)); /* mark value */ + } + } +} + + +static lu_mem traversetable (global_State *g, Table *h) { + const char *weakkey, *weakvalue; + const TValue *mode = gfasttm(g, h->metatable, TM_MODE); + markobject(g, h->metatable); + if (mode && ttisstring(mode) && /* is there a weak mode? */ + ((weakkey = strchr(svalue(mode), 'k')), + (weakvalue = strchr(svalue(mode), 'v')), + (weakkey || weakvalue))) { /* is really weak? */ + black2gray(obj2gco(h)); /* keep table gray */ + if (!weakkey) /* strong keys? */ + traverseweakvalue(g, h); + else if (!weakvalue) /* strong values? */ + traverseephemeron(g, h); + else /* all weak */ + linktable(h, &g->allweak); /* nothing to traverse now */ + } + else /* not weak */ + traversestrongtable(g, h); + return sizeof(Table) + sizeof(TValue) * h->sizearray + + sizeof(Node) * cast(size_t, sizenode(h)); +} + + +static int traverseproto (global_State *g, Proto *f) { + int i; + if (f->cache && iswhite(obj2gco(f->cache))) + f->cache = NULL; /* allow cache to be collected */ + markobject(g, f->source); + for (i = 0; i < f->sizek; i++) /* mark literals */ + markvalue(g, &f->k[i]); + for (i = 0; i < f->sizeupvalues; i++) /* mark upvalue names */ + markobject(g, f->upvalues[i].name); + for (i = 0; i < f->sizep; i++) /* mark nested protos */ + markobject(g, f->p[i]); + for (i = 0; i < f->sizelocvars; i++) /* mark local-variable names */ + markobject(g, f->locvars[i].varname); + return sizeof(Proto) + sizeof(Instruction) * f->sizecode + + sizeof(Proto *) * f->sizep + + sizeof(TValue) * f->sizek + + sizeof(int) * f->sizelineinfo + + sizeof(LocVar) * f->sizelocvars + + sizeof(Upvaldesc) * f->sizeupvalues; +} + + +static lu_mem traverseCclosure (global_State *g, CClosure *cl) { + int i; + for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */ + markvalue(g, &cl->upvalue[i]); + return sizeCclosure(cl->nupvalues); +} + +static lu_mem traverseLclosure (global_State *g, LClosure *cl) { + int i; + markobject(g, cl->p); /* mark its prototype */ + for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */ + markobject(g, cl->upvals[i]); + return sizeLclosure(cl->nupvalues); +} + + +static lu_mem traversestack (global_State *g, lua_State *th) { + int n = 0; + StkId o = th->stack; + if (o == NULL) + return 1; /* stack not completely built yet */ + for (; o < th->top; o++) /* mark live elements in the stack */ + markvalue(g, o); + if (g->gcstate == GCSatomic) { /* final traversal? */ + StkId lim = th->stack + th->stacksize; /* real end of stack */ + for (; o < lim; o++) /* clear not-marked stack slice */ + setnilvalue(o); + } + else { /* count call infos to compute size */ + CallInfo *ci; + for (ci = &th->base_ci; ci != th->ci; ci = ci->next) + n++; + } + return sizeof(lua_State) + sizeof(TValue) * th->stacksize + + sizeof(CallInfo) * n; +} + + +/* +** traverse one gray object, turning it to black (except for threads, +** which are always gray). +*/ +static void propagatemark (global_State *g) { + lu_mem size; + GCObject *o = g->gray; + lua_assert(isgray(o)); + gray2black(o); + switch (gch(o)->tt) { + case LUA_TTABLE: { + Table *h = gco2t(o); + g->gray = h->gclist; /* remove from 'gray' list */ + size = traversetable(g, h); + break; + } + case LUA_TLCL: { + LClosure *cl = gco2lcl(o); + g->gray = cl->gclist; /* remove from 'gray' list */ + size = traverseLclosure(g, cl); + break; + } + case LUA_TCCL: { + CClosure *cl = gco2ccl(o); + g->gray = cl->gclist; /* remove from 'gray' list */ + size = traverseCclosure(g, cl); + break; + } + case LUA_TTHREAD: { + lua_State *th = gco2th(o); + g->gray = th->gclist; /* remove from 'gray' list */ + th->gclist = g->grayagain; + g->grayagain = o; /* insert into 'grayagain' list */ + black2gray(o); + size = traversestack(g, th); + break; + } + case LUA_TPROTO: { + Proto *p = gco2p(o); + g->gray = p->gclist; /* remove from 'gray' list */ + size = traverseproto(g, p); + break; + } + default: lua_assert(0); return; + } + g->GCmemtrav += size; +} + + +static void propagateall (global_State *g) { + while (g->gray) propagatemark(g); +} + + +static void propagatelist (global_State *g, GCObject *l) { + lua_assert(g->gray == NULL); /* no grays left */ + g->gray = l; + propagateall(g); /* traverse all elements from 'l' */ +} + +/* +** retraverse all gray lists. Because tables may be reinserted in other +** lists when traversed, traverse the original lists to avoid traversing +** twice the same table (which is not wrong, but inefficient) +*/ +static void retraversegrays (global_State *g) { + GCObject *weak = g->weak; /* save original lists */ + GCObject *grayagain = g->grayagain; + GCObject *ephemeron = g->ephemeron; + g->weak = g->grayagain = g->ephemeron = NULL; + propagateall(g); /* traverse main gray list */ + propagatelist(g, grayagain); + propagatelist(g, weak); + propagatelist(g, ephemeron); +} + + +static void convergeephemerons (global_State *g) { + int changed; + do { + GCObject *w; + GCObject *next = g->ephemeron; /* get ephemeron list */ + g->ephemeron = NULL; /* tables will return to this list when traversed */ + changed = 0; + while ((w = next) != NULL) { + next = gco2t(w)->gclist; + if (traverseephemeron(g, gco2t(w))) { /* traverse marked some value? */ + propagateall(g); /* propagate changes */ + changed = 1; /* will have to revisit all ephemeron tables */ + } + } + } while (changed); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Sweep Functions +** ======================================================= +*/ + + +/* +** clear entries with unmarked keys from all weaktables in list 'l' up +** to element 'f' +*/ +static void clearkeys (global_State *g, GCObject *l, GCObject *f) { + for (; l != f; l = gco2t(l)->gclist) { + Table *h = gco2t(l); + Node *n, *limit = gnodelast(h); + for (n = gnode(h, 0); n < limit; n++) { + if (!ttisnil(gval(n)) && (iscleared(g, gkey(n)))) { + luaH_setnil(h, ganode(n)); + removeentry(n); /* and remove entry from table */ + } + } + } +} + + +/* +** clear entries with unmarked values from all weaktables in list 'l' up +** to element 'f' +*/ +static void clearvalues (global_State *g, GCObject *l, GCObject *f) { + for (; l != f; l = gco2t(l)->gclist) { + Table *h = gco2t(l); + Node *n, *limit = gnodelast(h); + int i; + for (i = 0; i < h->sizearray; i++) { + ANode *anode = h->array + i; + if (iscleared(g, &anode->i_val)) /* value was collected? */ + luaH_setnil(h, anode); + } + for (n = gnode(h, 0); n < limit; n++) { + if (!ttisnil(gval(n)) && iscleared(g, gval(n))) { + luaH_setnil(h, ganode(n)); /* remove value ... */ + removeentry(n); /* and remove entry from table */ + } + } + } +} + + +static void freeobj (lua_State *L, GCObject *o) { + switch (gch(o)->tt) { + case LUA_TPROTO: luaF_freeproto(L, gco2p(o)); break; + case LUA_TLCL: { + luaM_freemem(L, o, sizeLclosure(gco2lcl(o)->nupvalues)); + break; + } + case LUA_TCCL: { + luaM_freemem(L, o, sizeCclosure(gco2ccl(o)->nupvalues)); + break; + } + case LUA_TUPVAL: luaF_freeupval(L, gco2uv(o)); break; + case LUA_TTABLE: luaH_free(L, gco2t(o)); break; + case LUA_TTHREAD: luaE_freethread(L, gco2th(o)); break; + case LUA_TUSERDATA: luaM_freemem(L, o, sizeudata(gco2u(o))); break; + case LUA_TSHRSTR: + G(L)->strt.nuse--; + /* go through */ + case LUA_TLNGSTR: { + luaM_freemem(L, o, sizestring(gco2ts(o))); + break; + } + default: lua_assert(0); + } +} + + +#define sweepwholelist(L,p) sweeplist(L,p,MAX_LUMEM) +static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count); + + +/* +** sweep the (open) upvalues of a thread and resize its stack and +** list of call-info structures. +*/ +static void sweepthread (lua_State *L, lua_State *L1) { + if (L1->stack == NULL) return; /* stack not completely built yet */ + sweepwholelist(L, &L1->openupval); /* sweep open upvalues */ + luaE_freeCI(L1); /* free extra CallInfo slots */ + /* should not change the stack during an emergency gc cycle */ + if (G(L)->gckind != KGC_EMERGENCY) + luaD_shrinkstack(L1); +} + + +/* +** sweep at most 'count' elements from a list of GCObjects erasing dead +** objects, where a dead (not alive) object is one marked with the "old" +** (non current) white and not fixed. +** In non-generational mode, change all non-dead objects back to white, +** preparing for next collection cycle. +** In generational mode, keep black objects black, and also mark them as +** old; stop when hitting an old object, as all objects after that +** one will be old too. +** When object is a thread, sweep its list of open upvalues too. +*/ +static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { + global_State *g = G(L); + int ow = otherwhite(g); + int toclear, toset; /* bits to clear and to set in all live objects */ + int tostop; /* stop sweep when this is true */ + if (isgenerational(g)) { /* generational mode? */ + toclear = ~0; /* clear nothing */ + toset = bitmask(OLDBIT); /* set the old bit of all surviving objects */ + tostop = bitmask(OLDBIT); /* do not sweep old generation */ + } + else { /* normal mode */ + toclear = maskcolors; /* clear all color bits + old bit */ + toset = luaC_white(g); /* make object white */ + tostop = 0; /* do not stop */ + } + while (*p != NULL && count-- > 0) { + GCObject *curr = *p; + int marked = gch(curr)->marked; + if (isdeadm(ow, marked)) { /* is 'curr' dead? */ + *p = gch(curr)->next; /* remove 'curr' from list */ + freeobj(L, curr); /* erase 'curr' */ + } + else { + if (testbits(marked, tostop)) + return NULL; /* stop sweeping this list */ + if (gch(curr)->tt == LUA_TTHREAD) + sweepthread(L, gco2th(curr)); /* sweep thread's upvalues */ + /* update marks */ + gch(curr)->marked = cast_byte((marked & toclear) | toset); + p = &gch(curr)->next; /* go to next element */ + } + } + return (*p == NULL) ? NULL : p; +} + + +/* +** sweep a list until a live object (or end of list) +*/ +static GCObject **sweeptolive (lua_State *L, GCObject **p, int *n) { + GCObject ** old = p; + int i = 0; + do { + i++; + p = sweeplist(L, p, 1); + } while (p == old); + if (n) *n += i; + return p; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Finalization +** ======================================================= +*/ + +static void checkSizes (lua_State *L) { + global_State *g = G(L); + if (g->gckind != KGC_EMERGENCY) { /* do not change sizes in emergency */ + int hs = g->strt.size / 2; /* half the size of the string table */ + if (g->strt.nuse < cast(lu_int32, hs)) /* using less than that half? */ + luaS_resize(L, hs); /* halve its size */ + luaZ_freebuffer(L, &g->buff); /* free concatenation buffer */ + } +} + + +static GCObject *udata2finalize (global_State *g) { + GCObject *o = g->tobefnz; /* get first element */ + lua_assert(isfinalized(o)); + g->tobefnz = gch(o)->next; /* remove it from 'tobefnz' list */ + gch(o)->next = g->allgc; /* return it to 'allgc' list */ + g->allgc = o; + resetbit(gch(o)->marked, SEPARATED); /* mark that it is not in 'tobefnz' */ + lua_assert(!isold(o)); /* see MOVE OLD rule */ + if (!keepinvariantout(g)) /* not keeping invariant? */ + makewhite(g, o); /* "sweep" object */ + return o; +} + + +static void dothecall (lua_State *L, void *ud) { + UNUSED(ud); + luaD_call(L, L->top - 2, 0, 0); +} + + +static void GCTM (lua_State *L, int propagateerrors) { + global_State *g = G(L); + const TValue *tm; + TValue v; + setgcovalue(L, &v, udata2finalize(g)); + tm = luaT_gettmbyobj(L, &v, TM_GC); + if (tm != NULL && ttisfunction(tm)) { /* is there a finalizer? */ + int status; + lu_byte oldah = L->allowhook; + int running = g->gcrunning; + L->allowhook = 0; /* stop debug hooks during GC metamethod */ + g->gcrunning = 0; /* avoid GC steps */ + setobj2s(L, L->top, tm); /* push finalizer... */ + setobj2s(L, L->top + 1, &v); /* ... and its argument */ + L->top += 2; /* and (next line) call the finalizer */ + status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0); + L->allowhook = oldah; /* restore hooks */ + g->gcrunning = running; /* restore state */ + if (status != LUA_OK && propagateerrors) { /* error while running __gc? */ + if (status == LUA_ERRRUN) { /* is there an error object? */ + const char *msg = (ttisstring(L->top - 1)) + ? svalue(L->top - 1) + : "no message"; + luaO_pushfstring(L, "error in __gc metamethod (%s)", msg); + status = LUA_ERRGCMM; /* error in __gc metamethod */ + } + luaD_throw(L, status); /* re-throw error */ + } + } +} + + +/* +** move all unreachable objects (or 'all' objects) that need +** finalization from list 'finobj' to list 'tobefnz' (to be finalized) +*/ +static void separatetobefnz (lua_State *L, int all) { + global_State *g = G(L); + GCObject **p = &g->finobj; + GCObject *curr; + GCObject **lastnext = &g->tobefnz; + /* find last 'next' field in 'tobefnz' list (to add elements in its end) */ + while (*lastnext != NULL) + lastnext = &gch(*lastnext)->next; + while ((curr = *p) != NULL) { /* traverse all finalizable objects */ + lua_assert(!isfinalized(curr)); + lua_assert(testbit(gch(curr)->marked, SEPARATED)); + if (!(iswhite(curr) || all)) /* not being collected? */ + p = &gch(curr)->next; /* don't bother with it */ + else { + l_setbit(gch(curr)->marked, FINALIZEDBIT); /* won't be finalized again */ + *p = gch(curr)->next; /* remove 'curr' from 'finobj' list */ + gch(curr)->next = *lastnext; /* link at the end of 'tobefnz' list */ + *lastnext = curr; + lastnext = &gch(curr)->next; + } + } +} + + +/* +** if object 'o' has a finalizer, remove it from 'allgc' list (must +** search the list to find it) and link it in 'finobj' list. +*/ +void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { + global_State *g = G(L); + if (testbit(gch(o)->marked, SEPARATED) || /* obj. is already separated... */ + isfinalized(o) || /* ... or is finalized... */ + gfasttm(g, mt, TM_GC) == NULL) /* or has no finalizer? */ + return; /* nothing to be done */ + else { /* move 'o' to 'finobj' list */ + GCObject **p; + GCheader *ho = gch(o); + if (g->sweepgc == &ho->next) { /* avoid removing current sweep object */ + lua_assert(issweepphase(g)); + g->sweepgc = sweeptolive(L, g->sweepgc, NULL); + } + /* search for pointer pointing to 'o' */ + for (p = &g->allgc; *p != o; p = &gch(*p)->next) { /* empty */ } + *p = ho->next; /* remove 'o' from root list */ + ho->next = g->finobj; /* link it in list 'finobj' */ + g->finobj = o; + l_setbit(ho->marked, SEPARATED); /* mark it as such */ + if (!keepinvariantout(g)) /* not keeping invariant? */ + makewhite(g, o); /* "sweep" object */ + else + resetoldbit(o); /* see MOVE OLD rule */ + } +} + +/* }====================================================== */ + + +/* +** {====================================================== +** GC control +** ======================================================= +*/ + + +/* +** set a reasonable "time" to wait before starting a new GC cycle; +** cycle will start when memory use hits threshold +*/ +static void setpause (global_State *g, l_mem estimate) { + l_mem debt, threshold; + estimate = estimate / PAUSEADJ; /* adjust 'estimate' */ + threshold = (g->gcpause < MAX_LMEM / estimate) /* overflow? */ + ? estimate * g->gcpause /* no overflow */ + : MAX_LMEM; /* overflow; truncate to maximum */ + debt = -cast(l_mem, threshold - gettotalbytes(g)); + luaE_setdebt(g, debt); +} + + +#define sweepphases \ + (bitmask(GCSsweepstring) | bitmask(GCSsweepudata) | bitmask(GCSsweep)) + + +/* +** enter first sweep phase (strings) and prepare pointers for other +** sweep phases. The calls to 'sweeptolive' make pointers point to an +** object inside the list (instead of to the header), so that the real +** sweep do not need to skip objects created between "now" and the start +** of the real sweep. +** Returns how many objects it swept. +*/ +static int entersweep (lua_State *L) { + global_State *g = G(L); + int n = 0; + g->gcstate = GCSsweepstring; + lua_assert(g->sweepgc == NULL && g->sweepfin == NULL); + /* prepare to sweep strings, finalizable objects, and regular objects */ + g->sweepstrgc = 0; + g->sweepfin = sweeptolive(L, &g->finobj, &n); + g->sweepgc = sweeptolive(L, &g->allgc, &n); + return n; +} + + +/* +** change GC mode +*/ +void luaC_changemode (lua_State *L, int mode) { + global_State *g = G(L); + if (mode == g->gckind) return; /* nothing to change */ + if (mode == KGC_GEN) { /* change to generational mode */ + /* make sure gray lists are consistent */ + luaC_runtilstate(L, bitmask(GCSpropagate)); + g->GCestimate = gettotalbytes(g); + g->gckind = KGC_GEN; + } + else { /* change to incremental mode */ + /* sweep all objects to turn them back to white + (as white has not changed, nothing extra will be collected) */ + g->gckind = KGC_NORMAL; + entersweep(L); + luaC_runtilstate(L, ~sweepphases); + } +} + + +/* +** call all pending finalizers +*/ +static void callallpendingfinalizers (lua_State *L, int propagateerrors) { + global_State *g = G(L); + while (g->tobefnz) { + resetoldbit(g->tobefnz); + GCTM(L, propagateerrors); + } +} + + +void luaC_freeallobjects (lua_State *L) { + global_State *g = G(L); + int i; + separatetobefnz(L, 1); /* separate all objects with finalizers */ + lua_assert(g->finobj == NULL); + callallpendingfinalizers(L, 0); + g->currentwhite = WHITEBITS; /* this "white" makes all objects look dead */ + g->gckind = KGC_NORMAL; + sweepwholelist(L, &g->finobj); /* finalizers can create objs. in 'finobj' */ + sweepwholelist(L, &g->allgc); + for (i = 0; i < g->strt.size; i++) /* free all string lists */ + sweepwholelist(L, &g->strt.hash[i]); + lua_assert(g->strt.nuse == 0); +} + + +static l_mem atomic (lua_State *L) { + global_State *g = G(L); + l_mem work = -cast(l_mem, g->GCmemtrav); /* start counting work */ + GCObject *origweak, *origall; + lua_assert(!iswhite(obj2gco(g->mainthread))); + markobject(g, L); /* mark running thread */ + /* registry and global metatables may be changed by API */ + markvalue(g, &g->l_registry); + markmt(g); /* mark basic metatables */ + /* remark occasional upvalues of (maybe) dead threads */ + remarkupvals(g); + propagateall(g); /* propagate changes */ + work += g->GCmemtrav; /* stop counting (do not (re)count grays) */ + /* traverse objects caught by write barrier and by 'remarkupvals' */ + retraversegrays(g); + work -= g->GCmemtrav; /* restart counting */ + convergeephemerons(g); + /* at this point, all strongly accessible objects are marked. */ + /* clear values from weak tables, before checking finalizers */ + clearvalues(g, g->weak, NULL); + clearvalues(g, g->allweak, NULL); + origweak = g->weak; origall = g->allweak; + work += g->GCmemtrav; /* stop counting (objects being finalized) */ + separatetobefnz(L, 0); /* separate objects to be finalized */ + markbeingfnz(g); /* mark objects that will be finalized */ + propagateall(g); /* remark, to propagate `preserveness' */ + work -= g->GCmemtrav; /* restart counting */ + convergeephemerons(g); + /* at this point, all resurrected objects are marked. */ + /* remove dead objects from weak tables */ + clearkeys(g, g->ephemeron, NULL); /* clear keys from all ephemeron tables */ + clearkeys(g, g->allweak, NULL); /* clear keys from all allweak tables */ + /* clear values from resurrected weak tables */ + clearvalues(g, g->weak, origweak); + clearvalues(g, g->allweak, origall); + g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */ + work += g->GCmemtrav; /* complete counting */ + return work; /* estimate of memory marked by 'atomic' */ +} + + +static lu_mem singlestep (lua_State *L) { + global_State *g = G(L); + switch (g->gcstate) { + case GCSpause: { + /* start to count memory traversed */ + g->GCmemtrav = g->strt.size * sizeof(GCObject*); + lua_assert(!isgenerational(g)); + restartcollection(g); + g->gcstate = GCSpropagate; + return g->GCmemtrav; + } + case GCSpropagate: { + if (g->gray) { + lu_mem oldtrav = g->GCmemtrav; + propagatemark(g); + return g->GCmemtrav - oldtrav; /* memory traversed in this step */ + } + else { /* no more `gray' objects */ + lu_mem work; + int sw; + g->gcstate = GCSatomic; /* finish mark phase */ + g->GCestimate = g->GCmemtrav; /* save what was counted */; + work = atomic(L); /* add what was traversed by 'atomic' */ + g->GCestimate += work; /* estimate of total memory traversed */ + sw = entersweep(L); + return work + sw * GCSWEEPCOST; + } + } + case GCSsweepstring: { + int i; + for (i = 0; i < GCSWEEPMAX && g->sweepstrgc + i < g->strt.size; i++) + sweepwholelist(L, &g->strt.hash[g->sweepstrgc + i]); + g->sweepstrgc += i; + if (g->sweepstrgc >= g->strt.size) /* no more strings to sweep? */ + g->gcstate = GCSsweepudata; + return i * GCSWEEPCOST; + } + case GCSsweepudata: { + if (g->sweepfin) { + g->sweepfin = sweeplist(L, g->sweepfin, GCSWEEPMAX); + return GCSWEEPMAX*GCSWEEPCOST; + } + else { + g->gcstate = GCSsweep; + return 0; + } + } + case GCSsweep: { + if (g->sweepgc) { + g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); + return GCSWEEPMAX*GCSWEEPCOST; + } + else { + /* sweep main thread */ + GCObject *mt = obj2gco(g->mainthread); + sweeplist(L, &mt, 1); + checkSizes(L); + g->gcstate = GCSpause; /* finish collection */ + return GCSWEEPCOST; + } + } + default: lua_assert(0); return 0; + } +} + + +/* +** advances the garbage collector until it reaches a state allowed +** by 'statemask' +*/ +void luaC_runtilstate (lua_State *L, int statesmask) { + global_State *g = G(L); + while (!testbit(statesmask, g->gcstate)) + singlestep(L); +} + + +static void generationalcollection (lua_State *L) { + global_State *g = G(L); + lua_assert(g->gcstate == GCSpropagate); + if (g->GCestimate == 0) { /* signal for another major collection? */ + luaC_fullgc(L, 0); /* perform a full regular collection */ + g->GCestimate = gettotalbytes(g); /* update control */ + } + else { + lu_mem estimate = g->GCestimate; + luaC_runtilstate(L, bitmask(GCSpause)); /* run complete (minor) cycle */ + g->gcstate = GCSpropagate; /* skip restart */ + if (gettotalbytes(g) > (estimate / 100) * g->gcmajorinc) + g->GCestimate = 0; /* signal for a major collection */ + else + g->GCestimate = estimate; /* keep estimate from last major coll. */ + + } + setpause(g, gettotalbytes(g)); + lua_assert(g->gcstate == GCSpropagate); +} + + +static void incstep (lua_State *L) { + global_State *g = G(L); + l_mem debt = g->GCdebt; + int stepmul = g->gcstepmul; + if (stepmul < 40) stepmul = 40; /* avoid ridiculous low values (and 0) */ + /* convert debt from Kb to 'work units' (avoid zero debt and overflows) */ + debt = (debt / STEPMULADJ) + 1; + debt = (debt < MAX_LMEM / stepmul) ? debt * stepmul : MAX_LMEM; + do { /* always perform at least one single step */ + lu_mem work = singlestep(L); /* do some work */ + debt -= work; + } while (debt > -GCSTEPSIZE && g->gcstate != GCSpause); + if (g->gcstate == GCSpause) + setpause(g, g->GCestimate); /* pause until next cycle */ + else { + debt = (debt / stepmul) * STEPMULADJ; /* convert 'work units' to Kb */ + luaE_setdebt(g, debt); + } +} + + +/* +** performs a basic GC step +*/ +void luaC_forcestep (lua_State *L) { + global_State *g = G(L); + int i; + if (isgenerational(g)) generationalcollection(L); + else incstep(L); + /* run a few finalizers (or all of them at the end of a collect cycle) */ + for (i = 0; g->tobefnz && (i < GCFINALIZENUM || g->gcstate == GCSpause); i++) + GCTM(L, 1); /* call one finalizer */ +} + + +/* +** performs a basic GC step only if collector is running +*/ +void luaC_step (lua_State *L) { + global_State *g = G(L); + if (g->gcrunning) luaC_forcestep(L); + else luaE_setdebt(g, -GCSTEPSIZE); /* avoid being called too often */ +} + + + +/* +** performs a full GC cycle; if "isemergency", does not call +** finalizers (which could change stack positions) +*/ +void luaC_fullgc (lua_State *L, int isemergency) { + global_State *g = G(L); + int origkind = g->gckind; + lua_assert(origkind != KGC_EMERGENCY); + if (isemergency) /* do not run finalizers during emergency GC */ + g->gckind = KGC_EMERGENCY; + else { + g->gckind = KGC_NORMAL; + callallpendingfinalizers(L, 1); + } + if (keepinvariant(g)) { /* may there be some black objects? */ + /* must sweep all objects to turn them back to white + (as white has not changed, nothing will be collected) */ + entersweep(L); + } + /* finish any pending sweep phase to start a new cycle */ + luaC_runtilstate(L, bitmask(GCSpause)); + luaC_runtilstate(L, ~bitmask(GCSpause)); /* start new collection */ + luaC_runtilstate(L, bitmask(GCSpause)); /* run entire collection */ + if (origkind == KGC_GEN) { /* generational mode? */ + /* generational mode must be kept in propagate phase */ + luaC_runtilstate(L, bitmask(GCSpropagate)); + } + g->gckind = origkind; + setpause(g, gettotalbytes(g)); + if (!isemergency) /* do not run finalizers during emergency GC */ + callallpendingfinalizers(L, 1); +} + +/* }====================================================== */ + + diff --git a/luprex/ext/eris-master/src/lgc.h b/luprex/ext/eris-master/src/lgc.h new file mode 100644 index 00000000..84bb1cdf --- /dev/null +++ b/luprex/ext/eris-master/src/lgc.h @@ -0,0 +1,157 @@ +/* +** $Id: lgc.h,v 2.58.1.1 2013/04/12 18:48:47 roberto Exp $ +** Garbage Collector +** See Copyright Notice in lua.h +*/ + +#ifndef lgc_h +#define lgc_h + + +#include "lobject.h" +#include "lstate.h" + +/* +** Collectable objects may have one of three colors: white, which +** means the object is not marked; gray, which means the +** object is marked, but its references may be not marked; and +** black, which means that the object and all its references are marked. +** The main invariant of the garbage collector, while marking objects, +** is that a black object can never point to a white one. Moreover, +** any gray object must be in a "gray list" (gray, grayagain, weak, +** allweak, ephemeron) so that it can be visited again before finishing +** the collection cycle. These lists have no meaning when the invariant +** is not being enforced (e.g., sweep phase). +*/ + + + +/* how much to allocate before next GC step */ +#if !defined(GCSTEPSIZE) +/* ~100 small strings */ +#define GCSTEPSIZE (cast_int(100 * sizeof(TString))) +#endif + + +/* +** Possible states of the Garbage Collector +*/ +#define GCSpropagate 0 +#define GCSatomic 1 +#define GCSsweepstring 2 +#define GCSsweepudata 3 +#define GCSsweep 4 +#define GCSpause 5 + + +#define issweepphase(g) \ + (GCSsweepstring <= (g)->gcstate && (g)->gcstate <= GCSsweep) + +#define isgenerational(g) ((g)->gckind == KGC_GEN) + +/* +** macros to tell when main invariant (white objects cannot point to black +** ones) must be kept. During a non-generational collection, the sweep +** phase may break the invariant, as objects turned white may point to +** still-black objects. The invariant is restored when sweep ends and +** all objects are white again. During a generational collection, the +** invariant must be kept all times. +*/ + +#define keepinvariant(g) (isgenerational(g) || g->gcstate <= GCSatomic) + + +/* +** Outside the collector, the state in generational mode is kept in +** 'propagate', so 'keepinvariant' is always true. +*/ +#define keepinvariantout(g) \ + check_exp(g->gcstate == GCSpropagate || !isgenerational(g), \ + g->gcstate <= GCSatomic) + + +/* +** some useful bit tricks +*/ +#define resetbits(x,m) ((x) &= cast(lu_byte, ~(m))) +#define setbits(x,m) ((x) |= (m)) +#define testbits(x,m) ((x) & (m)) +#define bitmask(b) (1<<(b)) +#define bit2mask(b1,b2) (bitmask(b1) | bitmask(b2)) +#define l_setbit(x,b) setbits(x, bitmask(b)) +#define resetbit(x,b) resetbits(x, bitmask(b)) +#define testbit(x,b) testbits(x, bitmask(b)) + + +/* Layout for bit use in `marked' field: */ +#define WHITE0BIT 0 /* object is white (type 0) */ +#define WHITE1BIT 1 /* object is white (type 1) */ +#define BLACKBIT 2 /* object is black */ +#define FINALIZEDBIT 3 /* object has been separated for finalization */ +#define SEPARATED 4 /* object is in 'finobj' list or in 'tobefnz' */ +#define FIXEDBIT 5 /* object is fixed (should not be collected) */ +#define OLDBIT 6 /* object is old (only in generational mode) */ +/* bit 7 is currently used by tests (luaL_checkmemory) */ + +#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) + + +#define iswhite(x) testbits((x)->gch.marked, WHITEBITS) +#define isblack(x) testbit((x)->gch.marked, BLACKBIT) +#define isgray(x) /* neither white nor black */ \ + (!testbits((x)->gch.marked, WHITEBITS | bitmask(BLACKBIT))) + +#define isold(x) testbit((x)->gch.marked, OLDBIT) + +/* MOVE OLD rule: whenever an object is moved to the beginning of + a GC list, its old bit must be cleared */ +#define resetoldbit(o) resetbit((o)->gch.marked, OLDBIT) + +#define otherwhite(g) (g->currentwhite ^ WHITEBITS) +#define isdeadm(ow,m) (!(((m) ^ WHITEBITS) & (ow))) +#define isdead(g,v) isdeadm(otherwhite(g), (v)->gch.marked) + +#define changewhite(x) ((x)->gch.marked ^= WHITEBITS) +#define gray2black(x) l_setbit((x)->gch.marked, BLACKBIT) + +#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) + +#define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS) + + +#define luaC_condGC(L,c) \ + {if (G(L)->GCdebt > 0) {c;}; condchangemem(L);} +#define luaC_checkGC(L) luaC_condGC(L, luaC_step(L);) + + +#define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ + luaC_barrier_(L,obj2gco(p),gcvalue(v)); } + +#define luaC_barrierback(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ + luaC_barrierback_(L,p); } + +#define luaC_objbarrier(L,p,o) \ + { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \ + luaC_barrier_(L,obj2gco(p),obj2gco(o)); } + +#define luaC_objbarrierback(L,p,o) \ + { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) luaC_barrierback_(L,p); } + +#define luaC_barrierproto(L,p,c) \ + { if (isblack(obj2gco(p))) luaC_barrierproto_(L,p,c); } + +LUAI_FUNC void luaC_freeallobjects (lua_State *L); +LUAI_FUNC void luaC_step (lua_State *L); +LUAI_FUNC void luaC_forcestep (lua_State *L); +LUAI_FUNC void luaC_runtilstate (lua_State *L, int statesmask); +LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency); +LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz, + GCObject **list, int offset); +LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v); +LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o); +LUAI_FUNC void luaC_barrierproto_ (lua_State *L, Proto *p, Closure *c); +LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt); +LUAI_FUNC void luaC_checkupvalcolor (global_State *g, UpVal *uv); +LUAI_FUNC void luaC_changemode (lua_State *L, int mode); + +#endif diff --git a/luprex/ext/eris-master/src/linit.c b/luprex/ext/eris-master/src/linit.c new file mode 100644 index 00000000..0903f689 --- /dev/null +++ b/luprex/ext/eris-master/src/linit.c @@ -0,0 +1,68 @@ +/* +** $Id: linit.c,v 1.32.1.1 2013/04/12 18:48:47 roberto Exp $ +** Initialization of libraries for lua.c and other clients +** See Copyright Notice in lua.h +*/ + + +/* +** If you embed Lua in your program and need to open the standard +** libraries, call luaL_openlibs in your program. If you need a +** different set of libraries, copy this file to your project and edit +** it to suit your needs. +*/ + + +#define linit_c +#define LUA_LIB + +#include "lua.h" + +#include "lualib.h" +#include "lauxlib.h" + + +/* +** these libs are loaded by lua.c and are readily available to any Lua +** program +*/ +static const luaL_Reg loadedlibs[] = { + {"_G", luaopen_base}, + {LUA_LOADLIBNAME, luaopen_package}, + {LUA_COLIBNAME, luaopen_coroutine}, + {LUA_TABLIBNAME, luaopen_table}, + {LUA_IOLIBNAME, luaopen_io}, + {LUA_OSLIBNAME, luaopen_os}, + {LUA_STRLIBNAME, luaopen_string}, + {LUA_BITLIBNAME, luaopen_bit32}, + {LUA_MATHLIBNAME, luaopen_math}, + {LUA_DBLIBNAME, luaopen_debug}, + {LUA_ERISLIBNAME, luaopen_eris}, + {NULL, NULL} +}; + + +/* +** these libs are preloaded and must be required before used +*/ +static const luaL_Reg preloadedlibs[] = { + {NULL, NULL} +}; + + +LUALIB_API void luaL_openlibs (lua_State *L) { + const luaL_Reg *lib; + /* call open functions from 'loadedlibs' and set results to global table */ + for (lib = loadedlibs; lib->func; lib++) { + luaL_requiref(L, lib->name, lib->func, 1); + lua_pop(L, 1); /* remove lib */ + } + /* add open functions from 'preloadedlibs' into 'package.preload' table */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); + for (lib = preloadedlibs; lib->func; lib++) { + lua_pushcfunction(L, lib->func); + lua_setfield(L, -2, lib->name); + } + lua_pop(L, 1); /* remove _PRELOAD table */ +} + diff --git a/luprex/ext/eris-master/src/liolib.c b/luprex/ext/eris-master/src/liolib.c new file mode 100644 index 00000000..92c66586 --- /dev/null +++ b/luprex/ext/eris-master/src/liolib.c @@ -0,0 +1,682 @@ +/* +** $Id: liolib.c,v 2.112.1.1 2013/04/12 18:48:47 roberto Exp $ +** Standard I/O (and system) library +** See Copyright Notice in lua.h +*/ + + +/* +** This definition must come before the inclusion of 'stdio.h'; it +** should not affect non-POSIX systems +*/ +#if !defined(_FILE_OFFSET_BITS) +#define _LARGEFILE_SOURCE 1 +#define _FILE_OFFSET_BITS 64 +#endif + + +#include +#include +#include +#include + +#define liolib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#if !defined(lua_checkmode) + +/* +** Check whether 'mode' matches '[rwa]%+?b?'. +** Change this macro to accept other modes for 'fopen' besides +** the standard ones. +*/ +#define lua_checkmode(mode) \ + (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && \ + (*mode != '+' || ++mode) && /* skip if char is '+' */ \ + (*mode != 'b' || ++mode) && /* skip if char is 'b' */ \ + (*mode == '\0')) + +#endif + +/* +** {====================================================== +** lua_popen spawns a new process connected to the current +** one through the file streams. +** ======================================================= +*/ + +#if !defined(lua_popen) /* { */ + +#if defined(LUA_USE_POPEN) /* { */ + +#define lua_popen(L,c,m) ((void)L, fflush(NULL), popen(c,m)) +#define lua_pclose(L,file) ((void)L, pclose(file)) + +#elif defined(LUA_WIN) /* }{ */ + +#define lua_popen(L,c,m) ((void)L, _popen(c,m)) +#define lua_pclose(L,file) ((void)L, _pclose(file)) + + +#else /* }{ */ + +#define lua_popen(L,c,m) ((void)((void)c, m), \ + luaL_error(L, LUA_QL("popen") " not supported"), (FILE*)0) +#define lua_pclose(L,file) ((void)((void)L, file), -1) + + +#endif /* } */ + +#endif /* } */ + +/* }====================================================== */ + + +/* +** {====================================================== +** lua_fseek: configuration for longer offsets +** ======================================================= +*/ + +#if !defined(lua_fseek) && !defined(LUA_ANSI) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + +#define l_fseek(f,o,w) fseeko(f,o,w) +#define l_ftell(f) ftello(f) +#define l_seeknum off_t + +#elif defined(LUA_WIN) && !defined(_CRTIMP_TYPEINFO) \ + && defined(_MSC_VER) && (_MSC_VER >= 1400) /* }{ */ +/* Windows (but not DDK) and Visual C++ 2005 or higher */ + +#define l_fseek(f,o,w) _fseeki64(f,o,w) +#define l_ftell(f) _ftelli64(f) +#define l_seeknum __int64 + +#endif /* } */ + +#endif /* } */ + + +#if !defined(l_fseek) /* default definitions */ +#define l_fseek(f,o,w) fseek(f,o,w) +#define l_ftell(f) ftell(f) +#define l_seeknum long +#endif + +/* }====================================================== */ + + +#define IO_PREFIX "_IO_" +#define IO_INPUT (IO_PREFIX "input") +#define IO_OUTPUT (IO_PREFIX "output") + + +typedef luaL_Stream LStream; + + +#define tolstream(L) ((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE)) + +#define isclosed(p) ((p)->closef == NULL) + + +static int io_type (lua_State *L) { + LStream *p; + luaL_checkany(L, 1); + p = (LStream *)luaL_testudata(L, 1, LUA_FILEHANDLE); + if (p == NULL) + lua_pushnil(L); /* not a file */ + else if (isclosed(p)) + lua_pushliteral(L, "closed file"); + else + lua_pushliteral(L, "file"); + return 1; +} + + +static int f_tostring (lua_State *L) { + LStream *p = tolstream(L); + if (isclosed(p)) + lua_pushliteral(L, "file (closed)"); + else + lua_pushfstring(L, "file (%p)", p->f); + return 1; +} + + +static FILE *tofile (lua_State *L) { + LStream *p = tolstream(L); + if (isclosed(p)) + luaL_error(L, "attempt to use a closed file"); + lua_assert(p->f); + return p->f; +} + + +/* +** When creating file handles, always creates a `closed' file handle +** before opening the actual file; so, if there is a memory error, the +** file is not left opened. +*/ +static LStream *newprefile (lua_State *L) { + LStream *p = (LStream *)lua_newuserdata(L, sizeof(LStream)); + p->closef = NULL; /* mark file handle as 'closed' */ + luaL_setmetatable(L, LUA_FILEHANDLE); + return p; +} + + +static int aux_close (lua_State *L) { + LStream *p = tolstream(L); + lua_CFunction cf = p->closef; + p->closef = NULL; /* mark stream as closed */ + return (*cf)(L); /* close it */ +} + + +static int io_close (lua_State *L) { + if (lua_isnone(L, 1)) /* no argument? */ + lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT); /* use standard output */ + tofile(L); /* make sure argument is an open stream */ + return aux_close(L); +} + + +static int f_gc (lua_State *L) { + LStream *p = tolstream(L); + if (!isclosed(p) && p->f != NULL) + aux_close(L); /* ignore closed and incompletely open files */ + return 0; +} + + +/* +** function to close regular files +*/ +static int io_fclose (lua_State *L) { + LStream *p = tolstream(L); + int res = fclose(p->f); + return luaL_fileresult(L, (res == 0), NULL); +} + + +static LStream *newfile (lua_State *L) { + LStream *p = newprefile(L); + p->f = NULL; + p->closef = &io_fclose; + return p; +} + + +static void opencheck (lua_State *L, const char *fname, const char *mode) { + LStream *p = newfile(L); + p->f = fopen(fname, mode); + if (p->f == NULL) + luaL_error(L, "cannot open file " LUA_QS " (%s)", fname, strerror(errno)); +} + + +static int io_open (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + LStream *p = newfile(L); + const char *md = mode; /* to traverse/check mode */ + luaL_argcheck(L, lua_checkmode(md), 2, "invalid mode"); + p->f = fopen(filename, mode); + return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; +} + + +/* +** function to close 'popen' files +*/ +static int io_pclose (lua_State *L) { + LStream *p = tolstream(L); + return luaL_execresult(L, lua_pclose(L, p->f)); +} + + +static int io_popen (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + LStream *p = newprefile(L); + p->f = lua_popen(L, filename, mode); + p->closef = &io_pclose; + return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; +} + + +static int io_tmpfile (lua_State *L) { + LStream *p = newfile(L); + p->f = tmpfile(); + return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1; +} + + +static FILE *getiofile (lua_State *L, const char *findex) { + LStream *p; + lua_getfield(L, LUA_REGISTRYINDEX, findex); + p = (LStream *)lua_touserdata(L, -1); + if (isclosed(p)) + luaL_error(L, "standard %s file is closed", findex + strlen(IO_PREFIX)); + return p->f; +} + + +static int g_iofile (lua_State *L, const char *f, const char *mode) { + if (!lua_isnoneornil(L, 1)) { + const char *filename = lua_tostring(L, 1); + if (filename) + opencheck(L, filename, mode); + else { + tofile(L); /* check that it's a valid file handle */ + lua_pushvalue(L, 1); + } + lua_setfield(L, LUA_REGISTRYINDEX, f); + } + /* return current value */ + lua_getfield(L, LUA_REGISTRYINDEX, f); + return 1; +} + + +static int io_input (lua_State *L) { + return g_iofile(L, IO_INPUT, "r"); +} + + +static int io_output (lua_State *L) { + return g_iofile(L, IO_OUTPUT, "w"); +} + + +static int io_readline (lua_State *L); + + +static void aux_lines (lua_State *L, int toclose) { + int i; + int n = lua_gettop(L) - 1; /* number of arguments to read */ + /* ensure that arguments will fit here and into 'io_readline' stack */ + luaL_argcheck(L, n <= LUA_MINSTACK - 3, LUA_MINSTACK - 3, "too many options"); + lua_pushvalue(L, 1); /* file handle */ + lua_pushinteger(L, n); /* number of arguments to read */ + lua_pushboolean(L, toclose); /* close/not close file when finished */ + for (i = 1; i <= n; i++) lua_pushvalue(L, i + 1); /* copy arguments */ + lua_pushcclosure(L, io_readline, 3 + n); +} + + +static int f_lines (lua_State *L) { + tofile(L); /* check that it's a valid file handle */ + aux_lines(L, 0); + return 1; +} + + +static int io_lines (lua_State *L) { + int toclose; + if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */ + if (lua_isnil(L, 1)) { /* no file name? */ + lua_getfield(L, LUA_REGISTRYINDEX, IO_INPUT); /* get default input */ + lua_replace(L, 1); /* put it at index 1 */ + tofile(L); /* check that it's a valid file handle */ + toclose = 0; /* do not close it after iteration */ + } + else { /* open a new file */ + const char *filename = luaL_checkstring(L, 1); + opencheck(L, filename, "r"); + lua_replace(L, 1); /* put file at index 1 */ + toclose = 1; /* close it after iteration */ + } + aux_lines(L, toclose); + return 1; +} + + +/* +** {====================================================== +** READ +** ======================================================= +*/ + + +static int read_number (lua_State *L, FILE *f) { + lua_Number d; + if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) { + lua_pushnumber(L, d); + return 1; + } + else { + lua_pushnil(L); /* "result" to be removed */ + return 0; /* read fails */ + } +} + + +static int test_eof (lua_State *L, FILE *f) { + int c = getc(f); + ungetc(c, f); + lua_pushlstring(L, NULL, 0); + return (c != EOF); +} + + +static int read_line (lua_State *L, FILE *f, int chop) { + luaL_Buffer b; + luaL_buffinit(L, &b); + for (;;) { + size_t l; + char *p = luaL_prepbuffer(&b); + if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) { /* eof? */ + luaL_pushresult(&b); /* close buffer */ + return (lua_rawlen(L, -1) > 0); /* check whether read something */ + } + l = strlen(p); + if (l == 0 || p[l-1] != '\n') + luaL_addsize(&b, l); + else { + luaL_addsize(&b, l - chop); /* chop 'eol' if needed */ + luaL_pushresult(&b); /* close buffer */ + return 1; /* read at least an `eol' */ + } + } +} + + +#define MAX_SIZE_T (~(size_t)0) + +static void read_all (lua_State *L, FILE *f) { + size_t rlen = LUAL_BUFFERSIZE; /* how much to read in each cycle */ + luaL_Buffer b; + luaL_buffinit(L, &b); + for (;;) { + char *p = luaL_prepbuffsize(&b, rlen); + size_t nr = fread(p, sizeof(char), rlen, f); + luaL_addsize(&b, nr); + if (nr < rlen) break; /* eof? */ + else if (rlen <= (MAX_SIZE_T / 4)) /* avoid buffers too large */ + rlen *= 2; /* double buffer size at each iteration */ + } + luaL_pushresult(&b); /* close buffer */ +} + + +static int read_chars (lua_State *L, FILE *f, size_t n) { + size_t nr; /* number of chars actually read */ + char *p; + luaL_Buffer b; + luaL_buffinit(L, &b); + p = luaL_prepbuffsize(&b, n); /* prepare buffer to read whole block */ + nr = fread(p, sizeof(char), n, f); /* try to read 'n' chars */ + luaL_addsize(&b, nr); + luaL_pushresult(&b); /* close buffer */ + return (nr > 0); /* true iff read something */ +} + + +static int g_read (lua_State *L, FILE *f, int first) { + int nargs = lua_gettop(L) - 1; + int success; + int n; + clearerr(f); + if (nargs == 0) { /* no arguments? */ + success = read_line(L, f, 1); + n = first+1; /* to return 1 result */ + } + else { /* ensure stack space for all results and for auxlib's buffer */ + luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); + success = 1; + for (n = first; nargs-- && success; n++) { + if (lua_type(L, n) == LUA_TNUMBER) { + size_t l = (size_t)lua_tointeger(L, n); + success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); + } + else { + const char *p = lua_tostring(L, n); + luaL_argcheck(L, p && p[0] == '*', n, "invalid option"); + switch (p[1]) { + case 'n': /* number */ + success = read_number(L, f); + break; + case 'l': /* line */ + success = read_line(L, f, 1); + break; + case 'L': /* line with end-of-line */ + success = read_line(L, f, 0); + break; + case 'a': /* file */ + read_all(L, f); /* read entire file */ + success = 1; /* always success */ + break; + default: + return luaL_argerror(L, n, "invalid format"); + } + } + } + } + if (ferror(f)) + return luaL_fileresult(L, 0, NULL); + if (!success) { + lua_pop(L, 1); /* remove last result */ + lua_pushnil(L); /* push nil instead */ + } + return n - first; +} + + +static int io_read (lua_State *L) { + return g_read(L, getiofile(L, IO_INPUT), 1); +} + + +static int f_read (lua_State *L) { + return g_read(L, tofile(L), 2); +} + + +static int io_readline (lua_State *L) { + LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1)); + int i; + int n = (int)lua_tointeger(L, lua_upvalueindex(2)); + if (isclosed(p)) /* file is already closed? */ + return luaL_error(L, "file is already closed"); + lua_settop(L , 1); + for (i = 1; i <= n; i++) /* push arguments to 'g_read' */ + lua_pushvalue(L, lua_upvalueindex(3 + i)); + n = g_read(L, p->f, 2); /* 'n' is number of results */ + lua_assert(n > 0); /* should return at least a nil */ + if (!lua_isnil(L, -n)) /* read at least one value? */ + return n; /* return them */ + else { /* first result is nil: EOF or error */ + if (n > 1) { /* is there error information? */ + /* 2nd result is error message */ + return luaL_error(L, "%s", lua_tostring(L, -n + 1)); + } + if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */ + lua_settop(L, 0); + lua_pushvalue(L, lua_upvalueindex(1)); + aux_close(L); /* close it */ + } + return 0; + } +} + +/* }====================================================== */ + + +static int g_write (lua_State *L, FILE *f, int arg) { + int nargs = lua_gettop(L) - arg; + int status = 1; + for (; nargs--; arg++) { + if (lua_type(L, arg) == LUA_TNUMBER) { + /* optimization: could be done exactly as for strings */ + status = status && + fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0; + } + else { + size_t l; + const char *s = luaL_checklstring(L, arg, &l); + status = status && (fwrite(s, sizeof(char), l, f) == l); + } + } + if (status) return 1; /* file handle already on stack top */ + else return luaL_fileresult(L, status, NULL); +} + + +static int io_write (lua_State *L) { + return g_write(L, getiofile(L, IO_OUTPUT), 1); +} + + +static int f_write (lua_State *L) { + FILE *f = tofile(L); + lua_pushvalue(L, 1); /* push file at the stack top (to be returned) */ + return g_write(L, f, 2); +} + + +static int f_seek (lua_State *L) { + static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; + static const char *const modenames[] = {"set", "cur", "end", NULL}; + FILE *f = tofile(L); + int op = luaL_checkoption(L, 2, "cur", modenames); + lua_Number p3 = luaL_optnumber(L, 3, 0); + l_seeknum offset = (l_seeknum)p3; + luaL_argcheck(L, (lua_Number)offset == p3, 3, + "not an integer in proper range"); + op = l_fseek(f, offset, mode[op]); + if (op) + return luaL_fileresult(L, 0, NULL); /* error */ + else { + lua_pushnumber(L, (lua_Number)l_ftell(f)); + return 1; + } +} + + +static int f_setvbuf (lua_State *L) { + static const int mode[] = {_IONBF, _IOFBF, _IOLBF}; + static const char *const modenames[] = {"no", "full", "line", NULL}; + FILE *f = tofile(L); + int op = luaL_checkoption(L, 2, NULL, modenames); + lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); + int res = setvbuf(f, NULL, mode[op], sz); + return luaL_fileresult(L, res == 0, NULL); +} + + + +static int io_flush (lua_State *L) { + return luaL_fileresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL); +} + + +static int f_flush (lua_State *L) { + return luaL_fileresult(L, fflush(tofile(L)) == 0, NULL); +} + + +/* +** functions for 'io' library +*/ +static const luaL_Reg iolib[] = { + {"close", io_close}, + {"flush", io_flush}, + {"input", io_input}, + {"lines", io_lines}, + {"open", io_open}, + {"output", io_output}, + {"popen", io_popen}, + {"read", io_read}, + {"tmpfile", io_tmpfile}, + {"type", io_type}, + {"write", io_write}, + {NULL, NULL} +}; + + +/* +** methods for file handles +*/ +static const luaL_Reg flib[] = { + {"close", io_close}, + {"flush", f_flush}, + {"lines", f_lines}, + {"read", f_read}, + {"seek", f_seek}, + {"setvbuf", f_setvbuf}, + {"write", f_write}, + {"__gc", f_gc}, + {"__tostring", f_tostring}, + {NULL, NULL} +}; + + +static void createmeta (lua_State *L) { + luaL_newmetatable(L, LUA_FILEHANDLE); /* create metatable for file handles */ + lua_pushvalue(L, -1); /* push metatable */ + lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */ + luaL_setfuncs(L, flib, 0); /* add file methods to new metatable */ + lua_pop(L, 1); /* pop new metatable */ +} + + +/* +** function to (not) close the standard files stdin, stdout, and stderr +*/ +static int io_noclose (lua_State *L) { + LStream *p = tolstream(L); + p->closef = &io_noclose; /* keep file opened */ + lua_pushnil(L); + lua_pushliteral(L, "cannot close standard file"); + return 2; +} + + +static void createstdfile (lua_State *L, FILE *f, const char *k, + const char *fname) { + LStream *p = newprefile(L); + p->f = f; + p->closef = &io_noclose; + if (k != NULL) { + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, k); /* add file to registry */ + } + lua_setfield(L, -2, fname); /* add file to module */ +} + + +LUAMOD_API int luaopen_io (lua_State *L) { + luaL_newlib(L, iolib); /* new module */ + createmeta(L); + /* create (and set) default files */ + createstdfile(L, stdin, IO_INPUT, "stdin"); + createstdfile(L, stdout, IO_OUTPUT, "stdout"); + createstdfile(L, stderr, NULL, "stderr"); + return 1; +} + + +void eris_permiolib(lua_State *L, int forUnpersist) { + luaL_checktype(L, -1, LUA_TTABLE); + luaL_checkstack(L, 2, NULL); + + if (forUnpersist) { + lua_pushstring(L, "__eris.iolib_io_readline"); + lua_pushcfunction(L, io_readline); + } + else { + lua_pushcfunction(L, io_readline); + lua_pushstring(L, "__eris.iolib_io_readline"); + } + lua_rawset(L, -3); +} + diff --git a/luprex/ext/eris-master/src/llex.c b/luprex/ext/eris-master/src/llex.c new file mode 100644 index 00000000..ee6d8030 --- /dev/null +++ b/luprex/ext/eris-master/src/llex.c @@ -0,0 +1,530 @@ +/* +** $Id: llex.c,v 2.63.1.3 2015/02/09 17:56:34 roberto Exp $ +** Lexical Analyzer +** See Copyright Notice in lua.h +*/ + + +#include +#include + +#define llex_c +#define LUA_CORE + +#include "lua.h" + +#include "lctype.h" +#include "ldo.h" +#include "llex.h" +#include "lobject.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "lzio.h" + + + +#define next(ls) (ls->current = zgetc(ls->z)) + + + +#define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') + + +/* ORDER RESERVED */ +static const char *const luaX_tokens [] = { + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "goto", "if", + "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while", + "..", "...", "==", ">=", "<=", "~=", "::", "", + "", "", "" +}; + + +#define save_and_next(ls) (save(ls, ls->current), next(ls)) + + +static l_noret lexerror (LexState *ls, const char *msg, int token); + + +static void save (LexState *ls, int c) { + Mbuffer *b = ls->buff; + if (luaZ_bufflen(b) + 1 > luaZ_sizebuffer(b)) { + size_t newsize; + if (luaZ_sizebuffer(b) >= MAX_SIZET/2) + lexerror(ls, "lexical element too long", 0); + newsize = luaZ_sizebuffer(b) * 2; + luaZ_resizebuffer(ls->L, b, newsize); + } + b->buffer[luaZ_bufflen(b)++] = cast(char, c); +} + + +void luaX_init (lua_State *L) { + int i; + for (i=0; itsv.extra = cast_byte(i+1); /* reserved word */ + } +} + + +const char *luaX_token2str (LexState *ls, int token) { + if (token < FIRST_RESERVED) { /* single-byte symbols? */ + lua_assert(token == cast(unsigned char, token)); + return (lisprint(token)) ? luaO_pushfstring(ls->L, LUA_QL("%c"), token) : + luaO_pushfstring(ls->L, "char(%d)", token); + } + else { + const char *s = luaX_tokens[token - FIRST_RESERVED]; + if (token < TK_EOS) /* fixed format (symbols and reserved words)? */ + return luaO_pushfstring(ls->L, LUA_QS, s); + else /* names, strings, and numerals */ + return s; + } +} + + +static const char *txtToken (LexState *ls, int token) { + switch (token) { + case TK_NAME: + case TK_STRING: + case TK_NUMBER: + save(ls, '\0'); + return luaO_pushfstring(ls->L, LUA_QS, luaZ_buffer(ls->buff)); + default: + return luaX_token2str(ls, token); + } +} + + +static l_noret lexerror (LexState *ls, const char *msg, int token) { + char buff[LUA_IDSIZE]; + luaO_chunkid(buff, getstr(ls->source), LUA_IDSIZE); + msg = luaO_pushfstring(ls->L, "%s:%d: %s", buff, ls->linenumber, msg); + if (token) + luaO_pushfstring(ls->L, "%s near %s", msg, txtToken(ls, token)); + luaD_throw(ls->L, LUA_ERRSYNTAX); +} + + +l_noret luaX_syntaxerror (LexState *ls, const char *msg) { + lexerror(ls, msg, ls->t.token); +} + + +/* +** creates a new string and anchors it in function's table so that +** it will not be collected until the end of the function's compilation +** (by that time it should be anchored in function's prototype) +*/ +TString *luaX_newstring (LexState *ls, const char *str, size_t l) { + lua_State *L = ls->L; + TString *ts = luaS_newlstr(L, str, l); /* create new string */ + setsvalue2s(L, L->top++, ts); /* temporarily anchor it in stack */ + const TValue *o = luaH_get(ls->fs->h, L->top - 1); + if (ttisnil(o)) { /* not in use yet? (see 'addK') */ + /* boolean value does not need GC barrier; + table has no metatable, so it does not need to invalidate cache */ + TValue truev; + setbvalue(&truev, 1); + luaH_setupdate(L, ls->fs->h, L->top - 1, &truev, o); + luaC_checkGC(L); + } else { /* string already present */ + ts = rawtsvalue(keyfromval(o)); /* re-use value previously stored */ + } + L->top--; /* remove string from stack */ + return ts; +} + + +/* +** increment line number and skips newline sequence (any of +** \n, \r, \n\r, or \r\n) +*/ +static void inclinenumber (LexState *ls) { + int old = ls->current; + lua_assert(currIsNewline(ls)); + next(ls); /* skip `\n' or `\r' */ + if (currIsNewline(ls) && ls->current != old) + next(ls); /* skip `\n\r' or `\r\n' */ + if (++ls->linenumber >= MAX_INT) + lexerror(ls, "chunk has too many lines", 0); +} + + +void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, + int firstchar) { + ls->decpoint = '.'; + ls->L = L; + ls->current = firstchar; + ls->lookahead.token = TK_EOS; /* no look-ahead token */ + ls->z = z; + ls->fs = NULL; + ls->linenumber = 1; + ls->lastline = 1; + ls->source = source; + ls->envn = luaS_new(L, LUA_ENV); /* create env name */ + luaS_fix(ls->envn); /* never collect this name */ + luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ +} + + + +/* +** ======================================================= +** LEXICAL ANALYZER +** ======================================================= +*/ + + + +static int check_next (LexState *ls, const char *set) { + if (ls->current == '\0' || !strchr(set, ls->current)) + return 0; + save_and_next(ls); + return 1; +} + + +/* +** change all characters 'from' in buffer to 'to' +*/ +static void buffreplace (LexState *ls, char from, char to) { + size_t n = luaZ_bufflen(ls->buff); + char *p = luaZ_buffer(ls->buff); + while (n--) + if (p[n] == from) p[n] = to; +} + + +#if !defined(getlocaledecpoint) +#define getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif + + +#define buff2d(b,e) luaO_str2d(luaZ_buffer(b), luaZ_bufflen(b) - 1, e) + +/* +** in case of format error, try to change decimal point separator to +** the one defined in the current locale and check again +*/ +static void trydecpoint (LexState *ls, SemInfo *seminfo) { + char old = ls->decpoint; + ls->decpoint = getlocaledecpoint(); + buffreplace(ls, old, ls->decpoint); /* try new decimal separator */ + if (!buff2d(ls->buff, &seminfo->r)) { + /* format error with correct decimal point: no more options */ + buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */ + lexerror(ls, "malformed number", TK_NUMBER); + } +} + + +/* LUA_NUMBER */ +/* +** this function is quite liberal in what it accepts, as 'luaO_str2d' +** will reject ill-formed numerals. +*/ +static void read_numeral (LexState *ls, SemInfo *seminfo) { + const char *expo = "Ee"; + int first = ls->current; + lua_assert(lisdigit(ls->current)); + save_and_next(ls); + if (first == '0' && check_next(ls, "Xx")) /* hexadecimal? */ + expo = "Pp"; + for (;;) { + if (check_next(ls, expo)) /* exponent part? */ + check_next(ls, "+-"); /* optional exponent sign */ + if (lisxdigit(ls->current) || ls->current == '.') + save_and_next(ls); + else break; + } + save(ls, '\0'); + buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ + if (!buff2d(ls->buff, &seminfo->r)) /* format error? */ + trydecpoint(ls, seminfo); /* try to update decimal point separator */ +} + + +/* +** skip a sequence '[=*[' or ']=*]' and return its number of '='s or +** -1 if sequence is malformed +*/ +static int skip_sep (LexState *ls) { + int count = 0; + int s = ls->current; + lua_assert(s == '[' || s == ']'); + save_and_next(ls); + while (ls->current == '=') { + save_and_next(ls); + count++; + } + return (ls->current == s) ? count : (-count) - 1; +} + + +static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { + save_and_next(ls); /* skip 2nd `[' */ + if (currIsNewline(ls)) /* string starts with a newline? */ + inclinenumber(ls); /* skip it */ + for (;;) { + switch (ls->current) { + case EOZ: + lexerror(ls, (seminfo) ? "unfinished long string" : + "unfinished long comment", TK_EOS); + break; /* to avoid warnings */ + case ']': { + if (skip_sep(ls) == sep) { + save_and_next(ls); /* skip 2nd `]' */ + goto endloop; + } + break; + } + case '\n': case '\r': { + save(ls, '\n'); + inclinenumber(ls); + if (!seminfo) luaZ_resetbuffer(ls->buff); /* avoid wasting space */ + break; + } + default: { + if (seminfo) save_and_next(ls); + else next(ls); + } + } + } endloop: + if (seminfo) + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep), + luaZ_bufflen(ls->buff) - 2*(2 + sep)); +} + + +static void escerror (LexState *ls, int *c, int n, const char *msg) { + int i; + luaZ_resetbuffer(ls->buff); /* prepare error message */ + save(ls, '\\'); + for (i = 0; i < n && c[i] != EOZ; i++) + save(ls, c[i]); + lexerror(ls, msg, TK_STRING); +} + + +static int readhexaesc (LexState *ls) { + int c[3], i; /* keep input for error message */ + int r = 0; /* result accumulator */ + c[0] = 'x'; /* for error message */ + for (i = 1; i < 3; i++) { /* read two hexadecimal digits */ + c[i] = next(ls); + if (!lisxdigit(c[i])) + escerror(ls, c, i + 1, "hexadecimal digit expected"); + r = (r << 4) + luaO_hexavalue(c[i]); + } + return r; +} + + +static int readdecesc (LexState *ls) { + int c[3], i; + int r = 0; /* result accumulator */ + for (i = 0; i < 3 && lisdigit(ls->current); i++) { /* read up to 3 digits */ + c[i] = ls->current; + r = 10*r + c[i] - '0'; + next(ls); + } + if (r > UCHAR_MAX) + escerror(ls, c, i, "decimal escape too large"); + return r; +} + + +static void read_string (LexState *ls, int del, SemInfo *seminfo) { + save_and_next(ls); /* keep delimiter (for error messages) */ + while (ls->current != del) { + switch (ls->current) { + case EOZ: + lexerror(ls, "unfinished string", TK_EOS); + break; /* to avoid warnings */ + case '\n': + case '\r': + lexerror(ls, "unfinished string", TK_STRING); + break; /* to avoid warnings */ + case '\\': { /* escape sequences */ + int c; /* final character to be saved */ + next(ls); /* do not save the `\' */ + switch (ls->current) { + case 'a': c = '\a'; goto read_save; + case 'b': c = '\b'; goto read_save; + case 'f': c = '\f'; goto read_save; + case 'n': c = '\n'; goto read_save; + case 'r': c = '\r'; goto read_save; + case 't': c = '\t'; goto read_save; + case 'v': c = '\v'; goto read_save; + case 'x': c = readhexaesc(ls); goto read_save; + case '\n': case '\r': + inclinenumber(ls); c = '\n'; goto only_save; + case '\\': case '\"': case '\'': + c = ls->current; goto read_save; + case EOZ: goto no_save; /* will raise an error next loop */ + case 'z': { /* zap following span of spaces */ + next(ls); /* skip the 'z' */ + while (lisspace(ls->current)) { + if (currIsNewline(ls)) inclinenumber(ls); + else next(ls); + } + goto no_save; + } + default: { + if (!lisdigit(ls->current)) + escerror(ls, &ls->current, 1, "invalid escape sequence"); + /* digital escape \ddd */ + c = readdecesc(ls); + goto only_save; + } + } + read_save: next(ls); /* read next character */ + only_save: save(ls, c); /* save 'c' */ + no_save: break; + } + default: + save_and_next(ls); + } + } + save_and_next(ls); /* skip delimiter */ + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + 1, + luaZ_bufflen(ls->buff) - 2); +} + + +static int llex (LexState *ls, SemInfo *seminfo) { + luaZ_resetbuffer(ls->buff); + for (;;) { + switch (ls->current) { + case '\n': case '\r': { /* line breaks */ + inclinenumber(ls); + break; + } + case ' ': case '\f': case '\t': case '\v': { /* spaces */ + next(ls); + break; + } + case '-': { /* '-' or '--' (comment) */ + next(ls); + if (ls->current != '-') return '-'; + /* else is a comment */ + next(ls); + if (ls->current == '[') { /* long comment? */ + int sep = skip_sep(ls); + luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */ + if (sep >= 0) { + read_long_string(ls, NULL, sep); /* skip long comment */ + luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */ + break; + } + } + /* else short comment */ + while (!currIsNewline(ls) && ls->current != EOZ) + next(ls); /* skip until end of line (or end of file) */ + break; + } + case '[': { /* long string or simply '[' */ + int sep = skip_sep(ls); + if (sep >= 0) { + read_long_string(ls, seminfo, sep); + return TK_STRING; + } + else if (sep == -1) return '['; + else lexerror(ls, "invalid long string delimiter", TK_STRING); + } + case '=': { + next(ls); + if (ls->current != '=') return '='; + else { next(ls); return TK_EQ; } + } + case '<': { + next(ls); + if (ls->current != '=') return '<'; + else { next(ls); return TK_LE; } + } + case '>': { + next(ls); + if (ls->current != '=') return '>'; + else { next(ls); return TK_GE; } + } + case '~': { + next(ls); + if (ls->current != '=') return '~'; + else { next(ls); return TK_NE; } + } + case ':': { + next(ls); + if (ls->current != ':') return ':'; + else { next(ls); return TK_DBCOLON; } + } + case '"': case '\'': { /* short literal strings */ + read_string(ls, ls->current, seminfo); + return TK_STRING; + } + case '.': { /* '.', '..', '...', or number */ + save_and_next(ls); + if (check_next(ls, ".")) { + if (check_next(ls, ".")) + return TK_DOTS; /* '...' */ + else return TK_CONCAT; /* '..' */ + } + else if (!lisdigit(ls->current)) return '.'; + /* else go through */ + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + read_numeral(ls, seminfo); + return TK_NUMBER; + } + case EOZ: { + return TK_EOS; + } + default: { + if (lislalpha(ls->current)) { /* identifier or reserved word? */ + TString *ts; + do { + save_and_next(ls); + } while (lislalnum(ls->current)); + ts = luaX_newstring(ls, luaZ_buffer(ls->buff), + luaZ_bufflen(ls->buff)); + seminfo->ts = ts; + if (isreserved(ts)) /* reserved word? */ + return ts->tsv.extra - 1 + FIRST_RESERVED; + else { + return TK_NAME; + } + } + else { /* single-char tokens (+ - / ...) */ + int c = ls->current; + next(ls); + return c; + } + } + } + } +} + + +void luaX_next (LexState *ls) { + ls->lastline = ls->linenumber; + if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */ + ls->t = ls->lookahead; /* use this one */ + ls->lookahead.token = TK_EOS; /* and discharge it */ + } + else + ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */ +} + + +int luaX_lookahead (LexState *ls) { + lua_assert(ls->lookahead.token == TK_EOS); + ls->lookahead.token = llex(ls, &ls->lookahead.seminfo); + return ls->lookahead.token; +} + diff --git a/luprex/ext/eris-master/src/llex.h b/luprex/ext/eris-master/src/llex.h new file mode 100644 index 00000000..a4acdd30 --- /dev/null +++ b/luprex/ext/eris-master/src/llex.h @@ -0,0 +1,78 @@ +/* +** $Id: llex.h,v 1.72.1.1 2013/04/12 18:48:47 roberto Exp $ +** Lexical Analyzer +** See Copyright Notice in lua.h +*/ + +#ifndef llex_h +#define llex_h + +#include "lobject.h" +#include "lzio.h" + + +#define FIRST_RESERVED 257 + + + +/* +* WARNING: if you change the order of this enumeration, +* grep "ORDER RESERVED" +*/ +enum RESERVED { + /* terminal symbols denoted by reserved words */ + TK_AND = FIRST_RESERVED, TK_BREAK, + TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, + TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, + TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, + /* other terminal symbols */ + TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_DBCOLON, TK_EOS, + TK_NUMBER, TK_NAME, TK_STRING +}; + +/* number of reserved words */ +#define NUM_RESERVED (cast(int, TK_WHILE-FIRST_RESERVED+1)) + + +typedef union { + lua_Number r; + TString *ts; +} SemInfo; /* semantics information */ + + +typedef struct Token { + int token; + SemInfo seminfo; +} Token; + + +/* state of the lexer plus state of the parser when shared by all + functions */ +typedef struct LexState { + int current; /* current character (charint) */ + int linenumber; /* input line counter */ + int lastline; /* line of last token `consumed' */ + Token t; /* current token */ + Token lookahead; /* look ahead token */ + struct FuncState *fs; /* current function (parser) */ + struct lua_State *L; + ZIO *z; /* input stream */ + Mbuffer *buff; /* buffer for tokens */ + struct Dyndata *dyd; /* dynamic structures used by the parser */ + TString *source; /* current source name */ + TString *envn; /* environment variable name */ + char decpoint; /* locale decimal point */ +} LexState; + + +LUAI_FUNC void luaX_init (lua_State *L); +LUAI_FUNC void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, + TString *source, int firstchar); +LUAI_FUNC TString *luaX_newstring (LexState *ls, const char *str, size_t l); +LUAI_FUNC void luaX_next (LexState *ls); +LUAI_FUNC int luaX_lookahead (LexState *ls); +LUAI_FUNC l_noret luaX_syntaxerror (LexState *ls, const char *s); +LUAI_FUNC const char *luaX_token2str (LexState *ls, int token); + + +#endif diff --git a/luprex/ext/eris-master/src/llimits.h b/luprex/ext/eris-master/src/llimits.h new file mode 100644 index 00000000..152dd055 --- /dev/null +++ b/luprex/ext/eris-master/src/llimits.h @@ -0,0 +1,309 @@ +/* +** $Id: llimits.h,v 1.103.1.1 2013/04/12 18:48:47 roberto Exp $ +** Limits, basic types, and some other `installation-dependent' definitions +** See Copyright Notice in lua.h +*/ + +#ifndef llimits_h +#define llimits_h + + +#include +#include + + +#include "lua.h" + + +typedef unsigned LUA_INT32 lu_int32; + +typedef LUAI_UMEM lu_mem; + +typedef LUAI_MEM l_mem; + + + +/* chars used as small naturals (so that `char' is reserved for characters) */ +typedef unsigned char lu_byte; + + +#define MAX_SIZET ((size_t)(~(size_t)0)-2) + +#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)-2) + +#define MAX_LMEM ((l_mem) ((MAX_LUMEM >> 1) - 2)) + + +#define MAX_INT (INT_MAX-2) /* maximum value of an int (-2 for safety) */ + +/* +** conversion of pointer to integer +** this is for hashing only; there is no problem if the integer +** cannot hold the whole pointer value +*/ +#define IntPoint(p) ((unsigned int)(lu_mem)(p)) + + + +/* type to ensure maximum alignment */ +#if !defined(LUAI_USER_ALIGNMENT_T) +#define LUAI_USER_ALIGNMENT_T union { double u; void *s; long l; } +#endif + +typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; + + +/* result of a `usual argument conversion' over lua_Number */ +typedef LUAI_UACNUMBER l_uacNumber; + + +/* internal assertions for in-house debugging */ +#if defined(lua_assert) +#define check_exp(c,e) (lua_assert(c), (e)) +/* to avoid problems with conditions too long */ +#define lua_longassert(c) { if (!(c)) lua_assert(0); } +#else +#define lua_assert(c) ((void)0) +#define check_exp(c,e) (e) +#define lua_longassert(c) ((void)0) +#endif + +/* +** assertion for checking API calls +*/ +#if !defined(luai_apicheck) + +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(L,e) assert(e) +#else +#define luai_apicheck(L,e) lua_assert(e) +#endif + +#endif + +#define api_check(l,e,msg) luai_apicheck(l,(e) && msg) + + +#if !defined(UNUSED) +#define UNUSED(x) ((void)(x)) /* to avoid warnings */ +#endif + + +#define cast(t, exp) ((t)(exp)) + +#define cast_byte(i) cast(lu_byte, (i)) +#define cast_num(i) cast(lua_Number, (i)) +#define cast_int(i) cast(int, (i)) +#define cast_uchar(i) cast(unsigned char, (i)) + + +/* +** non-return type +*/ +#if defined(__GNUC__) +#define l_noret void __attribute__((noreturn)) +#elif defined(_MSC_VER) +#define l_noret void __declspec(noreturn) +#else +#define l_noret void +#endif + + + +/* +** maximum depth for nested C calls and syntactical nested non-terminals +** in a program. (Value must fit in an unsigned short int.) +*/ +#if !defined(LUAI_MAXCCALLS) +#define LUAI_MAXCCALLS 200 +#endif + +/* +** maximum number of upvalues in a closure (both C and Lua). (Value +** must fit in an unsigned char.) +*/ +#define MAXUPVAL UCHAR_MAX + + +/* +** type for virtual-machine instructions +** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) +*/ +typedef lu_int32 Instruction; + + + +/* maximum stack for a Lua function */ +#define MAXSTACK 250 + + + +/* minimum size for the string table (must be power of 2) */ +#if !defined(MINSTRTABSIZE) +#define MINSTRTABSIZE 32 +#endif + + +/* minimum size for string buffer */ +#if !defined(LUA_MINBUFFER) +#define LUA_MINBUFFER 32 +#endif + + +#if !defined(lua_lock) +#define lua_lock(L) ((void) 0) +#define lua_unlock(L) ((void) 0) +#endif + +#if !defined(luai_threadyield) +#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} +#endif + + +/* +** these macros allow user-specific actions on threads when you defined +** LUAI_EXTRASPACE and need to do something extra when a thread is +** created/deleted/resumed/yielded. +*/ +#if !defined(luai_userstateopen) +#define luai_userstateopen(L) ((void)L) +#endif + +#if !defined(luai_userstateclose) +#define luai_userstateclose(L) ((void)L) +#endif + +#if !defined(luai_userstatethread) +#define luai_userstatethread(L,L1) ((void)L) +#endif + +#if !defined(luai_userstatefree) +#define luai_userstatefree(L,L1) ((void)L) +#endif + +#if !defined(luai_userstateresume) +#define luai_userstateresume(L,n) ((void)L) +#endif + +#if !defined(luai_userstateyield) +#define luai_userstateyield(L,n) ((void)L) +#endif + +/* +** lua_number2int is a macro to convert lua_Number to int. +** lua_number2integer is a macro to convert lua_Number to lua_Integer. +** lua_number2unsigned is a macro to convert a lua_Number to a lua_Unsigned. +** lua_unsigned2number is a macro to convert a lua_Unsigned to a lua_Number. +** luai_hashnum is a macro to hash a lua_Number value into an integer. +** The hash must be deterministic and give reasonable values for +** both small and large values (outside the range of integers). +*/ + +#if defined(MS_ASMTRICK) || defined(LUA_MSASMTRICK) /* { */ +/* trick with Microsoft assembler for X86 */ + +#define lua_number2int(i,n) __asm {__asm fld n __asm fistp i} +#define lua_number2integer(i,n) lua_number2int(i, n) +#define lua_number2unsigned(i,n) \ + {__int64 l; __asm {__asm fld n __asm fistp l} i = (unsigned int)l;} + + +#elif defined(LUA_IEEE754TRICK) /* }{ */ +/* the next trick should work on any machine using IEEE754 with + a 32-bit int type */ + +union luai_Cast { double l_d; LUA_INT32 l_p[2]; }; + +#if !defined(LUA_IEEEENDIAN) /* { */ +#define LUAI_EXTRAIEEE \ + static const union luai_Cast ieeeendian = {-(33.0 + 6755399441055744.0)}; +#define LUA_IEEEENDIANLOC (ieeeendian.l_p[1] == 33) +#else +#define LUA_IEEEENDIANLOC LUA_IEEEENDIAN +#define LUAI_EXTRAIEEE /* empty */ +#endif /* } */ + +#define lua_number2int32(i,n,t) \ + { LUAI_EXTRAIEEE \ + volatile union luai_Cast u; u.l_d = (n) + 6755399441055744.0; \ + (i) = (t)u.l_p[LUA_IEEEENDIANLOC]; } + +#define luai_hashnum(i,n) \ + { volatile union luai_Cast u; u.l_d = (n) + 1.0; /* avoid -0 */ \ + (i) = u.l_p[0]; (i) += u.l_p[1]; } /* add double bits for his hash */ + +#define lua_number2int(i,n) lua_number2int32(i, n, int) +#define lua_number2unsigned(i,n) lua_number2int32(i, n, lua_Unsigned) + +/* the trick can be expanded to lua_Integer when it is a 32-bit value */ +#if defined(LUA_IEEELL) +#define lua_number2integer(i,n) lua_number2int32(i, n, lua_Integer) +#endif + +#endif /* } */ + + +/* the following definitions always work, but may be slow */ + +#if !defined(lua_number2int) +#define lua_number2int(i,n) ((i)=(int)(n)) +#endif + +#if !defined(lua_number2integer) +#define lua_number2integer(i,n) ((i)=(lua_Integer)(n)) +#endif + +#if !defined(lua_number2unsigned) /* { */ +/* the following definition assures proper modulo behavior */ +#if defined(LUA_NUMBER_DOUBLE) || defined(LUA_NUMBER_FLOAT) +#include +#define SUPUNSIGNED ((lua_Number)(~(lua_Unsigned)0) + 1) +#define lua_number2unsigned(i,n) \ + ((i)=(lua_Unsigned)((n) - floor((n)/SUPUNSIGNED)*SUPUNSIGNED)) +#else +#define lua_number2unsigned(i,n) ((i)=(lua_Unsigned)(n)) +#endif +#endif /* } */ + + +#if !defined(lua_unsigned2number) +/* on several machines, coercion from unsigned to double is slow, + so it may be worth to avoid */ +#define lua_unsigned2number(u) \ + (((u) <= (lua_Unsigned)INT_MAX) ? (lua_Number)(int)(u) : (lua_Number)(u)) +#endif + + + +#if defined(ltable_c) && !defined(luai_hashnum) + +#include +#include + +#define luai_hashnum(i,n) { int e; \ + n = l_mathop(frexp)(n, &e) * (lua_Number)(INT_MAX - DBL_MAX_EXP); \ + lua_number2int(i, n); i += e; } + +#endif + + + +/* +** macro to control inclusion of some hard tests on stack reallocation +*/ +#if !defined(HARDSTACKTESTS) +#define condmovestack(L) ((void)0) +#else +/* realloc stack keeping its size */ +#define condmovestack(L) luaD_reallocstack((L), (L)->stacksize) +#endif + +#if !defined(HARDMEMTESTS) +#define condchangemem(L) condmovestack(L) +#else +#define condchangemem(L) \ + ((void)(!(G(L)->gcrunning) || (luaC_fullgc(L, 0), 1))) +#endif + +#endif diff --git a/luprex/ext/eris-master/src/lmathlib.c b/luprex/ext/eris-master/src/lmathlib.c new file mode 100644 index 00000000..fe9fc542 --- /dev/null +++ b/luprex/ext/eris-master/src/lmathlib.c @@ -0,0 +1,279 @@ +/* +** $Id: lmathlib.c,v 1.83.1.1 2013/04/12 18:48:47 roberto Exp $ +** Standard mathematical library +** See Copyright Notice in lua.h +*/ + + +#include +#include + +#define lmathlib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#undef PI +#define PI ((lua_Number)(3.1415926535897932384626433832795)) +#define RADIANS_PER_DEGREE ((lua_Number)(PI/180.0)) + + + +static int math_abs (lua_State *L) { + lua_pushnumber(L, l_mathop(fabs)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sin (lua_State *L) { + lua_pushnumber(L, l_mathop(sin)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sinh (lua_State *L) { + lua_pushnumber(L, l_mathop(sinh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_cos (lua_State *L) { + lua_pushnumber(L, l_mathop(cos)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_cosh (lua_State *L) { + lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tan (lua_State *L) { + lua_pushnumber(L, l_mathop(tan)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tanh (lua_State *L) { + lua_pushnumber(L, l_mathop(tanh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_asin (lua_State *L) { + lua_pushnumber(L, l_mathop(asin)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_acos (lua_State *L) { + lua_pushnumber(L, l_mathop(acos)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_atan (lua_State *L) { + lua_pushnumber(L, l_mathop(atan)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_atan2 (lua_State *L) { + lua_pushnumber(L, l_mathop(atan2)(luaL_checknumber(L, 1), + luaL_checknumber(L, 2))); + return 1; +} + +static int math_ceil (lua_State *L) { + lua_pushnumber(L, l_mathop(ceil)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_floor (lua_State *L) { + lua_pushnumber(L, l_mathop(floor)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_fmod (lua_State *L) { + lua_pushnumber(L, l_mathop(fmod)(luaL_checknumber(L, 1), + luaL_checknumber(L, 2))); + return 1; +} + +static int math_modf (lua_State *L) { + lua_Number ip; + lua_Number fp = l_mathop(modf)(luaL_checknumber(L, 1), &ip); + lua_pushnumber(L, ip); + lua_pushnumber(L, fp); + return 2; +} + +static int math_sqrt (lua_State *L) { + lua_pushnumber(L, l_mathop(sqrt)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_pow (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + lua_Number y = luaL_checknumber(L, 2); + lua_pushnumber(L, l_mathop(pow)(x, y)); + return 1; +} + +static int math_log (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + lua_Number res; + if (lua_isnoneornil(L, 2)) + res = l_mathop(log)(x); + else { + lua_Number base = luaL_checknumber(L, 2); + if (base == (lua_Number)10.0) res = l_mathop(log10)(x); + else res = l_mathop(log)(x)/l_mathop(log)(base); + } + lua_pushnumber(L, res); + return 1; +} + +#if defined(LUA_COMPAT_LOG10) +static int math_log10 (lua_State *L) { + lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1))); + return 1; +} +#endif + +static int math_exp (lua_State *L) { + lua_pushnumber(L, l_mathop(exp)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_deg (lua_State *L) { + lua_pushnumber(L, luaL_checknumber(L, 1)/RADIANS_PER_DEGREE); + return 1; +} + +static int math_rad (lua_State *L) { + lua_pushnumber(L, luaL_checknumber(L, 1)*RADIANS_PER_DEGREE); + return 1; +} + +static int math_frexp (lua_State *L) { + int e; + lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); + lua_pushinteger(L, e); + return 2; +} + +static int math_ldexp (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + int ep = luaL_checkint(L, 2); + lua_pushnumber(L, l_mathop(ldexp)(x, ep)); + return 1; +} + + + +static int math_min (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + lua_Number dmin = luaL_checknumber(L, 1); + int i; + for (i=2; i<=n; i++) { + lua_Number d = luaL_checknumber(L, i); + if (d < dmin) + dmin = d; + } + lua_pushnumber(L, dmin); + return 1; +} + + +static int math_max (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + lua_Number dmax = luaL_checknumber(L, 1); + int i; + for (i=2; i<=n; i++) { + lua_Number d = luaL_checknumber(L, i); + if (d > dmax) + dmax = d; + } + lua_pushnumber(L, dmax); + return 1; +} + + +static int math_random (lua_State *L) { + /* the `%' avoids the (rare) case of r==1, and is needed also because on + some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */ + lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX; + switch (lua_gettop(L)) { /* check number of arguments */ + case 0: { /* no arguments */ + lua_pushnumber(L, r); /* Number between 0 and 1 */ + break; + } + case 1: { /* only upper limit */ + lua_Number u = luaL_checknumber(L, 1); + luaL_argcheck(L, (lua_Number)1.0 <= u, 1, "interval is empty"); + lua_pushnumber(L, l_mathop(floor)(r*u) + (lua_Number)(1.0)); /* [1, u] */ + break; + } + case 2: { /* lower and upper limits */ + lua_Number l = luaL_checknumber(L, 1); + lua_Number u = luaL_checknumber(L, 2); + luaL_argcheck(L, l <= u, 2, "interval is empty"); + lua_pushnumber(L, l_mathop(floor)(r*(u-l+1)) + l); /* [l, u] */ + break; + } + default: return luaL_error(L, "wrong number of arguments"); + } + return 1; +} + + +static int math_randomseed (lua_State *L) { + srand(luaL_checkunsigned(L, 1)); + (void)rand(); /* discard first value to avoid undesirable correlations */ + return 0; +} + + +static const luaL_Reg mathlib[] = { + {"abs", math_abs}, + {"acos", math_acos}, + {"asin", math_asin}, + {"atan2", math_atan2}, + {"atan", math_atan}, + {"ceil", math_ceil}, + {"cosh", math_cosh}, + {"cos", math_cos}, + {"deg", math_deg}, + {"exp", math_exp}, + {"floor", math_floor}, + {"fmod", math_fmod}, + {"frexp", math_frexp}, + {"ldexp", math_ldexp}, +#if defined(LUA_COMPAT_LOG10) + {"log10", math_log10}, +#endif + {"log", math_log}, + {"max", math_max}, + {"min", math_min}, + {"modf", math_modf}, + {"pow", math_pow}, + {"rad", math_rad}, + {"random", math_random}, + {"randomseed", math_randomseed}, + {"sinh", math_sinh}, + {"sin", math_sin}, + {"sqrt", math_sqrt}, + {"tanh", math_tanh}, + {"tan", math_tan}, + {NULL, NULL} +}; + + +/* +** Open math library +*/ +LUAMOD_API int luaopen_math (lua_State *L) { + luaL_newlib(L, mathlib); + lua_pushnumber(L, PI); + lua_setfield(L, -2, "pi"); + lua_pushnumber(L, HUGE_VAL); + lua_setfield(L, -2, "huge"); + return 1; +} + diff --git a/luprex/ext/eris-master/src/lmem.c b/luprex/ext/eris-master/src/lmem.c new file mode 100644 index 00000000..ee343e3e --- /dev/null +++ b/luprex/ext/eris-master/src/lmem.c @@ -0,0 +1,99 @@ +/* +** $Id: lmem.c,v 1.84.1.1 2013/04/12 18:48:47 roberto Exp $ +** Interface to Memory Manager +** See Copyright Notice in lua.h +*/ + + +#include + +#define lmem_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" + + + +/* +** About the realloc function: +** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize); +** (`osize' is the old size, `nsize' is the new size) +** +** * frealloc(ud, NULL, x, s) creates a new block of size `s' (no +** matter 'x'). +** +** * frealloc(ud, p, x, 0) frees the block `p' +** (in this specific case, frealloc must return NULL); +** particularly, frealloc(ud, NULL, 0, 0) does nothing +** (which is equivalent to free(NULL) in ANSI C) +** +** frealloc returns NULL if it cannot create or reallocate the area +** (any reallocation to an equal or smaller size cannot fail!) +*/ + + + +#define MINSIZEARRAY 4 + + +void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elems, + int limit, const char *what) { + void *newblock; + int newsize; + if (*size >= limit/2) { /* cannot double it? */ + if (*size >= limit) /* cannot grow even a little? */ + luaG_runerror(L, "too many %s (limit is %d)", what, limit); + newsize = limit; /* still have at least one free place */ + } + else { + newsize = (*size)*2; + if (newsize < MINSIZEARRAY) + newsize = MINSIZEARRAY; /* minimum size */ + } + newblock = luaM_reallocv(L, block, *size, newsize, size_elems); + *size = newsize; /* update only when everything else is OK */ + return newblock; +} + + +l_noret luaM_toobig (lua_State *L) { + luaG_runerror(L, "memory allocation error: block too big"); +} + + + +/* +** generic allocation routine. +*/ +void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { + void *newblock; + global_State *g = G(L); + size_t realosize = (block) ? osize : 0; + lua_assert((realosize == 0) == (block == NULL)); +#if defined(HARDMEMTESTS) + if (nsize > realosize && g->gcrunning) + luaC_fullgc(L, 1); /* force a GC whenever possible */ +#endif + newblock = (*g->frealloc)(g->ud, block, osize, nsize); + if (newblock == NULL && nsize > 0) { + api_check(L, nsize > realosize, + "realloc cannot fail when shrinking a block"); + if (g->gcrunning) { + luaC_fullgc(L, 1); /* try to free some memory... */ + newblock = (*g->frealloc)(g->ud, block, osize, nsize); /* try again */ + } + if (newblock == NULL) + luaD_throw(L, LUA_ERRMEM); + } + lua_assert((nsize == 0) == (newblock == NULL)); + g->GCdebt = (g->GCdebt + nsize) - realosize; + return newblock; +} + diff --git a/luprex/ext/eris-master/src/lmem.h b/luprex/ext/eris-master/src/lmem.h new file mode 100644 index 00000000..bd4f4e07 --- /dev/null +++ b/luprex/ext/eris-master/src/lmem.h @@ -0,0 +1,57 @@ +/* +** $Id: lmem.h,v 1.40.1.1 2013/04/12 18:48:47 roberto Exp $ +** Interface to Memory Manager +** See Copyright Notice in lua.h +*/ + +#ifndef lmem_h +#define lmem_h + + +#include + +#include "llimits.h" +#include "lua.h" + + +/* +** This macro avoids the runtime division MAX_SIZET/(e), as 'e' is +** always constant. +** The macro is somewhat complex to avoid warnings: +** +1 avoids warnings of "comparison has constant result"; +** cast to 'void' avoids warnings of "value unused". +*/ +#define luaM_reallocv(L,b,on,n,e) \ + (cast(void, \ + (cast(size_t, (n)+1) > MAX_SIZET/(e)) ? (luaM_toobig(L), 0) : 0), \ + luaM_realloc_(L, (b), (on)*(e), (n)*(e))) + +#define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0) +#define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0) +#define luaM_freearray(L, b, n) luaM_reallocv(L, (b), n, 0, sizeof((b)[0])) + +#define luaM_malloc(L,s) luaM_realloc_(L, NULL, 0, (s)) +#define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t))) +#define luaM_newvector(L,n,t) \ + cast(t *, luaM_reallocv(L, NULL, 0, n, sizeof(t))) + +#define luaM_newobject(L,tag,s) luaM_realloc_(L, NULL, tag, (s)) + +#define luaM_growvector(L,v,nelems,size,t,limit,e) \ + if ((nelems)+1 > (size)) \ + ((v)=cast(t *, luaM_growaux_(L,v,&(size),sizeof(t),limit,e))) + +#define luaM_reallocvector(L, v,oldn,n,t) \ + ((v)=cast(t *, luaM_reallocv(L, v, oldn, n, sizeof(t)))) + +LUAI_FUNC l_noret luaM_toobig (lua_State *L); + +/* not to be called directly */ +LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize, + size_t size); +LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int *size, + size_t size_elem, int limit, + const char *what); + +#endif + diff --git a/luprex/ext/eris-master/src/loadlib.c b/luprex/ext/eris-master/src/loadlib.c new file mode 100644 index 00000000..e9305ec9 --- /dev/null +++ b/luprex/ext/eris-master/src/loadlib.c @@ -0,0 +1,759 @@ +/* +** $Id: loadlib.c,v 1.111.1.1 2013/04/12 18:48:47 roberto Exp $ +** Dynamic library loader for Lua +** See Copyright Notice in lua.h +** +** This module contains an implementation of loadlib for Unix systems +** that have dlfcn, an implementation for Windows, and a stub for other +** systems. +*/ + + +/* +** if needed, includes windows header before everything else +*/ +#if defined(_WIN32) +#include +#endif + + +#include +#include + + +#define loadlib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** LUA_PATH and LUA_CPATH are the names of the environment +** variables that Lua check to set its paths. +*/ +#if !defined(LUA_PATH) +#define LUA_PATH "LUA_PATH" +#endif + +#if !defined(LUA_CPATH) +#define LUA_CPATH "LUA_CPATH" +#endif + +#define LUA_PATHSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR + +#define LUA_PATHVERSION LUA_PATH LUA_PATHSUFFIX +#define LUA_CPATHVERSION LUA_CPATH LUA_PATHSUFFIX + +/* +** LUA_PATH_SEP is the character that separates templates in a path. +** LUA_PATH_MARK is the string that marks the substitution points in a +** template. +** LUA_EXEC_DIR in a Windows path is replaced by the executable's +** directory. +** LUA_IGMARK is a mark to ignore all before it when building the +** luaopen_ function name. +*/ +#if !defined (LUA_PATH_SEP) +#define LUA_PATH_SEP ";" +#endif +#if !defined (LUA_PATH_MARK) +#define LUA_PATH_MARK "?" +#endif +#if !defined (LUA_EXEC_DIR) +#define LUA_EXEC_DIR "!" +#endif +#if !defined (LUA_IGMARK) +#define LUA_IGMARK "-" +#endif + + +/* +** LUA_CSUBSEP is the character that replaces dots in submodule names +** when searching for a C loader. +** LUA_LSUBSEP is the character that replaces dots in submodule names +** when searching for a Lua loader. +*/ +#if !defined(LUA_CSUBSEP) +#define LUA_CSUBSEP LUA_DIRSEP +#endif + +#if !defined(LUA_LSUBSEP) +#define LUA_LSUBSEP LUA_DIRSEP +#endif + + +/* prefix for open functions in C libraries */ +#define LUA_POF "luaopen_" + +/* separator for open functions in C libraries */ +#define LUA_OFSEP "_" + + +/* table (in the registry) that keeps handles for all loaded C libraries */ +#define CLIBS "_CLIBS" + +#define LIB_FAIL "open" + + +/* error codes for ll_loadfunc */ +#define ERRLIB 1 +#define ERRFUNC 2 + +#define setprogdir(L) ((void)0) + + +/* +** system-dependent functions +*/ +static void ll_unloadlib (void *lib); +static void *ll_load (lua_State *L, const char *path, int seeglb); +static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym); + + + +#if defined(LUA_USE_DLOPEN) +/* +** {======================================================================== +** This is an implementation of loadlib based on the dlfcn interface. +** The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD, +** NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least +** as an emulation layer on top of native functions. +** ========================================================================= +*/ + +#include + +static void ll_unloadlib (void *lib) { + dlclose(lib); +} + + +static void *ll_load (lua_State *L, const char *path, int seeglb) { + void *lib = dlopen(path, RTLD_NOW | (seeglb ? RTLD_GLOBAL : RTLD_LOCAL)); + if (lib == NULL) lua_pushstring(L, dlerror()); + return lib; +} + + +static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = (lua_CFunction)dlsym(lib, sym); + if (f == NULL) lua_pushstring(L, dlerror()); + return f; +} + +/* }====================================================== */ + + + +#elif defined(LUA_DL_DLL) +/* +** {====================================================================== +** This is an implementation of loadlib for Windows using native functions. +** ======================================================================= +*/ + +#undef setprogdir + +/* +** optional flags for LoadLibraryEx +*/ +#if !defined(LUA_LLE_FLAGS) +#define LUA_LLE_FLAGS 0 +#endif + + +static void setprogdir (lua_State *L) { + char buff[MAX_PATH + 1]; + char *lb; + DWORD nsize = sizeof(buff)/sizeof(char); + DWORD n = GetModuleFileNameA(NULL, buff, nsize); + if (n == 0 || n == nsize || (lb = strrchr(buff, '\\')) == NULL) + luaL_error(L, "unable to get ModuleFileName"); + else { + *lb = '\0'; + luaL_gsub(L, lua_tostring(L, -1), LUA_EXEC_DIR, buff); + lua_remove(L, -2); /* remove original string */ + } +} + + +static void pusherror (lua_State *L) { + int error = GetLastError(); + char buffer[128]; + if (FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, error, 0, buffer, sizeof(buffer)/sizeof(char), NULL)) + lua_pushstring(L, buffer); + else + lua_pushfstring(L, "system error %d\n", error); +} + +static void ll_unloadlib (void *lib) { + FreeLibrary((HMODULE)lib); +} + + +static void *ll_load (lua_State *L, const char *path, int seeglb) { + HMODULE lib = LoadLibraryExA(path, NULL, LUA_LLE_FLAGS); + (void)(seeglb); /* not used: symbols are 'global' by default */ + if (lib == NULL) pusherror(L); + return lib; +} + + +static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = (lua_CFunction)GetProcAddress((HMODULE)lib, sym); + if (f == NULL) pusherror(L); + return f; +} + +/* }====================================================== */ + + +#else +/* +** {====================================================== +** Fallback for other systems +** ======================================================= +*/ + +#undef LIB_FAIL +#define LIB_FAIL "absent" + + +#define DLMSG "dynamic libraries not enabled; check your Lua installation" + + +static void ll_unloadlib (void *lib) { + (void)(lib); /* not used */ +} + + +static void *ll_load (lua_State *L, const char *path, int seeglb) { + (void)(path); (void)(seeglb); /* not used */ + lua_pushliteral(L, DLMSG); + return NULL; +} + + +static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { + (void)(lib); (void)(sym); /* not used */ + lua_pushliteral(L, DLMSG); + return NULL; +} + +/* }====================================================== */ +#endif + + +static void *ll_checkclib (lua_State *L, const char *path) { + void *plib; + lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); + lua_getfield(L, -1, path); + plib = lua_touserdata(L, -1); /* plib = CLIBS[path] */ + lua_pop(L, 2); /* pop CLIBS table and 'plib' */ + return plib; +} + + +static void ll_addtoclib (lua_State *L, const char *path, void *plib) { + lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); + lua_pushlightuserdata(L, plib); + lua_pushvalue(L, -1); + lua_setfield(L, -3, path); /* CLIBS[path] = plib */ + lua_rawseti(L, -2, luaL_len(L, -2) + 1); /* CLIBS[#CLIBS + 1] = plib */ + lua_pop(L, 1); /* pop CLIBS table */ +} + + +/* +** __gc tag method for CLIBS table: calls 'll_unloadlib' for all lib +** handles in list CLIBS +*/ +static int gctm (lua_State *L) { + int n = luaL_len(L, 1); + for (; n >= 1; n--) { /* for each handle, in reverse order */ + lua_rawgeti(L, 1, n); /* get handle CLIBS[n] */ + ll_unloadlib(lua_touserdata(L, -1)); + lua_pop(L, 1); /* pop handle */ + } + return 0; +} + + +static int ll_loadfunc (lua_State *L, const char *path, const char *sym) { + void *reg = ll_checkclib(L, path); /* check loaded C libraries */ + if (reg == NULL) { /* must load library? */ + reg = ll_load(L, path, *sym == '*'); + if (reg == NULL) return ERRLIB; /* unable to load library */ + ll_addtoclib(L, path, reg); + } + if (*sym == '*') { /* loading only library (no function)? */ + lua_pushboolean(L, 1); /* return 'true' */ + return 0; /* no errors */ + } + else { + lua_CFunction f = ll_sym(L, reg, sym); + if (f == NULL) + return ERRFUNC; /* unable to find function */ + lua_pushcfunction(L, f); /* else create new function */ + return 0; /* no errors */ + } +} + + +static int ll_loadlib (lua_State *L) { + const char *path = luaL_checkstring(L, 1); + const char *init = luaL_checkstring(L, 2); + int stat = ll_loadfunc(L, path, init); + if (stat == 0) /* no errors? */ + return 1; /* return the loaded function */ + else { /* error; error message is on stack top */ + lua_pushnil(L); + lua_insert(L, -2); + lua_pushstring(L, (stat == ERRLIB) ? LIB_FAIL : "init"); + return 3; /* return nil, error message, and where */ + } +} + + + +/* +** {====================================================== +** 'require' function +** ======================================================= +*/ + + +static int readable (const char *filename) { + FILE *f = fopen(filename, "r"); /* try to open file */ + if (f == NULL) return 0; /* open failed */ + fclose(f); + return 1; +} + + +static const char *pushnexttemplate (lua_State *L, const char *path) { + const char *l; + while (*path == *LUA_PATH_SEP) path++; /* skip separators */ + if (*path == '\0') return NULL; /* no more templates */ + l = strchr(path, *LUA_PATH_SEP); /* find next separator */ + if (l == NULL) l = path + strlen(path); + lua_pushlstring(L, path, l - path); /* template */ + return l; +} + + +static const char *searchpath (lua_State *L, const char *name, + const char *path, + const char *sep, + const char *dirsep) { + luaL_Buffer msg; /* to build error message */ + luaL_buffinit(L, &msg); + if (*sep != '\0') /* non-empty separator? */ + name = luaL_gsub(L, name, sep, dirsep); /* replace it by 'dirsep' */ + while ((path = pushnexttemplate(L, path)) != NULL) { + const char *filename = luaL_gsub(L, lua_tostring(L, -1), + LUA_PATH_MARK, name); + lua_remove(L, -2); /* remove path template */ + if (readable(filename)) /* does file exist and is readable? */ + return filename; /* return that file name */ + lua_pushfstring(L, "\n\tno file " LUA_QS, filename); + lua_remove(L, -2); /* remove file name */ + luaL_addvalue(&msg); /* concatenate error msg. entry */ + } + luaL_pushresult(&msg); /* create error message */ + return NULL; /* not found */ +} + + +static int ll_searchpath (lua_State *L) { + const char *f = searchpath(L, luaL_checkstring(L, 1), + luaL_checkstring(L, 2), + luaL_optstring(L, 3, "."), + luaL_optstring(L, 4, LUA_DIRSEP)); + if (f != NULL) return 1; + else { /* error message is on top of the stack */ + lua_pushnil(L); + lua_insert(L, -2); + return 2; /* return nil + error message */ + } +} + + +static const char *findfile (lua_State *L, const char *name, + const char *pname, + const char *dirsep) { + const char *path; + lua_getfield(L, lua_upvalueindex(1), pname); + path = lua_tostring(L, -1); + if (path == NULL) + luaL_error(L, LUA_QL("package.%s") " must be a string", pname); + return searchpath(L, name, path, ".", dirsep); +} + + +static int checkload (lua_State *L, int stat, const char *filename) { + if (stat) { /* module loaded successfully? */ + lua_pushstring(L, filename); /* will be 2nd argument to module */ + return 2; /* return open function and file name */ + } + else + return luaL_error(L, "error loading module " LUA_QS + " from file " LUA_QS ":\n\t%s", + lua_tostring(L, 1), filename, lua_tostring(L, -1)); +} + + +static int searcher_Lua (lua_State *L) { + const char *filename; + const char *name = luaL_checkstring(L, 1); + filename = findfile(L, name, "path", LUA_LSUBSEP); + if (filename == NULL) return 1; /* module not found in this path */ + return checkload(L, (luaL_loadfile(L, filename) == LUA_OK), filename); +} + + +static int loadfunc (lua_State *L, const char *filename, const char *modname) { + const char *funcname; + const char *mark; + modname = luaL_gsub(L, modname, ".", LUA_OFSEP); + mark = strchr(modname, *LUA_IGMARK); + if (mark) { + int stat; + funcname = lua_pushlstring(L, modname, mark - modname); + funcname = lua_pushfstring(L, LUA_POF"%s", funcname); + stat = ll_loadfunc(L, filename, funcname); + if (stat != ERRFUNC) return stat; + modname = mark + 1; /* else go ahead and try old-style name */ + } + funcname = lua_pushfstring(L, LUA_POF"%s", modname); + return ll_loadfunc(L, filename, funcname); +} + + +static int searcher_C (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + const char *filename = findfile(L, name, "cpath", LUA_CSUBSEP); + if (filename == NULL) return 1; /* module not found in this path */ + return checkload(L, (loadfunc(L, filename, name) == 0), filename); +} + + +static int searcher_Croot (lua_State *L) { + const char *filename; + const char *name = luaL_checkstring(L, 1); + const char *p = strchr(name, '.'); + int stat; + if (p == NULL) return 0; /* is root */ + lua_pushlstring(L, name, p - name); + filename = findfile(L, lua_tostring(L, -1), "cpath", LUA_CSUBSEP); + if (filename == NULL) return 1; /* root not found */ + if ((stat = loadfunc(L, filename, name)) != 0) { + if (stat != ERRFUNC) + return checkload(L, 0, filename); /* real error */ + else { /* open function not found */ + lua_pushfstring(L, "\n\tno module " LUA_QS " in file " LUA_QS, + name, filename); + return 1; + } + } + lua_pushstring(L, filename); /* will be 2nd argument to module */ + return 2; +} + + +static int searcher_preload (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + lua_getfield(L, LUA_REGISTRYINDEX, "_PRELOAD"); + lua_getfield(L, -1, name); + if (lua_isnil(L, -1)) /* not found? */ + lua_pushfstring(L, "\n\tno field package.preload['%s']", name); + return 1; +} + + +static void findloader (lua_State *L, const char *name) { + int i; + luaL_Buffer msg; /* to build error message */ + luaL_buffinit(L, &msg); + lua_getfield(L, lua_upvalueindex(1), "searchers"); /* will be at index 3 */ + if (!lua_istable(L, 3)) + luaL_error(L, LUA_QL("package.searchers") " must be a table"); + /* iterate over available searchers to find a loader */ + for (i = 1; ; i++) { + lua_rawgeti(L, 3, i); /* get a searcher */ + if (lua_isnil(L, -1)) { /* no more searchers? */ + lua_pop(L, 1); /* remove nil */ + luaL_pushresult(&msg); /* create error message */ + luaL_error(L, "module " LUA_QS " not found:%s", + name, lua_tostring(L, -1)); + } + lua_pushstring(L, name); + lua_call(L, 1, 2); /* call it */ + if (lua_isfunction(L, -2)) /* did it find a loader? */ + return; /* module loader found */ + else if (lua_isstring(L, -2)) { /* searcher returned error message? */ + lua_pop(L, 1); /* remove extra return */ + luaL_addvalue(&msg); /* concatenate error message */ + } + else + lua_pop(L, 2); /* remove both returns */ + } +} + + +static int ll_require (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + lua_settop(L, 1); /* _LOADED table will be at index 2 */ + lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); + lua_getfield(L, 2, name); /* _LOADED[name] */ + if (lua_toboolean(L, -1)) /* is it there? */ + return 1; /* package is already loaded */ + /* else must load package */ + lua_pop(L, 1); /* remove 'getfield' result */ + findloader(L, name); + lua_pushstring(L, name); /* pass name as argument to module loader */ + lua_insert(L, -2); /* name is 1st argument (before search data) */ + lua_call(L, 2, 1); /* run loader to load module */ + if (!lua_isnil(L, -1)) /* non-nil return? */ + lua_setfield(L, 2, name); /* _LOADED[name] = returned value */ + lua_getfield(L, 2, name); + if (lua_isnil(L, -1)) { /* module did not set a value? */ + lua_pushboolean(L, 1); /* use true as result */ + lua_pushvalue(L, -1); /* extra copy to be returned */ + lua_setfield(L, 2, name); /* _LOADED[name] = true */ + } + return 1; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** 'module' function +** ======================================================= +*/ +#if defined(LUA_COMPAT_MODULE) + +/* +** changes the environment variable of calling function +*/ +static void set_env (lua_State *L) { + lua_Debug ar; + if (lua_getstack(L, 1, &ar) == 0 || + lua_getinfo(L, "f", &ar) == 0 || /* get calling function */ + lua_iscfunction(L, -1)) + luaL_error(L, LUA_QL("module") " not called from a Lua function"); + lua_pushvalue(L, -2); /* copy new environment table to top */ + lua_setupvalue(L, -2, 1); + lua_pop(L, 1); /* remove function */ +} + + +static void dooptions (lua_State *L, int n) { + int i; + for (i = 2; i <= n; i++) { + if (lua_isfunction(L, i)) { /* avoid 'calling' extra info. */ + lua_pushvalue(L, i); /* get option (a function) */ + lua_pushvalue(L, -2); /* module */ + lua_call(L, 1, 0); + } + } +} + + +static void modinit (lua_State *L, const char *modname) { + const char *dot; + lua_pushvalue(L, -1); + lua_setfield(L, -2, "_M"); /* module._M = module */ + lua_pushstring(L, modname); + lua_setfield(L, -2, "_NAME"); + dot = strrchr(modname, '.'); /* look for last dot in module name */ + if (dot == NULL) dot = modname; + else dot++; + /* set _PACKAGE as package name (full module name minus last part) */ + lua_pushlstring(L, modname, dot - modname); + lua_setfield(L, -2, "_PACKAGE"); +} + + +static int ll_module (lua_State *L) { + const char *modname = luaL_checkstring(L, 1); + int lastarg = lua_gettop(L); /* last parameter */ + luaL_pushmodule(L, modname, 1); /* get/create module table */ + /* check whether table already has a _NAME field */ + lua_getfield(L, -1, "_NAME"); + if (!lua_isnil(L, -1)) /* is table an initialized module? */ + lua_pop(L, 1); + else { /* no; initialize it */ + lua_pop(L, 1); + modinit(L, modname); + } + lua_pushvalue(L, -1); + set_env(L); + dooptions(L, lastarg); + return 1; +} + + +static int ll_seeall (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + if (!lua_getmetatable(L, 1)) { + lua_createtable(L, 0, 1); /* create new metatable */ + lua_pushvalue(L, -1); + lua_setmetatable(L, 1); + } + lua_pushglobaltable(L); + lua_setfield(L, -2, "__index"); /* mt.__index = _G */ + return 0; +} + +#endif +/* }====================================================== */ + + + +/* auxiliary mark (for internal use) */ +#define AUXMARK "\1" + + +/* +** return registry.LUA_NOENV as a boolean +*/ +static int noenv (lua_State *L) { + int b; + lua_getfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); + b = lua_toboolean(L, -1); + lua_pop(L, 1); /* remove value */ + return b; +} + + +static void setpath (lua_State *L, const char *fieldname, const char *envname1, + const char *envname2, const char *def) { + const char *path = getenv(envname1); + if (path == NULL) /* no environment variable? */ + path = getenv(envname2); /* try alternative name */ + if (path == NULL || noenv(L)) /* no environment variable? */ + lua_pushstring(L, def); /* use default */ + else { + /* replace ";;" by ";AUXMARK;" and then AUXMARK by default path */ + path = luaL_gsub(L, path, LUA_PATH_SEP LUA_PATH_SEP, + LUA_PATH_SEP AUXMARK LUA_PATH_SEP); + luaL_gsub(L, path, AUXMARK, def); + lua_remove(L, -2); + } + setprogdir(L); + lua_setfield(L, -2, fieldname); +} + + +static const luaL_Reg pk_funcs[] = { + {"loadlib", ll_loadlib}, + {"searchpath", ll_searchpath}, +#if defined(LUA_COMPAT_MODULE) + {"seeall", ll_seeall}, +#endif + {NULL, NULL} +}; + + +static const luaL_Reg ll_funcs[] = { +#if defined(LUA_COMPAT_MODULE) + {"module", ll_module}, +#endif + {"require", ll_require}, + {NULL, NULL} +}; + + +static void createsearcherstable (lua_State *L) { + static const lua_CFunction searchers[] = + {searcher_preload, searcher_Lua, searcher_C, searcher_Croot, NULL}; + int i; + /* create 'searchers' table */ + lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0); + /* fill it with pre-defined searchers */ + for (i=0; searchers[i] != NULL; i++) { + lua_pushvalue(L, -2); /* set 'package' as upvalue for all searchers */ + lua_pushcclosure(L, searchers[i], 1); + lua_rawseti(L, -2, i+1); + } +} + + +LUAMOD_API int luaopen_package (lua_State *L) { + /* create table CLIBS to keep track of loaded C libraries */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); + lua_createtable(L, 0, 1); /* metatable for CLIBS */ + lua_pushcfunction(L, gctm); + lua_setfield(L, -2, "__gc"); /* set finalizer for CLIBS table */ + lua_setmetatable(L, -2); + /* create `package' table */ + luaL_newlib(L, pk_funcs); + createsearcherstable(L); +#if defined(LUA_COMPAT_LOADERS) + lua_pushvalue(L, -1); /* make a copy of 'searchers' table */ + lua_setfield(L, -3, "loaders"); /* put it in field `loaders' */ +#endif + lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */ + /* set field 'path' */ + setpath(L, "path", LUA_PATHVERSION, LUA_PATH, LUA_PATH_DEFAULT); + /* set field 'cpath' */ + setpath(L, "cpath", LUA_CPATHVERSION, LUA_CPATH, LUA_CPATH_DEFAULT); + /* store config information */ + lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n" + LUA_EXEC_DIR "\n" LUA_IGMARK "\n"); + lua_setfield(L, -2, "config"); + /* set field `loaded' */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); + lua_setfield(L, -2, "loaded"); + /* set field `preload' */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); + lua_setfield(L, -2, "preload"); + lua_pushglobaltable(L); + lua_pushvalue(L, -2); /* set 'package' as upvalue for next lib */ + luaL_setfuncs(L, ll_funcs, 1); /* open lib into global table */ + lua_pop(L, 1); /* pop global table */ + return 1; /* return 'package' table */ +} + + +void eris_permloadlib(lua_State *L, int forUnpersist) { + static const lua_CFunction searchers[] = { + searcher_preload, + searcher_Lua, + searcher_C, + searcher_Croot, + NULL + }; + static const char *const searchernames[] = { + "__eris.loadlib_searcher_preload", + "__eris.loadlib_searcher_Lua", + "__eris.loadlib_searcher_C", + "__eris.loadlib_searcher_Croot", + NULL + }; + int i; + + luaL_checktype(L, -1, LUA_TTABLE); + luaL_checkstack(L, 2, NULL); + + for (i = 0; searchers[i]; ++i) { + if (forUnpersist) { + lua_pushstring(L, searchernames[i]); + lua_pushcfunction(L, searchers[i]); + } + else { + lua_pushcfunction(L, searchers[i]); + lua_pushstring(L, searchernames[i]); + } + lua_rawset(L, -3); + } +} + diff --git a/luprex/ext/eris-master/src/lobject.c b/luprex/ext/eris-master/src/lobject.c new file mode 100644 index 00000000..882d994d --- /dev/null +++ b/luprex/ext/eris-master/src/lobject.c @@ -0,0 +1,287 @@ +/* +** $Id: lobject.c,v 2.58.1.1 2013/04/12 18:48:47 roberto Exp $ +** Some generic functions over Lua objects +** See Copyright Notice in lua.h +*/ + +#include +#include +#include +#include + +#define lobject_c +#define LUA_CORE + +#include "lua.h" + +#include "lctype.h" +#include "ldebug.h" +#include "ldo.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "lvm.h" + + + +LUAI_DDEF const TValue luaO_nilobject_ = {NILCONSTANT}; + + +/* +** converts an integer to a "floating point byte", represented as +** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if +** eeeee != 0 and (xxx) otherwise. +*/ +int luaO_int2fb (unsigned int x) { + int e = 0; /* exponent */ + if (x < 8) return x; + while (x >= 0x10) { + x = (x+1) >> 1; + e++; + } + return ((e+1) << 3) | (cast_int(x) - 8); +} + + +/* converts back */ +int luaO_fb2int (int x) { + int e = (x >> 3) & 0x1f; + if (e == 0) return x; + else return ((x & 7) + 8) << (e - 1); +} + + +int luaO_ceillog2 (unsigned int x) { + static const lu_byte log_2[256] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + }; + int l = 0; + x--; + while (x >= 256) { l += 8; x >>= 8; } + return l + log_2[x]; +} + + +lua_Number luaO_arith (int op, lua_Number v1, lua_Number v2) { + switch (op) { + case LUA_OPADD: return luai_numadd(NULL, v1, v2); + case LUA_OPSUB: return luai_numsub(NULL, v1, v2); + case LUA_OPMUL: return luai_nummul(NULL, v1, v2); + case LUA_OPDIV: return luai_numdiv(NULL, v1, v2); + case LUA_OPMOD: return luai_nummod(NULL, v1, v2); + case LUA_OPPOW: return luai_numpow(NULL, v1, v2); + case LUA_OPUNM: return luai_numunm(NULL, v1); + default: lua_assert(0); return 0; + } +} + + +int luaO_hexavalue (int c) { + if (lisdigit(c)) return c - '0'; + else return ltolower(c) - 'a' + 10; +} + + +#if !defined(lua_strx2number) + +#include + + +static int isneg (const char **s) { + if (**s == '-') { (*s)++; return 1; } + else if (**s == '+') (*s)++; + return 0; +} + + +static lua_Number readhexa (const char **s, lua_Number r, int *count) { + for (; lisxdigit(cast_uchar(**s)); (*s)++) { /* read integer part */ + r = (r * cast_num(16.0)) + cast_num(luaO_hexavalue(cast_uchar(**s))); + (*count)++; + } + return r; +} + + +/* +** convert an hexadecimal numeric string to a number, following +** C99 specification for 'strtod' +*/ +static lua_Number lua_strx2number (const char *s, char **endptr) { + lua_Number r = 0.0; + int e = 0, i = 0; + int neg = 0; /* 1 if number is negative */ + *endptr = cast(char *, s); /* nothing is valid yet */ + while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ + neg = isneg(&s); /* check signal */ + if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X'))) /* check '0x' */ + return 0.0; /* invalid format (no '0x') */ + s += 2; /* skip '0x' */ + r = readhexa(&s, r, &i); /* read integer part */ + if (*s == '.') { + s++; /* skip dot */ + r = readhexa(&s, r, &e); /* read fractional part */ + } + if (i == 0 && e == 0) + return 0.0; /* invalid format (no digit) */ + e *= -4; /* each fractional digit divides value by 2^-4 */ + *endptr = cast(char *, s); /* valid up to here */ + if (*s == 'p' || *s == 'P') { /* exponent part? */ + int exp1 = 0; + int neg1; + s++; /* skip 'p' */ + neg1 = isneg(&s); /* signal */ + if (!lisdigit(cast_uchar(*s))) + goto ret; /* must have at least one digit */ + while (lisdigit(cast_uchar(*s))) /* read exponent */ + exp1 = exp1 * 10 + *(s++) - '0'; + if (neg1) exp1 = -exp1; + e += exp1; + } + *endptr = cast(char *, s); /* valid up to here */ + ret: + if (neg) r = -r; + return l_mathop(ldexp)(r, e); +} + +#endif + + +int luaO_str2d (const char *s, size_t len, lua_Number *result) { + char *endptr; + if (strpbrk(s, "nN")) /* reject 'inf' and 'nan' */ + return 0; + else if (strpbrk(s, "xX")) /* hexa? */ + *result = lua_strx2number(s, &endptr); + else + *result = lua_str2number(s, &endptr); + if (endptr == s) return 0; /* nothing recognized */ + while (lisspace(cast_uchar(*endptr))) endptr++; + return (endptr == s + len); /* OK if no trailing characters */ +} + + + +static void pushstr (lua_State *L, const char *str, size_t l) { + setsvalue2s(L, L->top++, luaS_newlstr(L, str, l)); +} + + +/* this function handles only `%d', `%c', %f, %p, and `%s' formats */ +const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { + int n = 0; + for (;;) { + const char *e = strchr(fmt, '%'); + if (e == NULL) break; + luaD_checkstack(L, 2); /* fmt + item */ + pushstr(L, fmt, e - fmt); + switch (*(e+1)) { + case 's': { + const char *s = va_arg(argp, char *); + if (s == NULL) s = "(null)"; + pushstr(L, s, strlen(s)); + break; + } + case 'c': { + char buff; + buff = cast(char, va_arg(argp, int)); + pushstr(L, &buff, 1); + break; + } + case 'd': { + setnvalue(L->top++, cast_num(va_arg(argp, int))); + break; + } + case 'f': { + setnvalue(L->top++, cast_num(va_arg(argp, l_uacNumber))); + break; + } + case 'p': { + char buff[4*sizeof(void *) + 8]; /* should be enough space for a `%p' */ + int l = sprintf(buff, "%p", va_arg(argp, void *)); + pushstr(L, buff, l); + break; + } + case '%': { + pushstr(L, "%", 1); + break; + } + default: { + luaG_runerror(L, + "invalid option " LUA_QL("%%%c") " to " LUA_QL("lua_pushfstring"), + *(e + 1)); + } + } + n += 2; + fmt = e+2; + } + luaD_checkstack(L, 1); + pushstr(L, fmt, strlen(fmt)); + if (n > 0) luaV_concat(L, n + 1); + return svalue(L->top - 1); +} + + +const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) { + const char *msg; + va_list argp; + va_start(argp, fmt); + msg = luaO_pushvfstring(L, fmt, argp); + va_end(argp); + return msg; +} + + +/* number of chars of a literal string without the ending \0 */ +#define LL(x) (sizeof(x)/sizeof(char) - 1) + +#define RETS "..." +#define PRE "[string \"" +#define POS "\"]" + +#define addstr(a,b,l) ( memcpy(a,b,(l) * sizeof(char)), a += (l) ) + +void luaO_chunkid (char *out, const char *source, size_t bufflen) { + size_t l = strlen(source); + if (*source == '=') { /* 'literal' source */ + if (l <= bufflen) /* small enough? */ + memcpy(out, source + 1, l * sizeof(char)); + else { /* truncate it */ + addstr(out, source + 1, bufflen - 1); + *out = '\0'; + } + } + else if (*source == '@') { /* file name */ + if (l <= bufflen) /* small enough? */ + memcpy(out, source + 1, l * sizeof(char)); + else { /* add '...' before rest of name */ + addstr(out, RETS, LL(RETS)); + bufflen -= LL(RETS); + memcpy(out, source + 1 + l - bufflen, bufflen * sizeof(char)); + } + } + else { /* string; format as [string "source"] */ + const char *nl = strchr(source, '\n'); /* find first new line (if any) */ + addstr(out, PRE, LL(PRE)); /* add prefix */ + bufflen -= LL(PRE RETS POS) + 1; /* save space for prefix+suffix+'\0' */ + if (l < bufflen && nl == NULL) { /* small one-line source? */ + addstr(out, source, l); /* keep it */ + } + else { + if (nl != NULL) l = nl - source; /* stop at first newline */ + if (l > bufflen) l = bufflen; + addstr(out, source, l); + addstr(out, RETS, LL(RETS)); + } + memcpy(out, POS, (LL(POS) + 1) * sizeof(char)); + } +} + diff --git a/luprex/ext/eris-master/src/lobject.h b/luprex/ext/eris-master/src/lobject.h new file mode 100644 index 00000000..a5bce49e --- /dev/null +++ b/luprex/ext/eris-master/src/lobject.h @@ -0,0 +1,608 @@ +/* +** $Id: lobject.h,v 2.71.1.2 2014/05/07 14:14:58 roberto Exp $ +** Type definitions for Lua objects +** See Copyright Notice in lua.h +*/ + + +#ifndef lobject_h +#define lobject_h + + +#include + + +#include "llimits.h" +#include "lua.h" + + +/* +** Extra tags for non-values +*/ +#define LUA_TPROTO LUA_NUMTAGS +#define LUA_TUPVAL (LUA_NUMTAGS+1) +#define LUA_TDEADKEY (LUA_NUMTAGS+2) + +/* +** number of all possible tags (including LUA_TNONE but excluding DEADKEY) +*/ +#define LUA_TOTALTAGS (LUA_TUPVAL+2) + + +/* +** tags for Tagged Values have the following use of bits: +** bits 0-3: actual tag (a LUA_T* value) +** bits 4-5: variant bits +** bit 6: whether value is collectable +*/ + +#define VARBITS (3 << 4) + + +/* +** LUA_TFUNCTION variants: +** 0 - Lua function +** 1 - light C function +** 2 - regular C function (closure) +*/ + +/* Variant tags for functions */ +#define LUA_TLCL (LUA_TFUNCTION | (0 << 4)) /* Lua closure */ +#define LUA_TLCF (LUA_TFUNCTION | (1 << 4)) /* light C function */ +#define LUA_TCCL (LUA_TFUNCTION | (2 << 4)) /* C closure */ + + +/* Variant tags for strings */ +#define LUA_TSHRSTR (LUA_TSTRING | (0 << 4)) /* short strings */ +#define LUA_TLNGSTR (LUA_TSTRING | (1 << 4)) /* long strings */ + + +/* Bit mark for collectable types */ +#define BIT_ISCOLLECTABLE (1 << 6) + +/* mark a tag as collectable */ +#define ctb(t) ((t) | BIT_ISCOLLECTABLE) + + +/* +** Union of all collectable objects +*/ +typedef union GCObject GCObject; + + +/* +** Common Header for all collectable objects (in macro form, to be +** included in other objects) +*/ +#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked + + +/* +** Common header in struct form +*/ +typedef struct GCheader { + CommonHeader; +} GCheader; + + + +/* +** Union of all Lua values +*/ +typedef union Value Value; + + +#define numfield lua_Number n; /* numbers */ + + + +/* +** Tagged Values. This is the basic representation of values in Lua, +** an actual value plus a tag with its type. +*/ + +#define TValuefields Value value_; int tt_ + +typedef struct lua_TValue TValue; + + +/* macro defining a nil value */ +#define NILCONSTANT {NULL}, LUA_TNIL + + +#define val_(o) ((o)->value_) +#define num_(o) (val_(o).n) + + +/* raw type tag of a TValue */ +#define rttype(o) ((o)->tt_) + +/* tag with no variants (bits 0-3) */ +#define novariant(x) ((x) & 0x0F) + +/* type tag of a TValue (bits 0-3 for tags + variant bits 4-5) */ +#define ttype(o) (rttype(o) & 0x3F) + +/* type tag of a TValue with no variants (bits 0-3) */ +#define ttypenv(o) (novariant(rttype(o))) + + +/* Macros to test type */ +#define checktag(o,t) (rttype(o) == (t)) +#define checktype(o,t) (ttypenv(o) == (t)) +#define ttisnumber(o) checktag((o), LUA_TNUMBER) +#define ttisnil(o) checktag((o), LUA_TNIL) +#define ttisboolean(o) checktag((o), LUA_TBOOLEAN) +#define ttislightuserdata(o) checktag((o), LUA_TLIGHTUSERDATA) +#define ttisstring(o) checktype((o), LUA_TSTRING) +#define ttisshrstring(o) checktag((o), ctb(LUA_TSHRSTR)) +#define ttislngstring(o) checktag((o), ctb(LUA_TLNGSTR)) +#define ttistable(o) checktag((o), ctb(LUA_TTABLE)) +#define ttisfunction(o) checktype(o, LUA_TFUNCTION) +#define ttisclosure(o) ((rttype(o) & 0x1F) == LUA_TFUNCTION) +#define ttisCclosure(o) checktag((o), ctb(LUA_TCCL)) +#define ttisLclosure(o) checktag((o), ctb(LUA_TLCL)) +#define ttislcf(o) checktag((o), LUA_TLCF) +#define ttisuserdata(o) checktag((o), ctb(LUA_TUSERDATA)) +#define ttisthread(o) checktag((o), ctb(LUA_TTHREAD)) +#define ttisdeadkey(o) checktag((o), LUA_TDEADKEY) + +#define ttisequal(o1,o2) (rttype(o1) == rttype(o2)) + +/* Macros to access values */ +#define nvalue(o) check_exp(ttisnumber(o), num_(o)) +#define gcvalue(o) check_exp(iscollectable(o), val_(o).gc) +#define pvalue(o) check_exp(ttislightuserdata(o), val_(o).p) +#define rawtsvalue(o) check_exp(ttisstring(o), &val_(o).gc->ts) +#define tsvalue(o) (&rawtsvalue(o)->tsv) +#define rawuvalue(o) check_exp(ttisuserdata(o), &val_(o).gc->u) +#define uvalue(o) (&rawuvalue(o)->uv) +#define clvalue(o) check_exp(ttisclosure(o), &val_(o).gc->cl) +#define clLvalue(o) check_exp(ttisLclosure(o), &val_(o).gc->cl.l) +#define clCvalue(o) check_exp(ttisCclosure(o), &val_(o).gc->cl.c) +#define fvalue(o) check_exp(ttislcf(o), val_(o).f) +#define hvalue(o) check_exp(ttistable(o), &val_(o).gc->h) +#define bvalue(o) check_exp(ttisboolean(o), val_(o).b) +#define thvalue(o) check_exp(ttisthread(o), &val_(o).gc->th) +/* a dead value may get the 'gc' field, but cannot access its contents */ +#define deadvalue(o) check_exp(ttisdeadkey(o), cast(void *, val_(o).gc)) + +#define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0)) + + +#define iscollectable(o) (rttype(o) & BIT_ISCOLLECTABLE) + + +/* Macros for internal tests */ +#define righttt(obj) (ttype(obj) == gcvalue(obj)->gch.tt) + +#define checkliveness(g,obj) \ + lua_longassert(!iscollectable(obj) || \ + (righttt(obj) && !isdead(g,gcvalue(obj)))) + + +/* Macros to set values */ +#define settt_(o,t) ((o)->tt_=(t)) + +#define setnvalue(obj,x) \ + { TValue *io=(obj); num_(io)=(x); settt_(io, LUA_TNUMBER); } + +#define setnilvalue(obj) settt_(obj, LUA_TNIL) + +#define setfvalue(obj,x) \ + { TValue *io=(obj); val_(io).f=(x); settt_(io, LUA_TLCF); } + +#define setpvalue(obj,x) \ + { TValue *io=(obj); val_(io).p=(x); settt_(io, LUA_TLIGHTUSERDATA); } + +#define setbvalue(obj,x) \ + { TValue *io=(obj); val_(io).b=(x); settt_(io, LUA_TBOOLEAN); } + +#define setgcovalue(L,obj,x) \ + { TValue *io=(obj); GCObject *i_g=(x); \ + val_(io).gc=i_g; settt_(io, ctb(gch(i_g)->tt)); } + +#define setsvalue(L,obj,x) \ + { TValue *io=(obj); \ + TString *x_ = (x); \ + val_(io).gc=cast(GCObject *, x_); settt_(io, ctb(x_->tsv.tt)); \ + checkliveness(G(L),io); } + +#define setuvalue(L,obj,x) \ + { TValue *io=(obj); \ + val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TUSERDATA)); \ + checkliveness(G(L),io); } + +#define setthvalue(L,obj,x) \ + { TValue *io=(obj); \ + val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TTHREAD)); \ + checkliveness(G(L),io); } + +#define setclLvalue(L,obj,x) \ + { TValue *io=(obj); \ + val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TLCL)); \ + checkliveness(G(L),io); } + +#define setclCvalue(L,obj,x) \ + { TValue *io=(obj); \ + val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TCCL)); \ + checkliveness(G(L),io); } + +#define sethvalue(L,obj,x) \ + { TValue *io=(obj); \ + val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TTABLE)); \ + checkliveness(G(L),io); } + +#define setdeadvalue(obj) settt_(obj, LUA_TDEADKEY) + + + +#define setobj(L,obj1,obj2) \ + { const TValue *io2=(obj2); TValue *io1=(obj1); \ + io1->value_ = io2->value_; io1->tt_ = io2->tt_; \ + checkliveness(G(L),io1); } + + +/* +** different types of assignments, according to destination +*/ + +/* from stack to (same) stack */ +#define setobjs2s setobj +/* to stack (not from same stack) */ +#define setobj2s setobj +#define setsvalue2s setsvalue +#define sethvalue2s sethvalue +#define setptvalue2s setptvalue +/* from table to same table */ +#define setobjt2t setobj +/* to table */ +#define setobj2t setobj +/* to new object */ +#define setobj2n setobj +#define setsvalue2n setsvalue + + +/* check whether a number is valid (useful only for NaN trick) */ +#define luai_checknum(L,o,c) { /* empty */ } + + +/* +** {====================================================== +** NaN Trick +** ======================================================= +*/ +#if defined(LUA_NANTRICK) + +/* +** numbers are represented in the 'd_' field. All other values have the +** value (NNMARK | tag) in 'tt__'. A number with such pattern would be +** a "signaled NaN", which is never generated by regular operations by +** the CPU (nor by 'strtod') +*/ + +/* allows for external implementation for part of the trick */ +#if !defined(NNMARK) /* { */ + + +#if !defined(LUA_IEEEENDIAN) +#error option 'LUA_NANTRICK' needs 'LUA_IEEEENDIAN' +#endif + + +#define NNMARK 0x7FF7A500 +#define NNMASK 0x7FFFFF00 + +#undef TValuefields +#undef NILCONSTANT + +#if (LUA_IEEEENDIAN == 0) /* { */ + +/* little endian */ +#define TValuefields \ + union { struct { Value v__; int tt__; } i; double d__; } u +#define NILCONSTANT {{{NULL}, tag2tt(LUA_TNIL)}} +/* field-access macros */ +#define v_(o) ((o)->u.i.v__) +#define d_(o) ((o)->u.d__) +#define tt_(o) ((o)->u.i.tt__) + +#else /* }{ */ + +/* big endian */ +#define TValuefields \ + union { struct { int tt__; Value v__; } i; double d__; } u +#define NILCONSTANT {{tag2tt(LUA_TNIL), {NULL}}} +/* field-access macros */ +#define v_(o) ((o)->u.i.v__) +#define d_(o) ((o)->u.d__) +#define tt_(o) ((o)->u.i.tt__) + +#endif /* } */ + +#endif /* } */ + + +/* correspondence with standard representation */ +#undef val_ +#define val_(o) v_(o) +#undef num_ +#define num_(o) d_(o) + + +#undef numfield +#define numfield /* no such field; numbers are the entire struct */ + +/* basic check to distinguish numbers from non-numbers */ +#undef ttisnumber +#define ttisnumber(o) ((tt_(o) & NNMASK) != NNMARK) + +#define tag2tt(t) (NNMARK | (t)) + +#undef rttype +#define rttype(o) (ttisnumber(o) ? LUA_TNUMBER : tt_(o) & 0xff) + +#undef settt_ +#define settt_(o,t) (tt_(o) = tag2tt(t)) + +#undef setnvalue +#define setnvalue(obj,x) \ + { TValue *io_=(obj); num_(io_)=(x); lua_assert(ttisnumber(io_)); } + +#undef setobj +#define setobj(L,obj1,obj2) \ + { const TValue *o2_=(obj2); TValue *o1_=(obj1); \ + o1_->u = o2_->u; \ + checkliveness(G(L),o1_); } + + +/* +** these redefinitions are not mandatory, but these forms are more efficient +*/ + +#undef checktag +#undef checktype +#define checktag(o,t) (tt_(o) == tag2tt(t)) +#define checktype(o,t) (ctb(tt_(o) | VARBITS) == ctb(tag2tt(t) | VARBITS)) + +#undef ttisequal +#define ttisequal(o1,o2) \ + (ttisnumber(o1) ? ttisnumber(o2) : (tt_(o1) == tt_(o2))) + + +#undef luai_checknum +#define luai_checknum(L,o,c) { if (!ttisnumber(o)) c; } + +#endif +/* }====================================================== */ + + + +/* +** {====================================================== +** types and prototypes +** ======================================================= +*/ + + +union Value { + GCObject *gc; /* collectable objects */ + void *p; /* light userdata */ + int b; /* booleans */ + lua_CFunction f; /* light C functions */ + numfield /* numbers */ +}; + + +struct lua_TValue { + TValuefields; +}; + + +typedef TValue *StkId; /* index to stack elements */ + + + + +/* +** Header for string value; string bytes follow the end of this structure +*/ +typedef union TString { + L_Umaxalign dummy; /* ensures maximum alignment for strings */ + struct { + CommonHeader; + lu_byte extra; /* reserved words for short strings; "has hash" for longs */ + unsigned int hash; + size_t len; /* number of characters in string */ + } tsv; +} TString; + + +/* get the actual string (array of bytes) from a TString */ +#define getstr(ts) cast(const char *, (ts) + 1) + +/* get the actual string (array of bytes) from a Lua value */ +#define svalue(o) getstr(rawtsvalue(o)) + + +/* +** Header for userdata; memory area follows the end of this structure +*/ +typedef union Udata { + L_Umaxalign dummy; /* ensures maximum alignment for `local' udata */ + struct { + CommonHeader; + struct Table *metatable; + struct Table *env; + size_t len; /* number of bytes */ + } uv; +} Udata; + + + +/* +** Description of an upvalue for function prototypes +*/ +typedef struct Upvaldesc { + TString *name; /* upvalue name (for debug information) */ + lu_byte instack; /* whether it is in stack */ + lu_byte idx; /* index of upvalue (in stack or in outer function's list) */ +} Upvaldesc; + + +/* +** Description of a local variable for function prototypes +** (used for debug information) +*/ +typedef struct LocVar { + TString *varname; + int startpc; /* first point where variable is active */ + int endpc; /* first point where variable is dead */ +} LocVar; + + +/* +** Function Prototypes +*/ +typedef struct Proto { + CommonHeader; + TValue *k; /* constants used by the function */ + Instruction *code; + struct Proto **p; /* functions defined inside the function */ + int *lineinfo; /* map from opcodes to source lines (debug information) */ + LocVar *locvars; /* information about local variables (debug information) */ + Upvaldesc *upvalues; /* upvalue information */ + union Closure *cache; /* last created closure with this prototype */ + TString *source; /* used for debug information */ + int sizeupvalues; /* size of 'upvalues' */ + int sizek; /* size of `k' */ + int sizecode; + int sizelineinfo; + int sizep; /* size of `p' */ + int sizelocvars; + int linedefined; + int lastlinedefined; + GCObject *gclist; + lu_byte numparams; /* number of fixed parameters */ + lu_byte is_vararg; + lu_byte maxstacksize; /* maximum stack used by this function */ +} Proto; + + + +/* +** Lua Upvalues +*/ +typedef struct UpVal { + CommonHeader; + TValue *v; /* points to stack or to its own value */ + union { + TValue value; /* the value (when closed) */ + struct { /* double linked list (when open) */ + struct UpVal *prev; + struct UpVal *next; + } l; + } u; +} UpVal; + + +/* +** Closures +*/ + +#define ClosureHeader \ + CommonHeader; lu_byte nupvalues; GCObject *gclist + +typedef struct CClosure { + ClosureHeader; + lua_CFunction f; + TValue upvalue[1]; /* list of upvalues */ +} CClosure; + + +typedef struct LClosure { + ClosureHeader; + struct Proto *p; + UpVal *upvals[1]; /* list of upvalues */ +} LClosure; + + +typedef union Closure { + CClosure c; + LClosure l; +} Closure; + + +#define isLfunction(o) ttisLclosure(o) + +#define getproto(o) (clLvalue(o)->p) + + +/* +** Tables +*/ + +typedef struct ANode { + TValue i_val; + int i_sequence; +} ANode; + +typedef struct Node { + ANode anode; + TValue i_key; + struct Node *i_next; +} Node; + +typedef struct Table { + CommonHeader; + lu_byte flags; /* 1<

lsizenode)) + + +/* +** (address of) a fixed nil value +*/ +#define luaO_nilobject (&luaO_nilobject_) + + +LUAI_DDEC const TValue luaO_nilobject_; + + +LUAI_FUNC int luaO_int2fb (unsigned int x); +LUAI_FUNC int luaO_fb2int (int x); +LUAI_FUNC int luaO_ceillog2 (unsigned int x); +LUAI_FUNC lua_Number luaO_arith (int op, lua_Number v1, lua_Number v2); +LUAI_FUNC int luaO_str2d (const char *s, size_t len, lua_Number *result); +LUAI_FUNC int luaO_hexavalue (int c); +LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt, + va_list argp); +LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...); +LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t len); + + +#endif + diff --git a/luprex/ext/eris-master/src/lopcodes.c b/luprex/ext/eris-master/src/lopcodes.c new file mode 100644 index 00000000..4190dc76 --- /dev/null +++ b/luprex/ext/eris-master/src/lopcodes.c @@ -0,0 +1,107 @@ +/* +** $Id: lopcodes.c,v 1.49.1.1 2013/04/12 18:48:47 roberto Exp $ +** Opcodes for Lua virtual machine +** See Copyright Notice in lua.h +*/ + + +#define lopcodes_c +#define LUA_CORE + + +#include "lopcodes.h" + + +/* ORDER OP */ + +LUAI_DDEF const char *const luaP_opnames[NUM_OPCODES+1] = { + "MOVE", + "LOADK", + "LOADKX", + "LOADBOOL", + "LOADNIL", + "GETUPVAL", + "GETTABUP", + "GETTABLE", + "SETTABUP", + "SETUPVAL", + "SETTABLE", + "NEWTABLE", + "SELF", + "ADD", + "SUB", + "MUL", + "DIV", + "MOD", + "POW", + "UNM", + "NOT", + "LEN", + "CONCAT", + "JMP", + "EQ", + "LT", + "LE", + "TEST", + "TESTSET", + "CALL", + "TAILCALL", + "RETURN", + "FORLOOP", + "FORPREP", + "TFORCALL", + "TFORLOOP", + "SETLIST", + "CLOSURE", + "VARARG", + "EXTRAARG", + NULL +}; + + +#define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m)) + +LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { +/* T A B C mode opcode */ + opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */ + ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */ + ,opmode(0, 1, OpArgN, OpArgN, iABx) /* OP_LOADKX */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_LOADBOOL */ + ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_LOADNIL */ + ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_GETUPVAL */ + ,opmode(0, 1, OpArgU, OpArgK, iABC) /* OP_GETTABUP */ + ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_GETTABLE */ + ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABUP */ + ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_SETUPVAL */ + ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABLE */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_NEWTABLE */ + ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_SELF */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */ + ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */ + ,opmode(0, 0, OpArgR, OpArgN, iAsBx) /* OP_JMP */ + ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_EQ */ + ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LT */ + ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LE */ + ,opmode(1, 0, OpArgN, OpArgU, iABC) /* OP_TEST */ + ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TESTSET */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_CALL */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_TAILCALL */ + ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_RETURN */ + ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORLOOP */ + ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORPREP */ + ,opmode(0, 0, OpArgN, OpArgU, iABC) /* OP_TFORCALL */ + ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_TFORLOOP */ + ,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */ + ,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */ + ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */ + ,opmode(0, 0, OpArgU, OpArgU, iAx) /* OP_EXTRAARG */ +}; + diff --git a/luprex/ext/eris-master/src/lopcodes.h b/luprex/ext/eris-master/src/lopcodes.h new file mode 100644 index 00000000..8e2f80a1 --- /dev/null +++ b/luprex/ext/eris-master/src/lopcodes.h @@ -0,0 +1,288 @@ +/* +** $Id: lopcodes.h,v 1.142.1.2 2014/10/20 18:32:09 roberto Exp $ +** Opcodes for Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#ifndef lopcodes_h +#define lopcodes_h + +#include "llimits.h" + + +/*=========================================================================== + We assume that instructions are unsigned numbers. + All instructions have an opcode in the first 6 bits. + Instructions can have the following fields: + `A' : 8 bits + `B' : 9 bits + `C' : 9 bits + 'Ax' : 26 bits ('A', 'B', and 'C' together) + `Bx' : 18 bits (`B' and `C' together) + `sBx' : signed Bx + + A signed argument is represented in excess K; that is, the number + value is the unsigned value minus K. K is exactly the maximum value + for that argument (so that -max is represented by 0, and +max is + represented by 2*max), which is half the maximum for the corresponding + unsigned argument. +===========================================================================*/ + + +enum OpMode {iABC, iABx, iAsBx, iAx}; /* basic instruction format */ + + +/* +** size and position of opcode arguments. +*/ +#define SIZE_C 9 +#define SIZE_B 9 +#define SIZE_Bx (SIZE_C + SIZE_B) +#define SIZE_A 8 +#define SIZE_Ax (SIZE_C + SIZE_B + SIZE_A) + +#define SIZE_OP 6 + +#define POS_OP 0 +#define POS_A (POS_OP + SIZE_OP) +#define POS_C (POS_A + SIZE_A) +#define POS_B (POS_C + SIZE_C) +#define POS_Bx POS_C +#define POS_Ax POS_A + + +/* +** limits for opcode arguments. +** we use (signed) int to manipulate most arguments, +** so they must fit in LUAI_BITSINT-1 bits (-1 for sign) +*/ +#if SIZE_Bx < LUAI_BITSINT-1 +#define MAXARG_Bx ((1<>1) /* `sBx' is signed */ +#else +#define MAXARG_Bx MAX_INT +#define MAXARG_sBx MAX_INT +#endif + +#if SIZE_Ax < LUAI_BITSINT-1 +#define MAXARG_Ax ((1<>POS_OP) & MASK1(SIZE_OP,0))) +#define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ + ((cast(Instruction, o)<>pos) & MASK1(size,0))) +#define setarg(i,v,pos,size) ((i) = (((i)&MASK0(size,pos)) | \ + ((cast(Instruction, v)<= R(A - 1) */ +OP_EQ,/* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ +OP_LT,/* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ +OP_LE,/* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ + +OP_TEST,/* A C if not (R(A) <=> C) then pc++ */ +OP_TESTSET,/* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ + +OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */ +OP_TAILCALL,/* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ +OP_RETURN,/* A B return R(A), ... ,R(A+B-2) (see note) */ + +OP_FORLOOP,/* A sBx R(A)+=R(A+2); + if R(A) > 4) & 3)) +#define getCMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 2) & 3)) +#define testAMode(m) (luaP_opmodes[m] & (1 << 6)) +#define testTMode(m) (luaP_opmodes[m] & (1 << 7)) + + +LUAI_DDEC const char *const luaP_opnames[NUM_OPCODES+1]; /* opcode names */ + + +/* number of list items to accumulate before a SETLIST instruction */ +#define LFIELDS_PER_FLUSH 50 + + +#endif diff --git a/luprex/ext/eris-master/src/loslib.c b/luprex/ext/eris-master/src/loslib.c new file mode 100644 index 00000000..052ba174 --- /dev/null +++ b/luprex/ext/eris-master/src/loslib.c @@ -0,0 +1,323 @@ +/* +** $Id: loslib.c,v 1.40.1.1 2013/04/12 18:48:47 roberto Exp $ +** Standard Operating System library +** See Copyright Notice in lua.h +*/ + + +#include +#include +#include +#include +#include + +#define loslib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** list of valid conversion specifiers for the 'strftime' function +*/ +#if !defined(LUA_STRFTIMEOPTIONS) + +#if !defined(LUA_USE_POSIX) +#define LUA_STRFTIMEOPTIONS { "aAbBcdHIjmMpSUwWxXyYz%", "" } +#else +#define LUA_STRFTIMEOPTIONS \ + { "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%", "" \ + "", "E", "cCxXyY", \ + "O", "deHImMSuUVwWy" } +#endif + +#endif + + + +/* +** By default, Lua uses tmpnam except when POSIX is available, where it +** uses mkstemp. +*/ +#if defined(LUA_USE_MKSTEMP) +#include +#define LUA_TMPNAMBUFSIZE 32 +#define lua_tmpnam(b,e) { \ + strcpy(b, "/tmp/lua_XXXXXX"); \ + e = mkstemp(b); \ + if (e != -1) close(e); \ + e = (e == -1); } + +#elif !defined(lua_tmpnam) + +#define LUA_TMPNAMBUFSIZE L_tmpnam +#define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } + +#endif + + +/* +** By default, Lua uses gmtime/localtime, except when POSIX is available, +** where it uses gmtime_r/localtime_r +*/ +#if defined(LUA_USE_GMTIME_R) + +#define l_gmtime(t,r) gmtime_r(t,r) +#define l_localtime(t,r) localtime_r(t,r) + +#elif !defined(l_gmtime) + +#define l_gmtime(t,r) ((void)r, gmtime(t)) +#define l_localtime(t,r) ((void)r, localtime(t)) + +#endif + + + +static int os_execute (lua_State *L) { + const char *cmd = luaL_optstring(L, 1, NULL); + int stat = system(cmd); + if (cmd != NULL) + return luaL_execresult(L, stat); + else { + lua_pushboolean(L, stat); /* true if there is a shell */ + return 1; + } +} + + +static int os_remove (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + return luaL_fileresult(L, remove(filename) == 0, filename); +} + + +static int os_rename (lua_State *L) { + const char *fromname = luaL_checkstring(L, 1); + const char *toname = luaL_checkstring(L, 2); + return luaL_fileresult(L, rename(fromname, toname) == 0, NULL); +} + + +static int os_tmpname (lua_State *L) { + char buff[LUA_TMPNAMBUFSIZE]; + int err; + lua_tmpnam(buff, err); + if (err) + return luaL_error(L, "unable to generate a unique filename"); + lua_pushstring(L, buff); + return 1; +} + + +static int os_getenv (lua_State *L) { + lua_pushstring(L, getenv(luaL_checkstring(L, 1))); /* if NULL push nil */ + return 1; +} + + +static int os_clock (lua_State *L) { + lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC); + return 1; +} + + +/* +** {====================================================== +** Time/Date operations +** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S, +** wday=%w+1, yday=%j, isdst=? } +** ======================================================= +*/ + +static void setfield (lua_State *L, const char *key, int value) { + lua_pushinteger(L, value); + lua_setfield(L, -2, key); +} + +static void setboolfield (lua_State *L, const char *key, int value) { + if (value < 0) /* undefined? */ + return; /* does not set field */ + lua_pushboolean(L, value); + lua_setfield(L, -2, key); +} + +static int getboolfield (lua_State *L, const char *key) { + int res; + lua_getfield(L, -1, key); + res = lua_isnil(L, -1) ? -1 : lua_toboolean(L, -1); + lua_pop(L, 1); + return res; +} + + +static int getfield (lua_State *L, const char *key, int d) { + int res, isnum; + lua_getfield(L, -1, key); + res = (int)lua_tointegerx(L, -1, &isnum); + if (!isnum) { + if (d < 0) + return luaL_error(L, "field " LUA_QS " missing in date table", key); + res = d; + } + lua_pop(L, 1); + return res; +} + + +static const char *checkoption (lua_State *L, const char *conv, char *buff) { + static const char *const options[] = LUA_STRFTIMEOPTIONS; + unsigned int i; + for (i = 0; i < sizeof(options)/sizeof(options[0]); i += 2) { + if (*conv != '\0' && strchr(options[i], *conv) != NULL) { + buff[1] = *conv; + if (*options[i + 1] == '\0') { /* one-char conversion specifier? */ + buff[2] = '\0'; /* end buffer */ + return conv + 1; + } + else if (*(conv + 1) != '\0' && + strchr(options[i + 1], *(conv + 1)) != NULL) { + buff[2] = *(conv + 1); /* valid two-char conversion specifier */ + buff[3] = '\0'; /* end buffer */ + return conv + 2; + } + } + } + luaL_argerror(L, 1, + lua_pushfstring(L, "invalid conversion specifier '%%%s'", conv)); + return conv; /* to avoid warnings */ +} + + +static int os_date (lua_State *L) { + const char *s = luaL_optstring(L, 1, "%c"); + time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL)); + struct tm tmr, *stm; + if (*s == '!') { /* UTC? */ + stm = l_gmtime(&t, &tmr); + s++; /* skip `!' */ + } + else + stm = l_localtime(&t, &tmr); + if (stm == NULL) /* invalid date? */ + lua_pushnil(L); + else if (strcmp(s, "*t") == 0) { + lua_createtable(L, 0, 9); /* 9 = number of fields */ + setfield(L, "sec", stm->tm_sec); + setfield(L, "min", stm->tm_min); + setfield(L, "hour", stm->tm_hour); + setfield(L, "day", stm->tm_mday); + setfield(L, "month", stm->tm_mon+1); + setfield(L, "year", stm->tm_year+1900); + setfield(L, "wday", stm->tm_wday+1); + setfield(L, "yday", stm->tm_yday+1); + setboolfield(L, "isdst", stm->tm_isdst); + } + else { + char cc[4]; + luaL_Buffer b; + cc[0] = '%'; + luaL_buffinit(L, &b); + while (*s) { + if (*s != '%') /* no conversion specifier? */ + luaL_addchar(&b, *s++); + else { + size_t reslen; + char buff[200]; /* should be big enough for any conversion result */ + s = checkoption(L, s + 1, cc); + reslen = strftime(buff, sizeof(buff), cc, stm); + luaL_addlstring(&b, buff, reslen); + } + } + luaL_pushresult(&b); + } + return 1; +} + + +static int os_time (lua_State *L) { + time_t t; + if (lua_isnoneornil(L, 1)) /* called without args? */ + t = time(NULL); /* get current time */ + else { + struct tm ts; + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 1); /* make sure table is at the top */ + ts.tm_sec = getfield(L, "sec", 0); + ts.tm_min = getfield(L, "min", 0); + ts.tm_hour = getfield(L, "hour", 12); + ts.tm_mday = getfield(L, "day", -1); + ts.tm_mon = getfield(L, "month", -1) - 1; + ts.tm_year = getfield(L, "year", -1) - 1900; + ts.tm_isdst = getboolfield(L, "isdst"); + t = mktime(&ts); + } + if (t == (time_t)(-1)) + lua_pushnil(L); + else + lua_pushnumber(L, (lua_Number)t); + return 1; +} + + +static int os_difftime (lua_State *L) { + lua_pushnumber(L, difftime((time_t)(luaL_checknumber(L, 1)), + (time_t)(luaL_optnumber(L, 2, 0)))); + return 1; +} + +/* }====================================================== */ + + +static int os_setlocale (lua_State *L) { + static const int cat[] = {LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, + LC_NUMERIC, LC_TIME}; + static const char *const catnames[] = {"all", "collate", "ctype", "monetary", + "numeric", "time", NULL}; + const char *l = luaL_optstring(L, 1, NULL); + int op = luaL_checkoption(L, 2, "all", catnames); + lua_pushstring(L, setlocale(cat[op], l)); + return 1; +} + + +static int os_exit (lua_State *L) { + int status; + if (lua_isboolean(L, 1)) + status = (lua_toboolean(L, 1) ? EXIT_SUCCESS : EXIT_FAILURE); + else + status = luaL_optint(L, 1, EXIT_SUCCESS); + if (lua_toboolean(L, 2)) + lua_close(L); + if (L) exit(status); /* 'if' to avoid warnings for unreachable 'return' */ + return 0; +} + + +static const luaL_Reg syslib[] = { + {"clock", os_clock}, + {"date", os_date}, + {"difftime", os_difftime}, + {"execute", os_execute}, + {"exit", os_exit}, + {"getenv", os_getenv}, + {"remove", os_remove}, + {"rename", os_rename}, + {"setlocale", os_setlocale}, + {"time", os_time}, + {"tmpname", os_tmpname}, + {NULL, NULL} +}; + +/* }====================================================== */ + + + +LUAMOD_API int luaopen_os (lua_State *L) { + luaL_newlib(L, syslib); + return 1; +} + diff --git a/luprex/ext/eris-master/src/lparser.c b/luprex/ext/eris-master/src/lparser.c new file mode 100644 index 00000000..9e1a9ca2 --- /dev/null +++ b/luprex/ext/eris-master/src/lparser.c @@ -0,0 +1,1638 @@ +/* +** $Id: lparser.c,v 2.130.1.1 2013/04/12 18:48:47 roberto Exp $ +** Lua Parser +** See Copyright Notice in lua.h +*/ + + +#include + +#define lparser_c +#define LUA_CORE + +#include "lua.h" + +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "llex.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" + + + +/* maximum number of local variables per function (must be smaller + than 250, due to the bytecode format) */ +#define MAXVARS 200 + + +#define hasmultret(k) ((k) == VCALL || (k) == VVARARG) + + + +/* +** nodes for block list (list of active blocks) +*/ +typedef struct BlockCnt { + struct BlockCnt *previous; /* chain */ + short firstlabel; /* index of first label in this block */ + short firstgoto; /* index of first pending goto in this block */ + lu_byte nactvar; /* # active locals outside the block */ + lu_byte upval; /* true if some variable in the block is an upvalue */ + lu_byte isloop; /* true if `block' is a loop */ +} BlockCnt; + + + +/* +** prototypes for recursive non-terminal functions +*/ +static void statement (LexState *ls); +static void expr (LexState *ls, expdesc *v); + + +static void anchor_token (LexState *ls) { + /* last token from outer function must be EOS */ + lua_assert(ls->fs != NULL || ls->t.token == TK_EOS); + if (ls->t.token == TK_NAME || ls->t.token == TK_STRING) { + TString *ts = ls->t.seminfo.ts; + luaX_newstring(ls, getstr(ts), ts->tsv.len); + } +} + + +/* semantic error */ +static l_noret semerror (LexState *ls, const char *msg) { + ls->t.token = 0; /* remove 'near to' from final message */ + luaX_syntaxerror(ls, msg); +} + + +static l_noret error_expected (LexState *ls, int token) { + luaX_syntaxerror(ls, + luaO_pushfstring(ls->L, "%s expected", luaX_token2str(ls, token))); +} + + +static l_noret errorlimit (FuncState *fs, int limit, const char *what) { + lua_State *L = fs->ls->L; + const char *msg; + int line = fs->f->linedefined; + const char *where = (line == 0) + ? "main function" + : luaO_pushfstring(L, "function at line %d", line); + msg = luaO_pushfstring(L, "too many %s (limit is %d) in %s", + what, limit, where); + luaX_syntaxerror(fs->ls, msg); +} + + +static void checklimit (FuncState *fs, int v, int l, const char *what) { + if (v > l) errorlimit(fs, l, what); +} + + +static int testnext (LexState *ls, int c) { + if (ls->t.token == c) { + luaX_next(ls); + return 1; + } + else return 0; +} + + +static void check (LexState *ls, int c) { + if (ls->t.token != c) + error_expected(ls, c); +} + + +static void checknext (LexState *ls, int c) { + check(ls, c); + luaX_next(ls); +} + + +#define check_condition(ls,c,msg) { if (!(c)) luaX_syntaxerror(ls, msg); } + + + +static void check_match (LexState *ls, int what, int who, int where) { + if (!testnext(ls, what)) { + if (where == ls->linenumber) + error_expected(ls, what); + else { + luaX_syntaxerror(ls, luaO_pushfstring(ls->L, + "%s expected (to close %s at line %d)", + luaX_token2str(ls, what), luaX_token2str(ls, who), where)); + } + } +} + + +static TString *str_checkname (LexState *ls) { + TString *ts; + check(ls, TK_NAME); + ts = ls->t.seminfo.ts; + luaX_next(ls); + return ts; +} + + +static void init_exp (expdesc *e, expkind k, int i) { + e->f = e->t = NO_JUMP; + e->k = k; + e->u.info = i; +} + + +static void codestring (LexState *ls, expdesc *e, TString *s) { + init_exp(e, VK, luaK_stringK(ls->fs, s)); +} + + +static void checkname (LexState *ls, expdesc *e) { + codestring(ls, e, str_checkname(ls)); +} + + +static int registerlocalvar (LexState *ls, TString *varname) { + FuncState *fs = ls->fs; + Proto *f = fs->f; + int oldsize = f->sizelocvars; + luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars, + LocVar, SHRT_MAX, "local variables"); + while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL; + f->locvars[fs->nlocvars].varname = varname; + luaC_objbarrier(ls->L, f, varname); + return fs->nlocvars++; +} + + +static void new_localvar (LexState *ls, TString *name) { + FuncState *fs = ls->fs; + Dyndata *dyd = ls->dyd; + int reg = registerlocalvar(ls, name); + checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal, + MAXVARS, "local variables"); + luaM_growvector(ls->L, dyd->actvar.arr, dyd->actvar.n + 1, + dyd->actvar.size, Vardesc, MAX_INT, "local variables"); + dyd->actvar.arr[dyd->actvar.n++].idx = cast(short, reg); +} + + +static void new_localvarliteral_ (LexState *ls, const char *name, size_t sz) { + new_localvar(ls, luaX_newstring(ls, name, sz)); +} + +#define new_localvarliteral(ls,v) \ + new_localvarliteral_(ls, "" v, (sizeof(v)/sizeof(char))-1) + + +static LocVar *getlocvar (FuncState *fs, int i) { + int idx = fs->ls->dyd->actvar.arr[fs->firstlocal + i].idx; + lua_assert(idx < fs->nlocvars); + return &fs->f->locvars[idx]; +} + + +static void adjustlocalvars (LexState *ls, int nvars) { + FuncState *fs = ls->fs; + fs->nactvar = cast_byte(fs->nactvar + nvars); + for (; nvars; nvars--) { + getlocvar(fs, fs->nactvar - nvars)->startpc = fs->pc; + } +} + + +static void removevars (FuncState *fs, int tolevel) { + fs->ls->dyd->actvar.n -= (fs->nactvar - tolevel); + while (fs->nactvar > tolevel) + getlocvar(fs, --fs->nactvar)->endpc = fs->pc; +} + + +static int searchupvalue (FuncState *fs, TString *name) { + int i; + Upvaldesc *up = fs->f->upvalues; + for (i = 0; i < fs->nups; i++) { + if (luaS_eqstr(up[i].name, name)) return i; + } + return -1; /* not found */ +} + + +static int newupvalue (FuncState *fs, TString *name, expdesc *v) { + Proto *f = fs->f; + int oldsize = f->sizeupvalues; + checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues"); + luaM_growvector(fs->ls->L, f->upvalues, fs->nups, f->sizeupvalues, + Upvaldesc, MAXUPVAL, "upvalues"); + while (oldsize < f->sizeupvalues) f->upvalues[oldsize++].name = NULL; + f->upvalues[fs->nups].instack = (v->k == VLOCAL); + f->upvalues[fs->nups].idx = cast_byte(v->u.info); + f->upvalues[fs->nups].name = name; + luaC_objbarrier(fs->ls->L, f, name); + return fs->nups++; +} + + +static int searchvar (FuncState *fs, TString *n) { + int i; + for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) { + if (luaS_eqstr(n, getlocvar(fs, i)->varname)) + return i; + } + return -1; /* not found */ +} + + +/* + Mark block where variable at given level was defined + (to emit close instructions later). +*/ +static void markupval (FuncState *fs, int level) { + BlockCnt *bl = fs->bl; + while (bl->nactvar > level) bl = bl->previous; + bl->upval = 1; +} + + +/* + Find variable with given name 'n'. If it is an upvalue, add this + upvalue into all intermediate functions. +*/ +static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { + if (fs == NULL) /* no more levels? */ + return VVOID; /* default is global */ + else { + int v = searchvar(fs, n); /* look up locals at current level */ + if (v >= 0) { /* found? */ + init_exp(var, VLOCAL, v); /* variable is local */ + if (!base) + markupval(fs, v); /* local will be used as an upval */ + return VLOCAL; + } + else { /* not found as local at current level; try upvalues */ + int idx = searchupvalue(fs, n); /* try existing upvalues */ + if (idx < 0) { /* not found? */ + if (singlevaraux(fs->prev, n, var, 0) == VVOID) /* try upper levels */ + return VVOID; /* not found; is a global */ + /* else was LOCAL or UPVAL */ + idx = newupvalue(fs, n, var); /* will be a new upvalue */ + } + init_exp(var, VUPVAL, idx); + return VUPVAL; + } + } +} + + +static void singlevar (LexState *ls, expdesc *var) { + TString *varname = str_checkname(ls); + FuncState *fs = ls->fs; + if (singlevaraux(fs, varname, var, 1) == VVOID) { /* global name? */ + expdesc key; + singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ + lua_assert(var->k == VLOCAL || var->k == VUPVAL); + codestring(ls, &key, varname); /* key is variable name */ + luaK_indexed(fs, var, &key); /* env[varname] */ + } +} + + +static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { + FuncState *fs = ls->fs; + int extra = nvars - nexps; + if (hasmultret(e->k)) { + extra++; /* includes call itself */ + if (extra < 0) extra = 0; + luaK_setreturns(fs, e, extra); /* last exp. provides the difference */ + if (extra > 1) luaK_reserveregs(fs, extra-1); + } + else { + if (e->k != VVOID) luaK_exp2nextreg(fs, e); /* close last expression */ + if (extra > 0) { + int reg = fs->freereg; + luaK_reserveregs(fs, extra); + luaK_nil(fs, reg, extra); + } + } +} + + +static void enterlevel (LexState *ls) { + lua_State *L = ls->L; + ++L->nCcalls; + checklimit(ls->fs, L->nCcalls, LUAI_MAXCCALLS, "C levels"); +} + + +#define leavelevel(ls) ((ls)->L->nCcalls--) + + +static void closegoto (LexState *ls, int g, Labeldesc *label) { + int i; + FuncState *fs = ls->fs; + Labellist *gl = &ls->dyd->gt; + Labeldesc *gt = &gl->arr[g]; + lua_assert(luaS_eqstr(gt->name, label->name)); + if (gt->nactvar < label->nactvar) { + TString *vname = getlocvar(fs, gt->nactvar)->varname; + const char *msg = luaO_pushfstring(ls->L, + " at line %d jumps into the scope of local " LUA_QS, + getstr(gt->name), gt->line, getstr(vname)); + semerror(ls, msg); + } + luaK_patchlist(fs, gt->pc, label->pc); + /* remove goto from pending list */ + for (i = g; i < gl->n - 1; i++) + gl->arr[i] = gl->arr[i + 1]; + gl->n--; +} + + +/* +** try to close a goto with existing labels; this solves backward jumps +*/ +static int findlabel (LexState *ls, int g) { + int i; + BlockCnt *bl = ls->fs->bl; + Dyndata *dyd = ls->dyd; + Labeldesc *gt = &dyd->gt.arr[g]; + /* check labels in current block for a match */ + for (i = bl->firstlabel; i < dyd->label.n; i++) { + Labeldesc *lb = &dyd->label.arr[i]; + if (luaS_eqstr(lb->name, gt->name)) { /* correct label? */ + if (gt->nactvar > lb->nactvar && + (bl->upval || dyd->label.n > bl->firstlabel)) + luaK_patchclose(ls->fs, gt->pc, lb->nactvar); + closegoto(ls, g, lb); /* close it */ + return 1; + } + } + return 0; /* label not found; cannot close goto */ +} + + +static int newlabelentry (LexState *ls, Labellist *l, TString *name, + int line, int pc) { + int n = l->n; + luaM_growvector(ls->L, l->arr, n, l->size, + Labeldesc, SHRT_MAX, "labels/gotos"); + l->arr[n].name = name; + l->arr[n].line = line; + l->arr[n].nactvar = ls->fs->nactvar; + l->arr[n].pc = pc; + l->n++; + return n; +} + + +/* +** check whether new label 'lb' matches any pending gotos in current +** block; solves forward jumps +*/ +static void findgotos (LexState *ls, Labeldesc *lb) { + Labellist *gl = &ls->dyd->gt; + int i = ls->fs->bl->firstgoto; + while (i < gl->n) { + if (luaS_eqstr(gl->arr[i].name, lb->name)) + closegoto(ls, i, lb); + else + i++; + } +} + + +/* +** "export" pending gotos to outer level, to check them against +** outer labels; if the block being exited has upvalues, and +** the goto exits the scope of any variable (which can be the +** upvalue), close those variables being exited. +*/ +static void movegotosout (FuncState *fs, BlockCnt *bl) { + int i = bl->firstgoto; + Labellist *gl = &fs->ls->dyd->gt; + /* correct pending gotos to current block and try to close it + with visible labels */ + while (i < gl->n) { + Labeldesc *gt = &gl->arr[i]; + if (gt->nactvar > bl->nactvar) { + if (bl->upval) + luaK_patchclose(fs, gt->pc, bl->nactvar); + gt->nactvar = bl->nactvar; + } + if (!findlabel(fs->ls, i)) + i++; /* move to next one */ + } +} + + +static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { + bl->isloop = isloop; + bl->nactvar = fs->nactvar; + bl->firstlabel = fs->ls->dyd->label.n; + bl->firstgoto = fs->ls->dyd->gt.n; + bl->upval = 0; + bl->previous = fs->bl; + fs->bl = bl; + lua_assert(fs->freereg == fs->nactvar); +} + + +/* +** create a label named "break" to resolve break statements +*/ +static void breaklabel (LexState *ls) { + TString *n = luaS_new(ls->L, "break"); + int l = newlabelentry(ls, &ls->dyd->label, n, 0, ls->fs->pc); + findgotos(ls, &ls->dyd->label.arr[l]); +} + +/* +** generates an error for an undefined 'goto'; choose appropriate +** message when label name is a reserved word (which can only be 'break') +*/ +static l_noret undefgoto (LexState *ls, Labeldesc *gt) { + const char *msg = isreserved(gt->name) + ? "<%s> at line %d not inside a loop" + : "no visible label " LUA_QS " for at line %d"; + msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); + semerror(ls, msg); +} + + +static void leaveblock (FuncState *fs) { + BlockCnt *bl = fs->bl; + LexState *ls = fs->ls; + if (bl->previous && bl->upval) { + /* create a 'jump to here' to close upvalues */ + int j = luaK_jump(fs); + luaK_patchclose(fs, j, bl->nactvar); + luaK_patchtohere(fs, j); + } + if (bl->isloop) + breaklabel(ls); /* close pending breaks */ + fs->bl = bl->previous; + removevars(fs, bl->nactvar); + lua_assert(bl->nactvar == fs->nactvar); + fs->freereg = fs->nactvar; /* free registers */ + ls->dyd->label.n = bl->firstlabel; /* remove local labels */ + if (bl->previous) /* inner block? */ + movegotosout(fs, bl); /* update pending gotos to outer block */ + else if (bl->firstgoto < ls->dyd->gt.n) /* pending gotos in outer block? */ + undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]); /* error */ +} + + +/* +** adds a new prototype into list of prototypes +*/ +static Proto *addprototype (LexState *ls) { + Proto *clp; + lua_State *L = ls->L; + FuncState *fs = ls->fs; + Proto *f = fs->f; /* prototype of current function */ + if (fs->np >= f->sizep) { + int oldsize = f->sizep; + luaM_growvector(L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, "functions"); + while (oldsize < f->sizep) f->p[oldsize++] = NULL; + } + f->p[fs->np++] = clp = luaF_newproto(L); + luaC_objbarrier(L, f, clp); + return clp; +} + + +/* +** codes instruction to create new closure in parent function. +** The OP_CLOSURE instruction must use the last available register, +** so that, if it invokes the GC, the GC knows which registers +** are in use at that time. +*/ +static void codeclosure (LexState *ls, expdesc *v) { + FuncState *fs = ls->fs->prev; + init_exp(v, VRELOCABLE, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np - 1)); + luaK_exp2nextreg(fs, v); /* fix it at the last register */ +} + + +static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { + lua_State *L = ls->L; + Proto *f; + fs->prev = ls->fs; /* linked list of funcstates */ + fs->ls = ls; + ls->fs = fs; + fs->pc = 0; + fs->lasttarget = 0; + fs->jpc = NO_JUMP; + fs->freereg = 0; + fs->nk = 0; + fs->np = 0; + fs->nups = 0; + fs->nlocvars = 0; + fs->nactvar = 0; + fs->firstlocal = ls->dyd->actvar.n; + fs->bl = NULL; + f = fs->f; + f->source = ls->source; + f->maxstacksize = 2; /* registers 0/1 are always valid */ + fs->h = luaH_new(L); + /* anchor table of constants (to avoid being collected) */ + sethvalue2s(L, L->top, fs->h); + incr_top(L); + enterblock(fs, bl, 0); +} + + +static void close_func (LexState *ls) { + lua_State *L = ls->L; + FuncState *fs = ls->fs; + Proto *f = fs->f; + luaK_ret(fs, 0, 0); /* final return */ + leaveblock(fs); + luaM_reallocvector(L, f->code, f->sizecode, fs->pc, Instruction); + f->sizecode = fs->pc; + luaM_reallocvector(L, f->lineinfo, f->sizelineinfo, fs->pc, int); + f->sizelineinfo = fs->pc; + luaM_reallocvector(L, f->k, f->sizek, fs->nk, TValue); + f->sizek = fs->nk; + luaM_reallocvector(L, f->p, f->sizep, fs->np, Proto *); + f->sizep = fs->np; + luaM_reallocvector(L, f->locvars, f->sizelocvars, fs->nlocvars, LocVar); + f->sizelocvars = fs->nlocvars; + luaM_reallocvector(L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc); + f->sizeupvalues = fs->nups; + lua_assert(fs->bl == NULL); + ls->fs = fs->prev; + /* last token read was anchored in defunct function; must re-anchor it */ + anchor_token(ls); + L->top--; /* pop table of constants */ + luaC_checkGC(L); +} + + + +/*============================================================*/ +/* GRAMMAR RULES */ +/*============================================================*/ + + +/* +** check whether current token is in the follow set of a block. +** 'until' closes syntactical blocks, but do not close scope, +** so it handled in separate. +*/ +static int block_follow (LexState *ls, int withuntil) { + switch (ls->t.token) { + case TK_ELSE: case TK_ELSEIF: + case TK_END: case TK_EOS: + return 1; + case TK_UNTIL: return withuntil; + default: return 0; + } +} + + +static void statlist (LexState *ls) { + /* statlist -> { stat [`;'] } */ + while (!block_follow(ls, 1)) { + if (ls->t.token == TK_RETURN) { + statement(ls); + return; /* 'return' must be last statement */ + } + statement(ls); + } +} + + +static void fieldsel (LexState *ls, expdesc *v) { + /* fieldsel -> ['.' | ':'] NAME */ + FuncState *fs = ls->fs; + expdesc key; + luaK_exp2anyregup(fs, v); + luaX_next(ls); /* skip the dot or colon */ + checkname(ls, &key); + luaK_indexed(fs, v, &key); +} + + +static void yindex (LexState *ls, expdesc *v) { + /* index -> '[' expr ']' */ + luaX_next(ls); /* skip the '[' */ + expr(ls, v); + luaK_exp2val(ls->fs, v); + checknext(ls, ']'); +} + + +/* +** {====================================================================== +** Rules for Constructors +** ======================================================================= +*/ + + +struct ConsControl { + expdesc v; /* last list item read */ + expdesc *t; /* table descriptor */ + int nh; /* total number of `record' elements */ + int na; /* total number of array elements */ + int tostore; /* number of array elements pending to be stored */ +}; + + +static void recfield (LexState *ls, struct ConsControl *cc) { + /* recfield -> (NAME | `['exp1`]') = exp1 */ + FuncState *fs = ls->fs; + int reg = ls->fs->freereg; + expdesc key, val; + int rkkey; + if (ls->t.token == TK_NAME) { + checklimit(fs, cc->nh, MAX_INT, "items in a constructor"); + checkname(ls, &key); + } + else /* ls->t.token == '[' */ + yindex(ls, &key); + cc->nh++; + checknext(ls, '='); + rkkey = luaK_exp2RK(fs, &key); + expr(ls, &val); + luaK_codeABC(fs, OP_SETTABLE, cc->t->u.info, rkkey, luaK_exp2RK(fs, &val)); + fs->freereg = reg; /* free registers */ +} + + +static void closelistfield (FuncState *fs, struct ConsControl *cc) { + if (cc->v.k == VVOID) return; /* there is no list item */ + luaK_exp2nextreg(fs, &cc->v); + cc->v.k = VVOID; + if (cc->tostore == LFIELDS_PER_FLUSH) { + luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); /* flush */ + cc->tostore = 0; /* no more items pending */ + } +} + + +static void lastlistfield (FuncState *fs, struct ConsControl *cc) { + if (cc->tostore == 0) return; + if (hasmultret(cc->v.k)) { + luaK_setmultret(fs, &cc->v); + luaK_setlist(fs, cc->t->u.info, cc->na, LUA_MULTRET); + cc->na--; /* do not count last expression (unknown number of elements) */ + } + else { + if (cc->v.k != VVOID) + luaK_exp2nextreg(fs, &cc->v); + luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); + } +} + + +static void listfield (LexState *ls, struct ConsControl *cc) { + /* listfield -> exp */ + expr(ls, &cc->v); + checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor"); + cc->na++; + cc->tostore++; +} + + +static void field (LexState *ls, struct ConsControl *cc) { + /* field -> listfield | recfield */ + switch(ls->t.token) { + case TK_NAME: { /* may be 'listfield' or 'recfield' */ + if (luaX_lookahead(ls) != '=') /* expression? */ + listfield(ls, cc); + else + recfield(ls, cc); + break; + } + case '[': { + recfield(ls, cc); + break; + } + default: { + listfield(ls, cc); + break; + } + } +} + + +static void constructor (LexState *ls, expdesc *t) { + /* constructor -> '{' [ field { sep field } [sep] ] '}' + sep -> ',' | ';' */ + FuncState *fs = ls->fs; + int line = ls->linenumber; + int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); + struct ConsControl cc; + cc.na = cc.nh = cc.tostore = 0; + cc.t = t; + init_exp(t, VRELOCABLE, pc); + init_exp(&cc.v, VVOID, 0); /* no value (yet) */ + luaK_exp2nextreg(ls->fs, t); /* fix it at stack top */ + checknext(ls, '{'); + do { + lua_assert(cc.v.k == VVOID || cc.tostore > 0); + if (ls->t.token == '}') break; + closelistfield(fs, &cc); + field(ls, &cc); + } while (testnext(ls, ',') || testnext(ls, ';')); + check_match(ls, '}', '{', line); + lastlistfield(fs, &cc); + SETARG_B(fs->f->code[pc], luaO_int2fb(cc.na)); /* set initial array size */ + SETARG_C(fs->f->code[pc], luaO_int2fb(cc.nh)); /* set initial table size */ +} + +/* }====================================================================== */ + + + +static void parlist (LexState *ls) { + /* parlist -> [ param { `,' param } ] */ + FuncState *fs = ls->fs; + Proto *f = fs->f; + int nparams = 0; + f->is_vararg = 0; + if (ls->t.token != ')') { /* is `parlist' not empty? */ + do { + switch (ls->t.token) { + case TK_NAME: { /* param -> NAME */ + new_localvar(ls, str_checkname(ls)); + nparams++; + break; + } + case TK_DOTS: { /* param -> `...' */ + luaX_next(ls); + f->is_vararg = 1; + break; + } + default: luaX_syntaxerror(ls, " or " LUA_QL("...") " expected"); + } + } while (!f->is_vararg && testnext(ls, ',')); + } + adjustlocalvars(ls, nparams); + f->numparams = cast_byte(fs->nactvar); + luaK_reserveregs(fs, fs->nactvar); /* reserve register for parameters */ +} + + +static void body (LexState *ls, expdesc *e, int ismethod, int line) { + /* body -> `(' parlist `)' block END */ + FuncState new_fs; + BlockCnt bl; + new_fs.f = addprototype(ls); + new_fs.f->linedefined = line; + open_func(ls, &new_fs, &bl); + checknext(ls, '('); + if (ismethod) { + new_localvarliteral(ls, "self"); /* create 'self' parameter */ + adjustlocalvars(ls, 1); + } + parlist(ls); + checknext(ls, ')'); + statlist(ls); + new_fs.f->lastlinedefined = ls->linenumber; + check_match(ls, TK_END, TK_FUNCTION, line); + codeclosure(ls, e); + close_func(ls); +} + + +static int explist (LexState *ls, expdesc *v) { + /* explist -> expr { `,' expr } */ + int n = 1; /* at least one expression */ + expr(ls, v); + while (testnext(ls, ',')) { + luaK_exp2nextreg(ls->fs, v); + expr(ls, v); + n++; + } + return n; +} + + +static void funcargs (LexState *ls, expdesc *f, int line) { + FuncState *fs = ls->fs; + expdesc args; + int base, nparams; + switch (ls->t.token) { + case '(': { /* funcargs -> `(' [ explist ] `)' */ + luaX_next(ls); + if (ls->t.token == ')') /* arg list is empty? */ + args.k = VVOID; + else { + explist(ls, &args); + luaK_setmultret(fs, &args); + } + check_match(ls, ')', '(', line); + break; + } + case '{': { /* funcargs -> constructor */ + constructor(ls, &args); + break; + } + case TK_STRING: { /* funcargs -> STRING */ + codestring(ls, &args, ls->t.seminfo.ts); + luaX_next(ls); /* must use `seminfo' before `next' */ + break; + } + default: { + luaX_syntaxerror(ls, "function arguments expected"); + } + } + lua_assert(f->k == VNONRELOC); + base = f->u.info; /* base register for call */ + if (hasmultret(args.k)) + nparams = LUA_MULTRET; /* open call */ + else { + if (args.k != VVOID) + luaK_exp2nextreg(fs, &args); /* close last argument */ + nparams = fs->freereg - (base+1); + } + init_exp(f, VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2)); + luaK_fixline(fs, line); + fs->freereg = base+1; /* call remove function and arguments and leaves + (unless changed) one result */ +} + + + + +/* +** {====================================================================== +** Expression parsing +** ======================================================================= +*/ + + +static void primaryexp (LexState *ls, expdesc *v) { + /* primaryexp -> NAME | '(' expr ')' */ + switch (ls->t.token) { + case '(': { + int line = ls->linenumber; + luaX_next(ls); + expr(ls, v); + check_match(ls, ')', '(', line); + luaK_dischargevars(ls->fs, v); + return; + } + case TK_NAME: { + singlevar(ls, v); + return; + } + default: { + luaX_syntaxerror(ls, "unexpected symbol"); + } + } +} + + +static void suffixedexp (LexState *ls, expdesc *v) { + /* suffixedexp -> + primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ + FuncState *fs = ls->fs; + int line = ls->linenumber; + primaryexp(ls, v); + for (;;) { + switch (ls->t.token) { + case '.': { /* fieldsel */ + fieldsel(ls, v); + break; + } + case '[': { /* `[' exp1 `]' */ + expdesc key; + luaK_exp2anyregup(fs, v); + yindex(ls, &key); + luaK_indexed(fs, v, &key); + break; + } + case ':': { /* `:' NAME funcargs */ + expdesc key; + luaX_next(ls); + checkname(ls, &key); + luaK_self(fs, v, &key); + funcargs(ls, v, line); + break; + } + case '(': case TK_STRING: case '{': { /* funcargs */ + luaK_exp2nextreg(fs, v); + funcargs(ls, v, line); + break; + } + default: return; + } + } +} + + +static void simpleexp (LexState *ls, expdesc *v) { + /* simpleexp -> NUMBER | STRING | NIL | TRUE | FALSE | ... | + constructor | FUNCTION body | suffixedexp */ + switch (ls->t.token) { + case TK_NUMBER: { + init_exp(v, VKNUM, 0); + v->u.nval = ls->t.seminfo.r; + break; + } + case TK_STRING: { + codestring(ls, v, ls->t.seminfo.ts); + break; + } + case TK_NIL: { + init_exp(v, VNIL, 0); + break; + } + case TK_TRUE: { + init_exp(v, VTRUE, 0); + break; + } + case TK_FALSE: { + init_exp(v, VFALSE, 0); + break; + } + case TK_DOTS: { /* vararg */ + FuncState *fs = ls->fs; + check_condition(ls, fs->f->is_vararg, + "cannot use " LUA_QL("...") " outside a vararg function"); + init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 1, 0)); + break; + } + case '{': { /* constructor */ + constructor(ls, v); + return; + } + case TK_FUNCTION: { + luaX_next(ls); + body(ls, v, 0, ls->linenumber); + return; + } + default: { + suffixedexp(ls, v); + return; + } + } + luaX_next(ls); +} + + +static UnOpr getunopr (int op) { + switch (op) { + case TK_NOT: return OPR_NOT; + case '-': return OPR_MINUS; + case '#': return OPR_LEN; + default: return OPR_NOUNOPR; + } +} + + +static BinOpr getbinopr (int op) { + switch (op) { + case '+': return OPR_ADD; + case '-': return OPR_SUB; + case '*': return OPR_MUL; + case '/': return OPR_DIV; + case '%': return OPR_MOD; + case '^': return OPR_POW; + case TK_CONCAT: return OPR_CONCAT; + case TK_NE: return OPR_NE; + case TK_EQ: return OPR_EQ; + case '<': return OPR_LT; + case TK_LE: return OPR_LE; + case '>': return OPR_GT; + case TK_GE: return OPR_GE; + case TK_AND: return OPR_AND; + case TK_OR: return OPR_OR; + default: return OPR_NOBINOPR; + } +} + + +static const struct { + lu_byte left; /* left priority for each binary operator */ + lu_byte right; /* right priority */ +} priority[] = { /* ORDER OPR */ + {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, /* `+' `-' `*' `/' `%' */ + {10, 9}, {5, 4}, /* ^, .. (right associative) */ + {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ + {3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */ + {2, 2}, {1, 1} /* and, or */ +}; + +#define UNARY_PRIORITY 8 /* priority for unary operators */ + + +/* +** subexpr -> (simpleexp | unop subexpr) { binop subexpr } +** where `binop' is any binary operator with a priority higher than `limit' +*/ +static BinOpr subexpr (LexState *ls, expdesc *v, int limit) { + BinOpr op; + UnOpr uop; + enterlevel(ls); + uop = getunopr(ls->t.token); + if (uop != OPR_NOUNOPR) { + int line = ls->linenumber; + luaX_next(ls); + subexpr(ls, v, UNARY_PRIORITY); + luaK_prefix(ls->fs, uop, v, line); + } + else simpleexp(ls, v); + /* expand while operators have priorities higher than `limit' */ + op = getbinopr(ls->t.token); + while (op != OPR_NOBINOPR && priority[op].left > limit) { + expdesc v2; + BinOpr nextop; + int line = ls->linenumber; + luaX_next(ls); + luaK_infix(ls->fs, op, v); + /* read sub-expression with higher priority */ + nextop = subexpr(ls, &v2, priority[op].right); + luaK_posfix(ls->fs, op, v, &v2, line); + op = nextop; + } + leavelevel(ls); + return op; /* return first untreated operator */ +} + + +static void expr (LexState *ls, expdesc *v) { + subexpr(ls, v, 0); +} + +/* }==================================================================== */ + + + +/* +** {====================================================================== +** Rules for Statements +** ======================================================================= +*/ + + +static void block (LexState *ls) { + /* block -> statlist */ + FuncState *fs = ls->fs; + BlockCnt bl; + enterblock(fs, &bl, 0); + statlist(ls); + leaveblock(fs); +} + + +/* +** structure to chain all variables in the left-hand side of an +** assignment +*/ +struct LHS_assign { + struct LHS_assign *prev; + expdesc v; /* variable (global, local, upvalue, or indexed) */ +}; + + +/* +** check whether, in an assignment to an upvalue/local variable, the +** upvalue/local variable is begin used in a previous assignment to a +** table. If so, save original upvalue/local value in a safe place and +** use this safe copy in the previous assignment. +*/ +static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { + FuncState *fs = ls->fs; + int extra = fs->freereg; /* eventual position to save local variable */ + int conflict = 0; + for (; lh; lh = lh->prev) { /* check all previous assignments */ + if (lh->v.k == VINDEXED) { /* assigning to a table? */ + /* table is the upvalue/local being assigned now? */ + if (lh->v.u.ind.vt == v->k && lh->v.u.ind.t == v->u.info) { + conflict = 1; + lh->v.u.ind.vt = VLOCAL; + lh->v.u.ind.t = extra; /* previous assignment will use safe copy */ + } + /* index is the local being assigned? (index cannot be upvalue) */ + if (v->k == VLOCAL && lh->v.u.ind.idx == v->u.info) { + conflict = 1; + lh->v.u.ind.idx = extra; /* previous assignment will use safe copy */ + } + } + } + if (conflict) { + /* copy upvalue/local value to a temporary (in position 'extra') */ + OpCode op = (v->k == VLOCAL) ? OP_MOVE : OP_GETUPVAL; + luaK_codeABC(fs, op, extra, v->u.info, 0); + luaK_reserveregs(fs, 1); + } +} + + +static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) { + expdesc e; + check_condition(ls, vkisvar(lh->v.k), "syntax error"); + if (testnext(ls, ',')) { /* assignment -> ',' suffixedexp assignment */ + struct LHS_assign nv; + nv.prev = lh; + suffixedexp(ls, &nv.v); + if (nv.v.k != VINDEXED) + check_conflict(ls, lh, &nv.v); + checklimit(ls->fs, nvars + ls->L->nCcalls, LUAI_MAXCCALLS, + "C levels"); + assignment(ls, &nv, nvars+1); + } + else { /* assignment -> `=' explist */ + int nexps; + checknext(ls, '='); + nexps = explist(ls, &e); + if (nexps != nvars) { + adjust_assign(ls, nvars, nexps, &e); + if (nexps > nvars) + ls->fs->freereg -= nexps - nvars; /* remove extra values */ + } + else { + luaK_setoneret(ls->fs, &e); /* close last expression */ + luaK_storevar(ls->fs, &lh->v, &e); + return; /* avoid default */ + } + } + init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */ + luaK_storevar(ls->fs, &lh->v, &e); +} + + +static int cond (LexState *ls) { + /* cond -> exp */ + expdesc v; + expr(ls, &v); /* read condition */ + if (v.k == VNIL) v.k = VFALSE; /* `falses' are all equal here */ + luaK_goiftrue(ls->fs, &v); + return v.f; +} + + +static void gotostat (LexState *ls, int pc) { + int line = ls->linenumber; + TString *label; + int g; + if (testnext(ls, TK_GOTO)) + label = str_checkname(ls); + else { + luaX_next(ls); /* skip break */ + label = luaS_new(ls->L, "break"); + } + g = newlabelentry(ls, &ls->dyd->gt, label, line, pc); + findlabel(ls, g); /* close it if label already defined */ +} + + +/* check for repeated labels on the same block */ +static void checkrepeated (FuncState *fs, Labellist *ll, TString *label) { + int i; + for (i = fs->bl->firstlabel; i < ll->n; i++) { + if (luaS_eqstr(label, ll->arr[i].name)) { + const char *msg = luaO_pushfstring(fs->ls->L, + "label " LUA_QS " already defined on line %d", + getstr(label), ll->arr[i].line); + semerror(fs->ls, msg); + } + } +} + + +/* skip no-op statements */ +static void skipnoopstat (LexState *ls) { + while (ls->t.token == ';' || ls->t.token == TK_DBCOLON) + statement(ls); +} + + +static void labelstat (LexState *ls, TString *label, int line) { + /* label -> '::' NAME '::' */ + FuncState *fs = ls->fs; + Labellist *ll = &ls->dyd->label; + int l; /* index of new label being created */ + checkrepeated(fs, ll, label); /* check for repeated labels */ + checknext(ls, TK_DBCOLON); /* skip double colon */ + /* create new entry for this label */ + l = newlabelentry(ls, ll, label, line, fs->pc); + skipnoopstat(ls); /* skip other no-op statements */ + if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */ + /* assume that locals are already out of scope */ + ll->arr[l].nactvar = fs->bl->nactvar; + } + findgotos(ls, &ll->arr[l]); +} + + +static void whilestat (LexState *ls, int line) { + /* whilestat -> WHILE cond DO block END */ + FuncState *fs = ls->fs; + int whileinit; + int condexit; + BlockCnt bl; + luaX_next(ls); /* skip WHILE */ + whileinit = luaK_getlabel(fs); + condexit = cond(ls); + enterblock(fs, &bl, 1); + checknext(ls, TK_DO); + block(ls); + luaK_jumpto(fs, whileinit); + check_match(ls, TK_END, TK_WHILE, line); + leaveblock(fs); + luaK_patchtohere(fs, condexit); /* false conditions finish the loop */ +} + + +static void repeatstat (LexState *ls, int line) { + /* repeatstat -> REPEAT block UNTIL cond */ + int condexit; + FuncState *fs = ls->fs; + int repeat_init = luaK_getlabel(fs); + BlockCnt bl1, bl2; + enterblock(fs, &bl1, 1); /* loop block */ + enterblock(fs, &bl2, 0); /* scope block */ + luaX_next(ls); /* skip REPEAT */ + statlist(ls); + check_match(ls, TK_UNTIL, TK_REPEAT, line); + condexit = cond(ls); /* read condition (inside scope block) */ + if (bl2.upval) /* upvalues? */ + luaK_patchclose(fs, condexit, bl2.nactvar); + leaveblock(fs); /* finish scope */ + luaK_patchlist(fs, condexit, repeat_init); /* close the loop */ + leaveblock(fs); /* finish loop */ +} + + +static int exp1 (LexState *ls) { + expdesc e; + int reg; + expr(ls, &e); + luaK_exp2nextreg(ls->fs, &e); + lua_assert(e.k == VNONRELOC); + reg = e.u.info; + return reg; +} + + +static void forbody (LexState *ls, int base, int line, int nvars, int isnum) { + /* forbody -> DO block */ + BlockCnt bl; + FuncState *fs = ls->fs; + int prep, endfor; + adjustlocalvars(ls, 3); /* control variables */ + checknext(ls, TK_DO); + prep = isnum ? luaK_codeAsBx(fs, OP_FORPREP, base, NO_JUMP) : luaK_jump(fs); + enterblock(fs, &bl, 0); /* scope for declared variables */ + adjustlocalvars(ls, nvars); + luaK_reserveregs(fs, nvars); + block(ls); + leaveblock(fs); /* end of scope for declared variables */ + luaK_patchtohere(fs, prep); + if (isnum) /* numeric for? */ + endfor = luaK_codeAsBx(fs, OP_FORLOOP, base, NO_JUMP); + else { /* generic for */ + luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars); + luaK_fixline(fs, line); + endfor = luaK_codeAsBx(fs, OP_TFORLOOP, base + 2, NO_JUMP); + } + luaK_patchlist(fs, endfor, prep + 1); + luaK_fixline(fs, line); +} + + +static void fornum (LexState *ls, TString *varname, int line) { + /* fornum -> NAME = exp1,exp1[,exp1] forbody */ + FuncState *fs = ls->fs; + int base = fs->freereg; + new_localvarliteral(ls, "(for index)"); + new_localvarliteral(ls, "(for limit)"); + new_localvarliteral(ls, "(for step)"); + new_localvar(ls, varname); + checknext(ls, '='); + exp1(ls); /* initial value */ + checknext(ls, ','); + exp1(ls); /* limit */ + if (testnext(ls, ',')) + exp1(ls); /* optional step */ + else { /* default step = 1 */ + luaK_codek(fs, fs->freereg, luaK_numberK(fs, 1)); + luaK_reserveregs(fs, 1); + } + forbody(ls, base, line, 1, 1); +} + + +static void forlist (LexState *ls, TString *indexname) { + /* forlist -> NAME {,NAME} IN explist forbody */ + FuncState *fs = ls->fs; + expdesc e; + int nvars = 4; /* gen, state, control, plus at least one declared var */ + int line; + int base = fs->freereg; + /* create control variables */ + new_localvarliteral(ls, "(for generator)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for control)"); + /* create declared variables */ + new_localvar(ls, indexname); + while (testnext(ls, ',')) { + new_localvar(ls, str_checkname(ls)); + nvars++; + } + checknext(ls, TK_IN); + line = ls->linenumber; + adjust_assign(ls, 3, explist(ls, &e), &e); + luaK_checkstack(fs, 3); /* extra space to call generator */ + forbody(ls, base, line, nvars - 3, 0); +} + + +static void forstat (LexState *ls, int line) { + /* forstat -> FOR (fornum | forlist) END */ + FuncState *fs = ls->fs; + TString *varname; + BlockCnt bl; + enterblock(fs, &bl, 1); /* scope for loop and control variables */ + luaX_next(ls); /* skip `for' */ + varname = str_checkname(ls); /* first variable name */ + switch (ls->t.token) { + case '=': fornum(ls, varname, line); break; + case ',': case TK_IN: forlist(ls, varname); break; + default: luaX_syntaxerror(ls, LUA_QL("=") " or " LUA_QL("in") " expected"); + } + check_match(ls, TK_END, TK_FOR, line); + leaveblock(fs); /* loop scope (`break' jumps to this point) */ +} + + +static void test_then_block (LexState *ls, int *escapelist) { + /* test_then_block -> [IF | ELSEIF] cond THEN block */ + BlockCnt bl; + FuncState *fs = ls->fs; + expdesc v; + int jf; /* instruction to skip 'then' code (if condition is false) */ + luaX_next(ls); /* skip IF or ELSEIF */ + expr(ls, &v); /* read condition */ + checknext(ls, TK_THEN); + if (ls->t.token == TK_GOTO || ls->t.token == TK_BREAK) { + luaK_goiffalse(ls->fs, &v); /* will jump to label if condition is true */ + enterblock(fs, &bl, 0); /* must enter block before 'goto' */ + gotostat(ls, v.t); /* handle goto/break */ + skipnoopstat(ls); /* skip other no-op statements */ + if (block_follow(ls, 0)) { /* 'goto' is the entire block? */ + leaveblock(fs); + return; /* and that is it */ + } + else /* must skip over 'then' part if condition is false */ + jf = luaK_jump(fs); + } + else { /* regular case (not goto/break) */ + luaK_goiftrue(ls->fs, &v); /* skip over block if condition is false */ + enterblock(fs, &bl, 0); + jf = v.f; + } + statlist(ls); /* `then' part */ + leaveblock(fs); + if (ls->t.token == TK_ELSE || + ls->t.token == TK_ELSEIF) /* followed by 'else'/'elseif'? */ + luaK_concat(fs, escapelist, luaK_jump(fs)); /* must jump over it */ + luaK_patchtohere(fs, jf); +} + + +static void ifstat (LexState *ls, int line) { + /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */ + FuncState *fs = ls->fs; + int escapelist = NO_JUMP; /* exit list for finished parts */ + test_then_block(ls, &escapelist); /* IF cond THEN block */ + while (ls->t.token == TK_ELSEIF) + test_then_block(ls, &escapelist); /* ELSEIF cond THEN block */ + if (testnext(ls, TK_ELSE)) + block(ls); /* `else' part */ + check_match(ls, TK_END, TK_IF, line); + luaK_patchtohere(fs, escapelist); /* patch escape list to 'if' end */ +} + + +static void localfunc (LexState *ls) { + expdesc b; + FuncState *fs = ls->fs; + new_localvar(ls, str_checkname(ls)); /* new local variable */ + adjustlocalvars(ls, 1); /* enter its scope */ + body(ls, &b, 0, ls->linenumber); /* function created in next register */ + /* debug information will only see the variable after this point! */ + getlocvar(fs, b.u.info)->startpc = fs->pc; +} + + +static void localstat (LexState *ls) { + /* stat -> LOCAL NAME {`,' NAME} [`=' explist] */ + int nvars = 0; + int nexps; + expdesc e; + do { + new_localvar(ls, str_checkname(ls)); + nvars++; + } while (testnext(ls, ',')); + if (testnext(ls, '=')) + nexps = explist(ls, &e); + else { + e.k = VVOID; + nexps = 0; + } + adjust_assign(ls, nvars, nexps, &e); + adjustlocalvars(ls, nvars); +} + + +static int funcname (LexState *ls, expdesc *v) { + /* funcname -> NAME {fieldsel} [`:' NAME] */ + int ismethod = 0; + singlevar(ls, v); + while (ls->t.token == '.') + fieldsel(ls, v); + if (ls->t.token == ':') { + ismethod = 1; + fieldsel(ls, v); + } + return ismethod; +} + + +static void funcstat (LexState *ls, int line) { + /* funcstat -> FUNCTION funcname body */ + int ismethod; + expdesc v, b; + luaX_next(ls); /* skip FUNCTION */ + ismethod = funcname(ls, &v); + body(ls, &b, ismethod, line); + luaK_storevar(ls->fs, &v, &b); + luaK_fixline(ls->fs, line); /* definition `happens' in the first line */ +} + + +static void exprstat (LexState *ls) { + /* stat -> func | assignment */ + FuncState *fs = ls->fs; + struct LHS_assign v; + suffixedexp(ls, &v.v); + if (ls->t.token == '=' || ls->t.token == ',') { /* stat -> assignment ? */ + v.prev = NULL; + assignment(ls, &v, 1); + } + else { /* stat -> func */ + check_condition(ls, v.v.k == VCALL, "syntax error"); + SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ + } +} + + +static void retstat (LexState *ls) { + /* stat -> RETURN [explist] [';'] */ + FuncState *fs = ls->fs; + expdesc e; + int first, nret; /* registers with returned values */ + if (block_follow(ls, 1) || ls->t.token == ';') + first = nret = 0; /* return no values */ + else { + nret = explist(ls, &e); /* optional return values */ + if (hasmultret(e.k)) { + luaK_setmultret(fs, &e); + if (e.k == VCALL && nret == 1) { /* tail call? */ + SET_OPCODE(getcode(fs,&e), OP_TAILCALL); + lua_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar); + } + first = fs->nactvar; + nret = LUA_MULTRET; /* return all values */ + } + else { + if (nret == 1) /* only one single value? */ + first = luaK_exp2anyreg(fs, &e); + else { + luaK_exp2nextreg(fs, &e); /* values must go to the `stack' */ + first = fs->nactvar; /* return all `active' values */ + lua_assert(nret == fs->freereg - first); + } + } + } + luaK_ret(fs, first, nret); + testnext(ls, ';'); /* skip optional semicolon */ +} + + +static void statement (LexState *ls) { + int line = ls->linenumber; /* may be needed for error messages */ + enterlevel(ls); + switch (ls->t.token) { + case ';': { /* stat -> ';' (empty statement) */ + luaX_next(ls); /* skip ';' */ + break; + } + case TK_IF: { /* stat -> ifstat */ + ifstat(ls, line); + break; + } + case TK_WHILE: { /* stat -> whilestat */ + whilestat(ls, line); + break; + } + case TK_DO: { /* stat -> DO block END */ + luaX_next(ls); /* skip DO */ + block(ls); + check_match(ls, TK_END, TK_DO, line); + break; + } + case TK_FOR: { /* stat -> forstat */ + forstat(ls, line); + break; + } + case TK_REPEAT: { /* stat -> repeatstat */ + repeatstat(ls, line); + break; + } + case TK_FUNCTION: { /* stat -> funcstat */ + funcstat(ls, line); + break; + } + case TK_LOCAL: { /* stat -> localstat */ + luaX_next(ls); /* skip LOCAL */ + if (testnext(ls, TK_FUNCTION)) /* local function? */ + localfunc(ls); + else + localstat(ls); + break; + } + case TK_DBCOLON: { /* stat -> label */ + luaX_next(ls); /* skip double colon */ + labelstat(ls, str_checkname(ls), line); + break; + } + case TK_RETURN: { /* stat -> retstat */ + luaX_next(ls); /* skip RETURN */ + retstat(ls); + break; + } + case TK_BREAK: /* stat -> breakstat */ + case TK_GOTO: { /* stat -> 'goto' NAME */ + gotostat(ls, luaK_jump(ls->fs)); + break; + } + default: { /* stat -> func | assignment */ + exprstat(ls); + break; + } + } + lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && + ls->fs->freereg >= ls->fs->nactvar); + ls->fs->freereg = ls->fs->nactvar; /* free registers */ + leavelevel(ls); +} + +/* }====================================================================== */ + + +/* +** compiles the main function, which is a regular vararg function with an +** upvalue named LUA_ENV +*/ +static void mainfunc (LexState *ls, FuncState *fs) { + BlockCnt bl; + expdesc v; + open_func(ls, fs, &bl); + fs->f->is_vararg = 1; /* main function is always vararg */ + init_exp(&v, VLOCAL, 0); /* create and... */ + newupvalue(fs, ls->envn, &v); /* ...set environment upvalue */ + luaX_next(ls); /* read first token */ + statlist(ls); /* parse main body */ + check(ls, TK_EOS); + close_func(ls); +} + + +Closure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar) { + LexState lexstate; + FuncState funcstate; + Closure *cl = luaF_newLclosure(L, 1); /* create main closure */ + /* anchor closure (to avoid being collected) */ + setclLvalue(L, L->top, cl); + incr_top(L); + funcstate.f = cl->l.p = luaF_newproto(L); + funcstate.f->source = luaS_new(L, name); /* create and anchor TString */ + lexstate.buff = buff; + lexstate.dyd = dyd; + dyd->actvar.n = dyd->gt.n = dyd->label.n = 0; + luaX_setinput(L, &lexstate, z, funcstate.f->source, firstchar); + mainfunc(&lexstate, &funcstate); + lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs); + /* all scopes should be correctly finished */ + lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0); + return cl; /* it's on the stack too */ +} + diff --git a/luprex/ext/eris-master/src/lparser.h b/luprex/ext/eris-master/src/lparser.h new file mode 100644 index 00000000..0346e3c4 --- /dev/null +++ b/luprex/ext/eris-master/src/lparser.h @@ -0,0 +1,119 @@ +/* +** $Id: lparser.h,v 1.70.1.1 2013/04/12 18:48:47 roberto Exp $ +** Lua Parser +** See Copyright Notice in lua.h +*/ + +#ifndef lparser_h +#define lparser_h + +#include "llimits.h" +#include "lobject.h" +#include "lzio.h" + + +/* +** Expression descriptor +*/ + +typedef enum { + VVOID, /* no value */ + VNIL, + VTRUE, + VFALSE, + VK, /* info = index of constant in `k' */ + VKNUM, /* nval = numerical value */ + VNONRELOC, /* info = result register */ + VLOCAL, /* info = local register */ + VUPVAL, /* info = index of upvalue in 'upvalues' */ + VINDEXED, /* t = table register/upvalue; idx = index R/K */ + VJMP, /* info = instruction pc */ + VRELOCABLE, /* info = instruction pc */ + VCALL, /* info = instruction pc */ + VVARARG /* info = instruction pc */ +} expkind; + + +#define vkisvar(k) (VLOCAL <= (k) && (k) <= VINDEXED) +#define vkisinreg(k) ((k) == VNONRELOC || (k) == VLOCAL) + +typedef struct expdesc { + expkind k; + union { + struct { /* for indexed variables (VINDEXED) */ + short idx; /* index (R/K) */ + lu_byte t; /* table (register or upvalue) */ + lu_byte vt; /* whether 't' is register (VLOCAL) or upvalue (VUPVAL) */ + } ind; + int info; /* for generic use */ + lua_Number nval; /* for VKNUM */ + } u; + int t; /* patch list of `exit when true' */ + int f; /* patch list of `exit when false' */ +} expdesc; + + +/* description of active local variable */ +typedef struct Vardesc { + short idx; /* variable index in stack */ +} Vardesc; + + +/* description of pending goto statements and label statements */ +typedef struct Labeldesc { + TString *name; /* label identifier */ + int pc; /* position in code */ + int line; /* line where it appeared */ + lu_byte nactvar; /* local level where it appears in current block */ +} Labeldesc; + + +/* list of labels or gotos */ +typedef struct Labellist { + Labeldesc *arr; /* array */ + int n; /* number of entries in use */ + int size; /* array size */ +} Labellist; + + +/* dynamic structures used by the parser */ +typedef struct Dyndata { + struct { /* list of active local variables */ + Vardesc *arr; + int n; + int size; + } actvar; + Labellist gt; /* list of pending gotos */ + Labellist label; /* list of active labels */ +} Dyndata; + + +/* control of blocks */ +struct BlockCnt; /* defined in lparser.c */ + + +/* state needed to generate code for a given function */ +typedef struct FuncState { + Proto *f; /* current function header */ + Table *h; /* table to find (and reuse) elements in `k' */ + struct FuncState *prev; /* enclosing function */ + struct LexState *ls; /* lexical state */ + struct BlockCnt *bl; /* chain of current blocks */ + int pc; /* next position to code (equivalent to `ncode') */ + int lasttarget; /* 'label' of last 'jump label' */ + int jpc; /* list of pending jumps to `pc' */ + int nk; /* number of elements in `k' */ + int np; /* number of elements in `p' */ + int firstlocal; /* index of first local var (in Dyndata array) */ + short nlocvars; /* number of elements in 'f->locvars' */ + lu_byte nactvar; /* number of active local variables */ + lu_byte nups; /* number of upvalues */ + lu_byte freereg; /* first free register */ +} FuncState; + + +LUAI_FUNC Closure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar); + + +#endif diff --git a/luprex/ext/eris-master/src/lstate.c b/luprex/ext/eris-master/src/lstate.c new file mode 100644 index 00000000..2fe6c503 --- /dev/null +++ b/luprex/ext/eris-master/src/lstate.c @@ -0,0 +1,313 @@ +/* +** $Id: lstate.c,v 2.99.1.2 2013/11/08 17:45:31 roberto Exp $ +** Global State +** See Copyright Notice in lua.h +*/ + + +#include +#include + +#define lstate_c +#define LUA_CORE + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "llex.h" +#include "lmem.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + +#if !defined(LUAI_GCPAUSE) +#define LUAI_GCPAUSE 200 /* 200% */ +#endif + +#if !defined(LUAI_GCMAJOR) +#define LUAI_GCMAJOR 200 /* 200% */ +#endif + +#if !defined(LUAI_GCMUL) +#define LUAI_GCMUL 200 /* GC runs 'twice the speed' of memory allocation */ +#endif + + +#define MEMERRMSG "not enough memory" + + +/* +** a macro to help the creation of a unique random seed when a state is +** created; the seed is used to randomize hashes. +*/ +#if !defined(luai_makeseed) +#include +#define luai_makeseed() 1234 +#endif + + + +/* +** thread state + extra space +*/ +typedef struct LX { +#if defined(LUAI_EXTRASPACE) + char buff[LUAI_EXTRASPACE]; +#endif + lua_State l; +} LX; + + +/* +** Main thread combines a thread state and the global state +*/ +typedef struct LG { + LX l; + global_State g; +} LG; + + + +#define fromstate(L) (cast(LX *, cast(lu_byte *, (L)) - offsetof(LX, l))) + + +/* +** Compute an initial seed as random as possible. In ANSI, rely on +** Address Space Layout Randomization (if present) to increase +** randomness.. +*/ + +static unsigned int makeseed (lua_State *L) { + return 1234; +} + + +/* +** set GCdebt to a new value keeping the value (totalbytes + GCdebt) +** invariant +*/ +void luaE_setdebt (global_State *g, l_mem debt) { + g->totalbytes -= (debt - g->GCdebt); + g->GCdebt = debt; +} + + +CallInfo *luaE_extendCI (lua_State *L) { + CallInfo *ci = luaM_new(L, CallInfo); + lua_assert(L->ci->next == NULL); + L->ci->next = ci; + ci->previous = L->ci; + ci->next = NULL; + return ci; +} + + +void luaE_freeCI (lua_State *L) { + CallInfo *ci = L->ci; + CallInfo *next = ci->next; + ci->next = NULL; + while ((ci = next) != NULL) { + next = ci->next; + luaM_free(L, ci); + } +} + + +static void stack_init (lua_State *L1, lua_State *L) { + int i; CallInfo *ci; + /* initialize stack array */ + L1->stack = luaM_newvector(L, BASIC_STACK_SIZE, TValue); + L1->stacksize = BASIC_STACK_SIZE; + for (i = 0; i < BASIC_STACK_SIZE; i++) + setnilvalue(L1->stack + i); /* erase new stack */ + L1->top = L1->stack; + L1->stack_last = L1->stack + L1->stacksize - EXTRA_STACK; + /* initialize first ci */ + ci = &L1->base_ci; + ci->next = ci->previous = NULL; + ci->callstatus = 0; + ci->func = L1->top; + setnilvalue(L1->top++); /* 'function' entry for this 'ci' */ + ci->top = L1->top + LUA_MINSTACK; + L1->ci = ci; +} + + +static void freestack (lua_State *L) { + if (L->stack == NULL) + return; /* stack not completely built yet */ + L->ci = &L->base_ci; /* free the entire 'ci' list */ + luaE_freeCI(L); + luaM_freearray(L, L->stack, L->stacksize); /* free stack array */ +} + + +/* +** Create registry table and its predefined values +*/ +static void init_registry (lua_State *L, global_State *g) { + TValue mt; + /* create registry */ + Table *registry = luaH_new(L); + sethvalue(L, &g->l_registry, registry); + luaH_resize(L, registry, LUA_RIDX_LAST, 0); + /* registry[LUA_RIDX_MAINTHREAD] = L */ + setthvalue(L, &mt, L); + luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &mt); + /* registry[LUA_RIDX_GLOBALS] = table of globals */ + sethvalue(L, &mt, luaH_new(L)); + luaH_setint(L, registry, LUA_RIDX_GLOBALS, &mt); +} + + +/* +** open parts of the state that may cause memory-allocation errors +*/ +static void f_luaopen (lua_State *L, void *ud) { + global_State *g = G(L); + UNUSED(ud); + stack_init(L, L); /* init stack */ + init_registry(L, g); + luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ + luaT_init(L); + luaX_init(L); + /* pre-create memory-error message */ + g->memerrmsg = luaS_newliteral(L, MEMERRMSG); + luaS_fix(g->memerrmsg); /* it should never be collected */ + g->gcrunning = 1; /* allow gc */ + g->version = lua_version(NULL); + luai_userstateopen(L); +} + + +/* +** preinitialize a state with consistent values without allocating +** any memory (to avoid errors) +*/ +static void preinit_state (lua_State *L, global_State *g) { + G(L) = g; + L->stack = NULL; + L->ci = NULL; + L->stacksize = 0; + L->errorJmp = NULL; + L->nCcalls = 0; + L->hook = NULL; + L->hookmask = 0; + L->basehookcount = 0; + L->allowhook = 1; + resethookcount(L); + L->openupval = NULL; + L->nny = 1; + L->status = LUA_OK; + L->errfunc = 0; + L->nextid = 0; +} + + +static void close_state (lua_State *L) { + global_State *g = G(L); + luaF_close(L, L->stack); /* close all upvalues for this thread */ + luaC_freeallobjects(L); /* collect all objects */ + if (g->version) /* closing a fully built state? */ + luai_userstateclose(L); + luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size); + luaZ_freebuffer(L, &g->buff); + freestack(L); + lua_assert(gettotalbytes(g) == sizeof(LG)); + (*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */ +} + + +LUA_API lua_State *lua_newthread (lua_State *L) { + lua_State *L1; + lua_lock(L); + luaC_checkGC(L); + L1 = &luaC_newobj(L, LUA_TTHREAD, sizeof(LX), NULL, offsetof(LX, l))->th; + setthvalue(L, L->top, L1); + api_incr_top(L); + preinit_state(L1, G(L)); + L1->hookmask = L->hookmask; + L1->basehookcount = L->basehookcount; + L1->hook = L->hook; + resethookcount(L1); + luai_userstatethread(L, L1); + stack_init(L1, L); /* init stack */ + lua_unlock(L); + return L1; +} + + +void luaE_freethread (lua_State *L, lua_State *L1) { + LX *l = fromstate(L1); + luaF_close(L1, L1->stack); /* close all upvalues for this thread */ + lua_assert(L1->openupval == NULL); + luai_userstatefree(L, L1); + freestack(L1); + luaM_free(L, l); +} + + +LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { + int i; + lua_State *L; + global_State *g; + LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); + if (l == NULL) return NULL; + L = &l->l.l; + g = &l->g; + L->next = NULL; + L->tt = LUA_TTHREAD; + g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT); + L->marked = luaC_white(g); + g->gckind = KGC_NORMAL; + preinit_state(L, g); + g->frealloc = f; + g->ud = ud; + g->mainthread = L; + g->seed = makeseed(L); + g->uvhead.u.l.prev = &g->uvhead; + g->uvhead.u.l.next = &g->uvhead; + g->gcrunning = 0; /* no GC while building state */ + g->GCestimate = 0; + g->strt.size = 0; + g->strt.nuse = 0; + g->strt.hash = NULL; + setnilvalue(&g->l_registry); + luaZ_initbuffer(L, &g->buff); + g->panic = NULL; + g->version = NULL; + g->gcstate = GCSpause; + g->allgc = NULL; + g->finobj = NULL; + g->tobefnz = NULL; + g->sweepgc = g->sweepfin = NULL; + g->gray = g->grayagain = NULL; + g->weak = g->ephemeron = g->allweak = NULL; + g->totalbytes = sizeof(LG); + g->GCdebt = 0; + g->gcpause = LUAI_GCPAUSE; + g->gcmajorinc = LUAI_GCMAJOR; + g->gcstepmul = LUAI_GCMUL; + for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL; + if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) { + /* memory allocation error: free partial state */ + close_state(L); + L = NULL; + } + return L; +} + + +LUA_API void lua_close (lua_State *L) { + L = G(L)->mainthread; /* only the main thread can be closed */ + lua_lock(L); + close_state(L); +} + + diff --git a/luprex/ext/eris-master/src/lstate.h b/luprex/ext/eris-master/src/lstate.h new file mode 100644 index 00000000..7a26c7c9 --- /dev/null +++ b/luprex/ext/eris-master/src/lstate.h @@ -0,0 +1,229 @@ +/* +** $Id: lstate.h,v 2.82.1.1 2013/04/12 18:48:47 roberto Exp $ +** Global State +** See Copyright Notice in lua.h +*/ + +#ifndef lstate_h +#define lstate_h + +#include "lua.h" + +#include "lobject.h" +#include "ltm.h" +#include "lzio.h" + + +/* + +** Some notes about garbage-collected objects: All objects in Lua must +** be kept somehow accessible until being freed. +** +** Lua keeps most objects linked in list g->allgc. The link uses field +** 'next' of the CommonHeader. +** +** Strings are kept in several lists headed by the array g->strt.hash. +** +** Open upvalues are not subject to independent garbage collection. They +** are collected together with their respective threads. Lua keeps a +** double-linked list with all open upvalues (g->uvhead) so that it can +** mark objects referred by them. (They are always gray, so they must +** be remarked in the atomic step. Usually their contents would be marked +** when traversing the respective threads, but the thread may already be +** dead, while the upvalue is still accessible through closures.) +** +** Objects with finalizers are kept in the list g->finobj. +** +** The list g->tobefnz links all objects being finalized. + +*/ + + +struct lua_longjmp; /* defined in ldo.c */ + + + +/* extra stack space to handle TM calls and some other extras */ +#define EXTRA_STACK 5 + + +#define BASIC_STACK_SIZE (2*LUA_MINSTACK) + + +/* kinds of Garbage Collection */ +#define KGC_NORMAL 0 +#define KGC_EMERGENCY 1 /* gc was forced by an allocation failure */ +#define KGC_GEN 2 /* generational collection */ + + +typedef struct stringtable { + GCObject **hash; + lu_int32 nuse; /* number of elements */ + int size; +} stringtable; + + +/* +** information about a call +*/ +typedef struct CallInfo { + StkId func; /* function index in the stack */ + StkId top; /* top for this function */ + struct CallInfo *previous, *next; /* dynamic call link */ + short nresults; /* expected number of results from this function */ + lu_byte callstatus; + ptrdiff_t extra; + union { + struct { /* only for Lua functions */ + StkId base; /* base for this function */ + const Instruction *savedpc; + } l; + struct { /* only for C functions */ + int ctx; /* context info. in case of yields */ + lua_CFunction k; /* continuation in case of yields */ + ptrdiff_t old_errfunc; + lu_byte old_allowhook; + lu_byte status; + } c; + } u; +} CallInfo; + + +/* +** Bits in CallInfo status +*/ +#define CIST_LUA (1<<0) /* call is running a Lua function */ +#define CIST_HOOKED (1<<1) /* call is running a debug hook */ +#define CIST_REENTRY (1<<2) /* call is running on same invocation of + luaV_execute of previous call */ +#define CIST_YIELDED (1<<3) /* call reentered after suspension */ +#define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ +#define CIST_STAT (1<<5) /* call has an error status (pcall) */ +#define CIST_TAIL (1<<6) /* call was tail called */ +#define CIST_HOOKYIELD (1<<7) /* last hook called yielded */ + + +#define isLua(ci) ((ci)->callstatus & CIST_LUA) + + +/* +** `global state', shared by all threads of this state +*/ +typedef struct global_State { + lua_Alloc frealloc; /* function to reallocate memory */ + void *ud; /* auxiliary data to `frealloc' */ + lu_mem totalbytes; /* number of bytes currently allocated - GCdebt */ + l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ + lu_mem GCmemtrav; /* memory traversed by the GC */ + lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ + stringtable strt; /* hash table for strings */ + TValue l_registry; + unsigned int seed; /* randomized seed for hashes */ + lu_byte currentwhite; + lu_byte gcstate; /* state of garbage collector */ + lu_byte gckind; /* kind of GC running */ + lu_byte gcrunning; /* true if GC is running */ + int sweepstrgc; /* position of sweep in `strt' */ + GCObject *allgc; /* list of all collectable objects */ + GCObject *finobj; /* list of collectable objects with finalizers */ + GCObject **sweepgc; /* current position of sweep in list 'allgc' */ + GCObject **sweepfin; /* current position of sweep in list 'finobj' */ + GCObject *gray; /* list of gray objects */ + GCObject *grayagain; /* list of objects to be traversed atomically */ + GCObject *weak; /* list of tables with weak values */ + GCObject *ephemeron; /* list of ephemeron tables (weak keys) */ + GCObject *allweak; /* list of all-weak tables */ + GCObject *tobefnz; /* list of userdata to be GC */ + UpVal uvhead; /* head of double-linked list of all open upvalues */ + Mbuffer buff; /* temporary buffer for string concatenation */ + int gcpause; /* size of pause between successive GCs */ + int gcmajorinc; /* pause between major collections (only in gen. mode) */ + int gcstepmul; /* GC `granularity' */ + lua_CFunction panic; /* to be called in unprotected errors */ + struct lua_State *mainthread; + const lua_Number *version; /* pointer to version number */ + TString *memerrmsg; /* memory-error message */ + TString *tmname[TM_N]; /* array with tag-method names */ + struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */ +} global_State; + + +/* +** `per thread' state +*/ +struct lua_State { + CommonHeader; + lu_byte status; + StkId top; /* first free slot in the stack */ + global_State *l_G; + CallInfo *ci; /* call info for current function */ + const Instruction *oldpc; /* last pc traced */ + StkId stack_last; /* last free slot in the stack */ + StkId stack; /* stack base */ + int stacksize; + unsigned short nny; /* number of non-yieldable calls in stack */ + unsigned short nCcalls; /* number of nested C calls */ + lu_byte hookmask; + lu_byte allowhook; + int basehookcount; + int hookcount; + lua_Hook hook; + GCObject *openupval; /* list of open upvalues in this stack */ + GCObject *gclist; + struct lua_longjmp *errorJmp; /* current error recover point */ + ptrdiff_t errfunc; /* current error handling function (stack index) */ + CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ + lua_Integer nextid; /* ID allocator for luprex */ +}; + + +#define G(L) (L->l_G) + + +/* +** Union of all collectable objects +*/ +union GCObject { + GCheader gch; /* common header */ + union TString ts; + union Udata u; + union Closure cl; + struct Table h; + struct Proto p; + struct UpVal uv; + struct lua_State th; /* thread */ +}; + + +#define gch(o) (&(o)->gch) + +/* macros to convert a GCObject into a specific value */ +#define rawgco2ts(o) \ + check_exp(novariant((o)->gch.tt) == LUA_TSTRING, &((o)->ts)) +#define gco2ts(o) (&rawgco2ts(o)->tsv) +#define rawgco2u(o) check_exp((o)->gch.tt == LUA_TUSERDATA, &((o)->u)) +#define gco2u(o) (&rawgco2u(o)->uv) +#define gco2lcl(o) check_exp((o)->gch.tt == LUA_TLCL, &((o)->cl.l)) +#define gco2ccl(o) check_exp((o)->gch.tt == LUA_TCCL, &((o)->cl.c)) +#define gco2cl(o) \ + check_exp(novariant((o)->gch.tt) == LUA_TFUNCTION, &((o)->cl)) +#define gco2t(o) check_exp((o)->gch.tt == LUA_TTABLE, &((o)->h)) +#define gco2p(o) check_exp((o)->gch.tt == LUA_TPROTO, &((o)->p)) +#define gco2uv(o) check_exp((o)->gch.tt == LUA_TUPVAL, &((o)->uv)) +#define gco2th(o) check_exp((o)->gch.tt == LUA_TTHREAD, &((o)->th)) + +/* macro to convert any Lua object into a GCObject */ +#define obj2gco(v) (cast(GCObject *, (v))) + + +/* actual number of total bytes allocated */ +#define gettotalbytes(g) ((g)->totalbytes + (g)->GCdebt) + +LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); +LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); +LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); +LUAI_FUNC void luaE_freeCI (lua_State *L); + + +#endif + diff --git a/luprex/ext/eris-master/src/lstring.c b/luprex/ext/eris-master/src/lstring.c new file mode 100644 index 00000000..af96c89c --- /dev/null +++ b/luprex/ext/eris-master/src/lstring.c @@ -0,0 +1,185 @@ +/* +** $Id: lstring.c,v 2.26.1.1 2013/04/12 18:48:47 roberto Exp $ +** String table (keeps all strings handled by Lua) +** See Copyright Notice in lua.h +*/ + + +#include + +#define lstring_c +#define LUA_CORE + +#include "lua.h" + +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" + + +/* +** Lua will use at most ~(2^LUAI_HASHLIMIT) bytes from a string to +** compute its hash +*/ +#if !defined(LUAI_HASHLIMIT) +#define LUAI_HASHLIMIT 5 +#endif + + +/* +** equality for long strings +*/ +int luaS_eqlngstr (TString *a, TString *b) { + size_t len = a->tsv.len; + lua_assert(a->tsv.tt == LUA_TLNGSTR && b->tsv.tt == LUA_TLNGSTR); + return (a == b) || /* same instance or... */ + ((len == b->tsv.len) && /* equal length and ... */ + (memcmp(getstr(a), getstr(b), len) == 0)); /* equal contents */ +} + + +/* +** equality for strings +*/ +int luaS_eqstr (TString *a, TString *b) { + return (a->tsv.tt == b->tsv.tt) && + (a->tsv.tt == LUA_TSHRSTR ? eqshrstr(a, b) : luaS_eqlngstr(a, b)); +} + + +unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) { + unsigned int h = seed ^ cast(unsigned int, l); + size_t l1; + size_t step = (l >> LUAI_HASHLIMIT) + 1; + for (l1 = l; l1 >= step; l1 -= step) + h = h ^ ((h<<5) + (h>>2) + cast_byte(str[l1 - 1])); + return h; +} + + +/* +** resizes the string table +*/ +void luaS_resize (lua_State *L, int newsize) { + int i; + stringtable *tb = &G(L)->strt; + /* cannot resize while GC is traversing strings */ + luaC_runtilstate(L, ~bitmask(GCSsweepstring)); + if (newsize > tb->size) { + luaM_reallocvector(L, tb->hash, tb->size, newsize, GCObject *); + for (i = tb->size; i < newsize; i++) tb->hash[i] = NULL; + } + /* rehash */ + for (i=0; isize; i++) { + GCObject *p = tb->hash[i]; + tb->hash[i] = NULL; + while (p) { /* for each node in the list */ + GCObject *next = gch(p)->next; /* save next */ + unsigned int h = lmod(gco2ts(p)->hash, newsize); /* new position */ + gch(p)->next = tb->hash[h]; /* chain it */ + tb->hash[h] = p; + resetoldbit(p); /* see MOVE OLD rule */ + p = next; + } + } + if (newsize < tb->size) { + /* shrinking slice must be empty */ + lua_assert(tb->hash[newsize] == NULL && tb->hash[tb->size - 1] == NULL); + luaM_reallocvector(L, tb->hash, tb->size, newsize, GCObject *); + } + tb->size = newsize; +} + + +/* +** creates a new string object +*/ +static TString *createstrobj (lua_State *L, const char *str, size_t l, + int tag, unsigned int h, GCObject **list) { + TString *ts; + size_t totalsize; /* total size of TString object */ + totalsize = sizeof(TString) + ((l + 1) * sizeof(char)); + ts = &luaC_newobj(L, tag, totalsize, list, 0)->ts; + ts->tsv.len = l; + ts->tsv.hash = h; + ts->tsv.extra = 0; + memcpy(ts+1, str, l*sizeof(char)); + ((char *)(ts+1))[l] = '\0'; /* ending 0 */ + return ts; +} + + +/* +** creates a new short string, inserting it into string table +*/ +static TString *newshrstr (lua_State *L, const char *str, size_t l, + unsigned int h) { + GCObject **list; /* (pointer to) list where it will be inserted */ + stringtable *tb = &G(L)->strt; + TString *s; + if (tb->nuse >= cast(lu_int32, tb->size) && tb->size <= MAX_INT/2) + luaS_resize(L, tb->size*2); /* too crowded */ + list = &tb->hash[lmod(h, tb->size)]; + s = createstrobj(L, str, l, LUA_TSHRSTR, h, list); + tb->nuse++; + return s; +} + + +/* +** checks whether short string exists and reuses it or creates a new one +*/ +static TString *internshrstr (lua_State *L, const char *str, size_t l) { + GCObject *o; + global_State *g = G(L); + unsigned int h = luaS_hash(str, l, g->seed); + for (o = g->strt.hash[lmod(h, g->strt.size)]; + o != NULL; + o = gch(o)->next) { + TString *ts = rawgco2ts(o); + if (h == ts->tsv.hash && + l == ts->tsv.len && + (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) { + if (isdead(G(L), o)) /* string is dead (but was not collected yet)? */ + changewhite(o); /* resurrect it */ + return ts; + } + } + return newshrstr(L, str, l, h); /* not found; create a new string */ +} + + +/* +** new string (with explicit length) +*/ +TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { + if (l <= LUAI_MAXSHORTLEN) /* short string? */ + return internshrstr(L, str, l); + else { + if (l + 1 > (MAX_SIZET - sizeof(TString))/sizeof(char)) + luaM_toobig(L); + return createstrobj(L, str, l, LUA_TLNGSTR, G(L)->seed, NULL); + } +} + + +/* +** new zero-terminated string +*/ +TString *luaS_new (lua_State *L, const char *str) { + return luaS_newlstr(L, str, strlen(str)); +} + + +Udata *luaS_newudata (lua_State *L, size_t s, Table *e) { + Udata *u; + if (s > MAX_SIZET - sizeof(Udata)) + luaM_toobig(L); + u = &luaC_newobj(L, LUA_TUSERDATA, sizeof(Udata) + s, NULL, 0)->u; + u->uv.len = s; + u->uv.metatable = NULL; + u->uv.env = e; + return u; +} + diff --git a/luprex/ext/eris-master/src/lstring.h b/luprex/ext/eris-master/src/lstring.h new file mode 100644 index 00000000..260e7f16 --- /dev/null +++ b/luprex/ext/eris-master/src/lstring.h @@ -0,0 +1,46 @@ +/* +** $Id: lstring.h,v 1.49.1.1 2013/04/12 18:48:47 roberto Exp $ +** String table (keep all strings handled by Lua) +** See Copyright Notice in lua.h +*/ + +#ifndef lstring_h +#define lstring_h + +#include "lgc.h" +#include "lobject.h" +#include "lstate.h" + + +#define sizestring(s) (sizeof(union TString)+((s)->len+1)*sizeof(char)) + +#define sizeudata(u) (sizeof(union Udata)+(u)->len) + +#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ + (sizeof(s)/sizeof(char))-1)) + +#define luaS_fix(s) l_setbit((s)->tsv.marked, FIXEDBIT) + + +/* +** test whether a string is a reserved word +*/ +#define isreserved(s) ((s)->tsv.tt == LUA_TSHRSTR && (s)->tsv.extra > 0) + + +/* +** equality for short strings, which are always internalized +*/ +#define eqshrstr(a,b) check_exp((a)->tsv.tt == LUA_TSHRSTR, (a) == (b)) + + +LUAI_FUNC unsigned int luaS_hash (const char *str, size_t l, unsigned int seed); +LUAI_FUNC int luaS_eqlngstr (TString *a, TString *b); +LUAI_FUNC int luaS_eqstr (TString *a, TString *b); +LUAI_FUNC void luaS_resize (lua_State *L, int newsize); +LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, Table *e); +LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); +LUAI_FUNC TString *luaS_new (lua_State *L, const char *str); + + +#endif diff --git a/luprex/ext/eris-master/src/lstrlib.c b/luprex/ext/eris-master/src/lstrlib.c new file mode 100644 index 00000000..8da1c72b --- /dev/null +++ b/luprex/ext/eris-master/src/lstrlib.c @@ -0,0 +1,1035 @@ +/* +** $Id: lstrlib.c,v 1.178.1.1 2013/04/12 18:48:47 roberto Exp $ +** Standard library for string operations and pattern-matching +** See Copyright Notice in lua.h +*/ + + +#include +#include +#include +#include +#include + +#define lstrlib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** maximum number of captures that a pattern can do during +** pattern-matching. This limit is arbitrary. +*/ +#if !defined(LUA_MAXCAPTURES) +#define LUA_MAXCAPTURES 32 +#endif + + +/* macro to `unsign' a character */ +#define uchar(c) ((unsigned char)(c)) + + + +static int str_len (lua_State *L) { + size_t l; + luaL_checklstring(L, 1, &l); + lua_pushinteger(L, (lua_Integer)l); + return 1; +} + + +/* translate a relative string position: negative means back from end */ +static size_t posrelat (ptrdiff_t pos, size_t len) { + if (pos >= 0) return (size_t)pos; + else if (0u - (size_t)pos > len) return 0; + else return len - ((size_t)-pos) + 1; +} + + +static int str_sub (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + size_t start = posrelat(luaL_checkinteger(L, 2), l); + size_t end = posrelat(luaL_optinteger(L, 3, -1), l); + if (start < 1) start = 1; + if (end > l) end = l; + if (start <= end) + lua_pushlstring(L, s + start - 1, end - start + 1); + else lua_pushliteral(L, ""); + return 1; +} + + +static int str_reverse (lua_State *L) { + size_t l, i; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + char *p = luaL_buffinitsize(L, &b, l); + for (i = 0; i < l; i++) + p[i] = s[l - i - 1]; + luaL_pushresultsize(&b, l); + return 1; +} + + +static int str_lower (lua_State *L) { + size_t l; + size_t i; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + char *p = luaL_buffinitsize(L, &b, l); + for (i=0; i> 1) + +static int str_rep (lua_State *L) { + size_t l, lsep; + const char *s = luaL_checklstring(L, 1, &l); + int n = luaL_checkint(L, 2); + const char *sep = luaL_optlstring(L, 3, "", &lsep); + if (n <= 0) lua_pushliteral(L, ""); + else if (l + lsep < l || l + lsep >= MAXSIZE / n) /* may overflow? */ + return luaL_error(L, "resulting string too large"); + else { + size_t totallen = n * l + (n - 1) * lsep; + luaL_Buffer b; + char *p = luaL_buffinitsize(L, &b, totallen); + while (n-- > 1) { /* first n-1 copies (followed by separator) */ + memcpy(p, s, l * sizeof(char)); p += l; + if (lsep > 0) { /* avoid empty 'memcpy' (may be expensive) */ + memcpy(p, sep, lsep * sizeof(char)); p += lsep; + } + } + memcpy(p, s, l * sizeof(char)); /* last copy (not followed by separator) */ + luaL_pushresultsize(&b, totallen); + } + return 1; +} + + +static int str_byte (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + size_t posi = posrelat(luaL_optinteger(L, 2, 1), l); + size_t pose = posrelat(luaL_optinteger(L, 3, posi), l); + int n, i; + if (posi < 1) posi = 1; + if (pose > l) pose = l; + if (posi > pose) return 0; /* empty interval; return no values */ + n = (int)(pose - posi + 1); + if (posi + n <= pose) /* (size_t -> int) overflow? */ + return luaL_error(L, "string slice too long"); + luaL_checkstack(L, n, "string slice too long"); + for (i=0; i= ms->level || ms->capture[l].len == CAP_UNFINISHED) + return luaL_error(ms->L, "invalid capture index %%%d", l + 1); + return l; +} + + +static int capture_to_close (MatchState *ms) { + int level = ms->level; + for (level--; level>=0; level--) + if (ms->capture[level].len == CAP_UNFINISHED) return level; + return luaL_error(ms->L, "invalid pattern capture"); +} + + +static const char *classend (MatchState *ms, const char *p) { + switch (*p++) { + case L_ESC: { + if (p == ms->p_end) + luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")"); + return p+1; + } + case '[': { + if (*p == '^') p++; + do { /* look for a `]' */ + if (p == ms->p_end) + luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")"); + if (*(p++) == L_ESC && p < ms->p_end) + p++; /* skip escapes (e.g. `%]') */ + } while (*p != ']'); + return p+1; + } + default: { + return p; + } + } +} + + +static int match_class (int c, int cl) { + int res; + switch (tolower(cl)) { + case 'a' : res = isalpha(c); break; + case 'c' : res = iscntrl(c); break; + case 'd' : res = isdigit(c); break; + case 'g' : res = isgraph(c); break; + case 'l' : res = islower(c); break; + case 'p' : res = ispunct(c); break; + case 's' : res = isspace(c); break; + case 'u' : res = isupper(c); break; + case 'w' : res = isalnum(c); break; + case 'x' : res = isxdigit(c); break; + case 'z' : res = (c == 0); break; /* deprecated option */ + default: return (cl == c); + } + return (islower(cl) ? res : !res); +} + + +static int matchbracketclass (int c, const char *p, const char *ec) { + int sig = 1; + if (*(p+1) == '^') { + sig = 0; + p++; /* skip the `^' */ + } + while (++p < ec) { + if (*p == L_ESC) { + p++; + if (match_class(c, uchar(*p))) + return sig; + } + else if ((*(p+1) == '-') && (p+2 < ec)) { + p+=2; + if (uchar(*(p-2)) <= c && c <= uchar(*p)) + return sig; + } + else if (uchar(*p) == c) return sig; + } + return !sig; +} + + +static int singlematch (MatchState *ms, const char *s, const char *p, + const char *ep) { + if (s >= ms->src_end) + return 0; + else { + int c = uchar(*s); + switch (*p) { + case '.': return 1; /* matches any char */ + case L_ESC: return match_class(c, uchar(*(p+1))); + case '[': return matchbracketclass(c, p, ep-1); + default: return (uchar(*p) == c); + } + } +} + + +static const char *matchbalance (MatchState *ms, const char *s, + const char *p) { + if (p >= ms->p_end - 1) + luaL_error(ms->L, "malformed pattern " + "(missing arguments to " LUA_QL("%%b") ")"); + if (*s != *p) return NULL; + else { + int b = *p; + int e = *(p+1); + int cont = 1; + while (++s < ms->src_end) { + if (*s == e) { + if (--cont == 0) return s+1; + } + else if (*s == b) cont++; + } + } + return NULL; /* string ends out of balance */ +} + + +static const char *max_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + ptrdiff_t i = 0; /* counts maximum expand for item */ + while (singlematch(ms, s + i, p, ep)) + i++; + /* keeps trying to match with the maximum repetitions */ + while (i>=0) { + const char *res = match(ms, (s+i), ep+1); + if (res) return res; + i--; /* else didn't match; reduce 1 repetition to try again */ + } + return NULL; +} + + +static const char *min_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + for (;;) { + const char *res = match(ms, s, ep+1); + if (res != NULL) + return res; + else if (singlematch(ms, s, p, ep)) + s++; /* try with one more repetition */ + else return NULL; + } +} + + +static const char *start_capture (MatchState *ms, const char *s, + const char *p, int what) { + const char *res; + int level = ms->level; + if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures"); + ms->capture[level].init = s; + ms->capture[level].len = what; + ms->level = level+1; + if ((res=match(ms, s, p)) == NULL) /* match failed? */ + ms->level--; /* undo capture */ + return res; +} + + +static const char *end_capture (MatchState *ms, const char *s, + const char *p) { + int l = capture_to_close(ms); + const char *res; + ms->capture[l].len = s - ms->capture[l].init; /* close capture */ + if ((res = match(ms, s, p)) == NULL) /* match failed? */ + ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ + return res; +} + + +static const char *match_capture (MatchState *ms, const char *s, int l) { + size_t len; + l = check_capture(ms, l); + len = ms->capture[l].len; + if ((size_t)(ms->src_end-s) >= len && + memcmp(ms->capture[l].init, s, len) == 0) + return s+len; + else return NULL; +} + + +static const char *match (MatchState *ms, const char *s, const char *p) { + if (ms->matchdepth-- == 0) + luaL_error(ms->L, "pattern too complex"); + init: /* using goto's to optimize tail recursion */ + if (p != ms->p_end) { /* end of pattern? */ + switch (*p) { + case '(': { /* start capture */ + if (*(p + 1) == ')') /* position capture? */ + s = start_capture(ms, s, p + 2, CAP_POSITION); + else + s = start_capture(ms, s, p + 1, CAP_UNFINISHED); + break; + } + case ')': { /* end capture */ + s = end_capture(ms, s, p + 1); + break; + } + case '$': { + if ((p + 1) != ms->p_end) /* is the `$' the last char in pattern? */ + goto dflt; /* no; go to default */ + s = (s == ms->src_end) ? s : NULL; /* check end of string */ + break; + } + case L_ESC: { /* escaped sequences not in the format class[*+?-]? */ + switch (*(p + 1)) { + case 'b': { /* balanced string? */ + s = matchbalance(ms, s, p + 2); + if (s != NULL) { + p += 4; goto init; /* return match(ms, s, p + 4); */ + } /* else fail (s == NULL) */ + break; + } + case 'f': { /* frontier? */ + const char *ep; char previous; + p += 2; + if (*p != '[') + luaL_error(ms->L, "missing " LUA_QL("[") " after " + LUA_QL("%%f") " in pattern"); + ep = classend(ms, p); /* points to what is next */ + previous = (s == ms->src_init) ? '\0' : *(s - 1); + if (!matchbracketclass(uchar(previous), p, ep - 1) && + matchbracketclass(uchar(*s), p, ep - 1)) { + p = ep; goto init; /* return match(ms, s, ep); */ + } + s = NULL; /* match failed */ + break; + } + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': { /* capture results (%0-%9)? */ + s = match_capture(ms, s, uchar(*(p + 1))); + if (s != NULL) { + p += 2; goto init; /* return match(ms, s, p + 2) */ + } + break; + } + default: goto dflt; + } + break; + } + default: dflt: { /* pattern class plus optional suffix */ + const char *ep = classend(ms, p); /* points to optional suffix */ + /* does not match at least once? */ + if (!singlematch(ms, s, p, ep)) { + if (*ep == '*' || *ep == '?' || *ep == '-') { /* accept empty? */ + p = ep + 1; goto init; /* return match(ms, s, ep + 1); */ + } + else /* '+' or no suffix */ + s = NULL; /* fail */ + } + else { /* matched once */ + switch (*ep) { /* handle optional suffix */ + case '?': { /* optional */ + const char *res; + if ((res = match(ms, s + 1, ep + 1)) != NULL) + s = res; + else { + p = ep + 1; goto init; /* else return match(ms, s, ep + 1); */ + } + break; + } + case '+': /* 1 or more repetitions */ + s++; /* 1 match already done */ + /* go through */ + case '*': /* 0 or more repetitions */ + s = max_expand(ms, s, p, ep); + break; + case '-': /* 0 or more repetitions (minimum) */ + s = min_expand(ms, s, p, ep); + break; + default: /* no suffix */ + s++; p = ep; goto init; /* return match(ms, s + 1, ep); */ + } + } + break; + } + } + } + ms->matchdepth++; + return s; +} + + + +static const char *lmemfind (const char *s1, size_t l1, + const char *s2, size_t l2) { + if (l2 == 0) return s1; /* empty strings are everywhere */ + else if (l2 > l1) return NULL; /* avoids a negative `l1' */ + else { + const char *init; /* to search for a `*s2' inside `s1' */ + l2--; /* 1st char will be checked by `memchr' */ + l1 = l1-l2; /* `s2' cannot be found after that */ + while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { + init++; /* 1st char is already checked */ + if (memcmp(init, s2+1, l2) == 0) + return init-1; + else { /* correct `l1' and `s1' to try again */ + l1 -= init-s1; + s1 = init; + } + } + return NULL; /* not found */ + } +} + + +static void push_onecapture (MatchState *ms, int i, const char *s, + const char *e) { + if (i >= ms->level) { + if (i == 0) /* ms->level == 0, too */ + lua_pushlstring(ms->L, s, e - s); /* add whole match */ + else + luaL_error(ms->L, "invalid capture index"); + } + else { + ptrdiff_t l = ms->capture[i].len; + if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture"); + if (l == CAP_POSITION) + lua_pushinteger(ms->L, ms->capture[i].init - ms->src_init + 1); + else + lua_pushlstring(ms->L, ms->capture[i].init, l); + } +} + + +static int push_captures (MatchState *ms, const char *s, const char *e) { + int i; + int nlevels = (ms->level == 0 && s) ? 1 : ms->level; + luaL_checkstack(ms->L, nlevels, "too many captures"); + for (i = 0; i < nlevels; i++) + push_onecapture(ms, i, s, e); + return nlevels; /* number of strings pushed */ +} + + +/* check whether pattern has no special characters */ +static int nospecials (const char *p, size_t l) { + size_t upto = 0; + do { + if (strpbrk(p + upto, SPECIALS)) + return 0; /* pattern has a special character */ + upto += strlen(p + upto) + 1; /* may have more after \0 */ + } while (upto <= l); + return 1; /* no special chars found */ +} + + +static int str_find_aux (lua_State *L, int find) { + size_t ls, lp; + const char *s = luaL_checklstring(L, 1, &ls); + const char *p = luaL_checklstring(L, 2, &lp); + size_t init = posrelat(luaL_optinteger(L, 3, 1), ls); + if (init < 1) init = 1; + else if (init > ls + 1) { /* start after string's end? */ + lua_pushnil(L); /* cannot find anything */ + return 1; + } + /* explicit request or no special characters? */ + if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) { + /* do a plain search */ + const char *s2 = lmemfind(s + init - 1, ls - init + 1, p, lp); + if (s2) { + lua_pushinteger(L, s2 - s + 1); + lua_pushinteger(L, s2 - s + lp); + return 2; + } + } + else { + MatchState ms; + const char *s1 = s + init - 1; + int anchor = (*p == '^'); + if (anchor) { + p++; lp--; /* skip anchor character */ + } + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src_init = s; + ms.src_end = s + ls; + ms.p_end = p + lp; + do { + const char *res; + ms.level = 0; + lua_assert(ms.matchdepth == MAXCCALLS); + if ((res=match(&ms, s1, p)) != NULL) { + if (find) { + lua_pushinteger(L, s1 - s + 1); /* start */ + lua_pushinteger(L, res - s); /* end */ + return push_captures(&ms, NULL, 0) + 2; + } + else + return push_captures(&ms, s1, res); + } + } while (s1++ < ms.src_end && !anchor); + } + lua_pushnil(L); /* not found */ + return 1; +} + + +static int str_find (lua_State *L) { + return str_find_aux(L, 1); +} + + +static int str_match (lua_State *L) { + return str_find_aux(L, 0); +} + + +static int gmatch_aux (lua_State *L) { + MatchState ms; + size_t ls, lp; + const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls); + const char *p = lua_tolstring(L, lua_upvalueindex(2), &lp); + const char *src; + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src_init = s; + ms.src_end = s+ls; + ms.p_end = p + lp; + for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); + src <= ms.src_end; + src++) { + const char *e; + ms.level = 0; + lua_assert(ms.matchdepth == MAXCCALLS); + if ((e = match(&ms, src, p)) != NULL) { + lua_Integer newstart = e-s; + if (e == src) newstart++; /* empty match? go at least one position */ + lua_pushinteger(L, newstart); + lua_replace(L, lua_upvalueindex(3)); + return push_captures(&ms, src, e); + } + } + return 0; /* not found */ +} + + +static int gmatch (lua_State *L) { + luaL_checkstring(L, 1); + luaL_checkstring(L, 2); + lua_settop(L, 2); + lua_pushinteger(L, 0); + lua_pushcclosure(L, gmatch_aux, 3); + return 1; +} + + +static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, + const char *e) { + size_t l, i; + const char *news = lua_tolstring(ms->L, 3, &l); + for (i = 0; i < l; i++) { + if (news[i] != L_ESC) + luaL_addchar(b, news[i]); + else { + i++; /* skip ESC */ + if (!isdigit(uchar(news[i]))) { + if (news[i] != L_ESC) + luaL_error(ms->L, "invalid use of " LUA_QL("%c") + " in replacement string", L_ESC); + luaL_addchar(b, news[i]); + } + else if (news[i] == '0') + luaL_addlstring(b, s, e - s); + else { + push_onecapture(ms, news[i] - '1', s, e); + luaL_addvalue(b); /* add capture to accumulated result */ + } + } + } +} + + +static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, + const char *e, int tr) { + lua_State *L = ms->L; + switch (tr) { + case LUA_TFUNCTION: { + int n; + lua_pushvalue(L, 3); + n = push_captures(ms, s, e); + lua_call(L, n, 1); + break; + } + case LUA_TTABLE: { + push_onecapture(ms, 0, s, e); + lua_gettable(L, 3); + break; + } + default: { /* LUA_TNUMBER or LUA_TSTRING */ + add_s(ms, b, s, e); + return; + } + } + if (!lua_toboolean(L, -1)) { /* nil or false? */ + lua_pop(L, 1); + lua_pushlstring(L, s, e - s); /* keep original text */ + } + else if (!lua_isstring(L, -1)) + luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1)); + luaL_addvalue(b); /* add result to accumulator */ +} + + +static int str_gsub (lua_State *L) { + size_t srcl, lp; + const char *src = luaL_checklstring(L, 1, &srcl); + const char *p = luaL_checklstring(L, 2, &lp); + int tr = lua_type(L, 3); + size_t max_s = luaL_optinteger(L, 4, srcl+1); + int anchor = (*p == '^'); + size_t n = 0; + MatchState ms; + luaL_Buffer b; + luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || + tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, + "string/function/table expected"); + luaL_buffinit(L, &b); + if (anchor) { + p++; lp--; /* skip anchor character */ + } + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src_init = src; + ms.src_end = src+srcl; + ms.p_end = p + lp; + while (n < max_s) { + const char *e; + ms.level = 0; + lua_assert(ms.matchdepth == MAXCCALLS); + e = match(&ms, src, p); + if (e) { + n++; + add_value(&ms, &b, src, e, tr); + } + if (e && e>src) /* non empty match? */ + src = e; /* skip it */ + else if (src < ms.src_end) + luaL_addchar(&b, *src++); + else break; + if (anchor) break; + } + luaL_addlstring(&b, src, ms.src_end-src); + luaL_pushresult(&b); + lua_pushinteger(L, n); /* number of substitutions */ + return 2; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** STRING FORMAT +** ======================================================= +*/ + +/* +** LUA_INTFRMLEN is the length modifier for integer conversions in +** 'string.format'; LUA_INTFRM_T is the integer type corresponding to +** the previous length +*/ +#if !defined(LUA_INTFRMLEN) /* { */ +#if defined(LUA_USE_LONGLONG) + +#define LUA_INTFRMLEN "ll" +#define LUA_INTFRM_T long long + +#else + +#define LUA_INTFRMLEN "l" +#define LUA_INTFRM_T long + +#endif +#endif /* } */ + + +/* +** LUA_FLTFRMLEN is the length modifier for float conversions in +** 'string.format'; LUA_FLTFRM_T is the float type corresponding to +** the previous length +*/ +#if !defined(LUA_FLTFRMLEN) + +#define LUA_FLTFRMLEN "" +#define LUA_FLTFRM_T double + +#endif + + +/* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ +#define MAX_ITEM 512 +/* valid flags in a format specification */ +#define FLAGS "-+ #0" +/* +** maximum size of each format specification (such as '%-099.99d') +** (+10 accounts for %99.99x plus margin of error) +*/ +#define MAX_FORMAT (sizeof(FLAGS) + sizeof(LUA_INTFRMLEN) + 10) + + +static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { + size_t l; + const char *s = luaL_checklstring(L, arg, &l); + luaL_addchar(b, '"'); + while (l--) { + if (*s == '"' || *s == '\\' || *s == '\n') { + luaL_addchar(b, '\\'); + luaL_addchar(b, *s); + } + else if (*s == '\0' || iscntrl(uchar(*s))) { + char buff[10]; + if (!isdigit(uchar(*(s+1)))) + sprintf(buff, "\\%d", (int)uchar(*s)); + else + sprintf(buff, "\\%03d", (int)uchar(*s)); + luaL_addstring(b, buff); + } + else + luaL_addchar(b, *s); + s++; + } + luaL_addchar(b, '"'); +} + +static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { + const char *p = strfrmt; + while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ + if ((size_t)(p - strfrmt) >= sizeof(FLAGS)/sizeof(char)) + luaL_error(L, "invalid format (repeated flags)"); + if (isdigit(uchar(*p))) p++; /* skip width */ + if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ + if (*p == '.') { + p++; + if (isdigit(uchar(*p))) p++; /* skip precision */ + if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ + } + if (isdigit(uchar(*p))) + luaL_error(L, "invalid format (width or precision too long)"); + *(form++) = '%'; + memcpy(form, strfrmt, (p - strfrmt + 1) * sizeof(char)); + form += p - strfrmt + 1; + *form = '\0'; + return p; +} + + +/* +** add length modifier into formats +*/ +static void addlenmod (char *form, const char *lenmod) { + size_t l = strlen(form); + size_t lm = strlen(lenmod); + char spec = form[l - 1]; + strcpy(form + l - 1, lenmod); + form[l + lm - 1] = spec; + form[l + lm] = '\0'; +} + + +static int str_format (lua_State *L) { + int top = lua_gettop(L); + int arg = 1; + size_t sfl; + const char *strfrmt = luaL_checklstring(L, arg, &sfl); + const char *strfrmt_end = strfrmt+sfl; + luaL_Buffer b; + luaL_buffinit(L, &b); + while (strfrmt < strfrmt_end) { + if (*strfrmt != L_ESC) + luaL_addchar(&b, *strfrmt++); + else if (*++strfrmt == L_ESC) + luaL_addchar(&b, *strfrmt++); /* %% */ + else { /* format item */ + char form[MAX_FORMAT]; /* to store the format (`%...') */ + char *buff = luaL_prepbuffsize(&b, MAX_ITEM); /* to put formatted item */ + int nb = 0; /* number of bytes in added item */ + if (++arg > top) + luaL_argerror(L, arg, "no value"); + strfrmt = scanformat(L, strfrmt, form); + switch (*strfrmt++) { + case 'c': { + nb = sprintf(buff, form, luaL_checkint(L, arg)); + break; + } + case 'd': case 'i': { + lua_Number n = luaL_checknumber(L, arg); + LUA_INTFRM_T ni = (LUA_INTFRM_T)n; + lua_Number diff = n - (lua_Number)ni; + luaL_argcheck(L, -1 < diff && diff < 1, arg, + "not a number in proper range"); + addlenmod(form, LUA_INTFRMLEN); + nb = sprintf(buff, form, ni); + break; + } + case 'o': case 'u': case 'x': case 'X': { + lua_Number n = luaL_checknumber(L, arg); + unsigned LUA_INTFRM_T ni = (unsigned LUA_INTFRM_T)n; + lua_Number diff = n - (lua_Number)ni; + luaL_argcheck(L, -1 < diff && diff < 1, arg, + "not a non-negative number in proper range"); + addlenmod(form, LUA_INTFRMLEN); + nb = sprintf(buff, form, ni); + break; + } + case 'e': case 'E': case 'f': +#if defined(LUA_USE_AFORMAT) + case 'a': case 'A': +#endif + case 'g': case 'G': { + addlenmod(form, LUA_FLTFRMLEN); + nb = sprintf(buff, form, (LUA_FLTFRM_T)luaL_checknumber(L, arg)); + break; + } + case 'q': { + addquoted(L, &b, arg); + break; + } + case 's': { + size_t l; + const char *s = luaL_tolstring(L, arg, &l); + if (!strchr(form, '.') && l >= 100) { + /* no precision and string is too long to be formatted; + keep original string */ + luaL_addvalue(&b); + break; + } + else { + nb = sprintf(buff, form, s); + lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ + break; + } + } + default: { /* also treat cases `pnLlh' */ + return luaL_error(L, "invalid option " LUA_QL("%%%c") " to " + LUA_QL("format"), *(strfrmt - 1)); + } + } + luaL_addsize(&b, nb); + } + } + luaL_pushresult(&b); + return 1; +} + +/* }====================================================== */ + + +static const luaL_Reg strlib[] = { + {"byte", str_byte}, + {"char", str_char}, + {"dump", str_dump}, + {"find", str_find}, + {"format", str_format}, + {"gmatch", gmatch}, + {"gsub", str_gsub}, + {"len", str_len}, + {"lower", str_lower}, + {"match", str_match}, + {"rep", str_rep}, + {"reverse", str_reverse}, + {"sub", str_sub}, + {"upper", str_upper}, + {NULL, NULL} +}; + + +static void createmetatable (lua_State *L) { + lua_createtable(L, 0, 1); /* table to be metatable for strings */ + lua_pushliteral(L, ""); /* dummy string */ + lua_pushvalue(L, -2); /* copy table */ + lua_setmetatable(L, -2); /* set table as metatable for strings */ + lua_pop(L, 1); /* pop dummy string */ + lua_pushvalue(L, -2); /* get string library */ + lua_setfield(L, -2, "__index"); /* metatable.__index = string */ + lua_pop(L, 1); /* pop metatable */ +} + + +/* +** Open string library +*/ +LUAMOD_API int luaopen_string (lua_State *L) { + luaL_newlib(L, strlib); + createmetatable(L); + return 1; +} + + +void eris_permstrlib(lua_State *L, int forUnpersist) { + luaL_checktype(L, -1, LUA_TTABLE); + luaL_checkstack(L, 2, NULL); + + if (forUnpersist) { + lua_pushstring(L, "__eris.strlib_gmatch_aux"); + lua_pushcfunction(L, gmatch_aux); + } + else { + lua_pushcfunction(L, gmatch_aux); + lua_pushstring(L, "__eris.strlib_gmatch_aux"); + } + lua_rawset(L, -3); +} + diff --git a/luprex/ext/eris-master/src/ltable.c b/luprex/ext/eris-master/src/ltable.c new file mode 100644 index 00000000..056d4fbd --- /dev/null +++ b/luprex/ext/eris-master/src/ltable.c @@ -0,0 +1,745 @@ +/* +** $Id: ltable.c,v 2.72.1.1 2013/04/12 18:48:47 roberto Exp $ +** Lua tables (hash) +** See Copyright Notice in lua.h +*/ + + +/* +** Implementation of tables (aka arrays, objects, or hash tables). +** Tables keep its elements in two parts: an array part and a hash part. +** Non-negative integer keys are all candidates to be kept in the array +** part. The actual size of the array is the largest `n' such that at +** least half the slots between 0 and n are in use. +** Hash uses a mix of chained scatter table with Brent's variation. +** A main invariant of these tables is that, if an element is not +** in its main position (i.e. the `original' position that its hash gives +** to it), then the colliding element is in its own main position. +** Hence even when the load factor reaches 100%, performance remains good. +*/ + +#include +#include + +#define ltable_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "lvm.h" + + +/* +** max size of array part is 2^MAXBITS +*/ +#if LUAI_BITSINT >= 32 +#define MAXBITS 30 +#else +#define MAXBITS (LUAI_BITSINT-2) +#endif + +#define MAXASIZE (1 << MAXBITS) + + +#define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) + +#define hashstr(t,str) hashpow2(t, (str)->tsv.hash) +#define hashboolean(t,p) hashpow2(t, p) + + +/* +** for some types, it is better to avoid modulus by power of 2, as +** they tend to have many 2 factors. +*/ +#define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) + + +#define hashpointer(t,p) hashmod(t, IntPoint(p)) + + +#define dummynode (&dummynode_) + +#define isdummy(n) ((n) == dummynode) + +static const Node dummynode_ = { + {{NILCONSTANT}, 0}, /* anode: value, sequence */ + {NILCONSTANT}, /* key */ + NULL /* next */ +}; + +static int truesizenode(Table *t) { + return isdummy(t->node) ? 0 : sizenode(t); +} + +/* given a valid anode pointer, return its index */ +static int anodeindex(Table *t, ANode *anode) { + int aoffs = anode - t->array; + if (aoffs >= 0 && aoffs < t->sizearray && (t->array+aoffs==anode)) { + return aoffs; + } + Node *node = cast(Node *, anode); + return (node - t->node) + t->sizearray; +} + +/* given a valid anode index, return its pointer */ +static ANode *nthanode(Table *t, int n) { + if (n < t->sizearray) { + return t->array + n; + } else { + return &t->node[n - t->sizearray].anode; + } +} + +/* +** hash for lua_Numbers +*/ +static Node *hashnum (const Table *t, lua_Number n) { + int i; + luai_hashnum(i, n); + if (i < 0) { + if (cast(unsigned int, i) == 0u - i) /* use unsigned to avoid overflows */ + i = 0; /* handle INT_MIN */ + i = -i; /* must be a positive value */ + } + return hashmod(t, i); +} + + + +/* +** returns the `main' position of an element in a table (that is, the index +** of its hash value) +*/ +static Node *mainposition (const Table *t, const TValue *key) { + switch (ttype(key)) { + case LUA_TNUMBER: + return hashnum(t, nvalue(key)); + case LUA_TLNGSTR: { + TString *s = rawtsvalue(key); + if (s->tsv.extra == 0) { /* no hash? */ + s->tsv.hash = luaS_hash(getstr(s), s->tsv.len, s->tsv.hash); + s->tsv.extra = 1; /* now it has its hash */ + } + return hashstr(t, rawtsvalue(key)); + } + case LUA_TSHRSTR: + return hashstr(t, rawtsvalue(key)); + case LUA_TBOOLEAN: + return hashboolean(t, bvalue(key)); + case LUA_TLIGHTUSERDATA: + return hashpointer(t, pvalue(key)); + case LUA_TLCF: + return hashpointer(t, fvalue(key)); + default: + return hashpointer(t, gcvalue(key)); + } +} + + +/* +** returns the index for `key' if `key' is an appropriate key to live in +** the array part of the table, -1 otherwise. +*/ +static int arrayindex (const TValue *key) { + if (ttisnumber(key)) { + lua_Number n = nvalue(key); + int k; + lua_number2int(k, n); + if (luai_numeq(cast_num(k), n)) + return k; + } + return -1; /* `key' did not match some condition */ +} + +int luaH_nkeys (Table *t) { + return t->nnkeys; +} + +int luaH_nthkey (lua_State *L, Table *t, int n, StkId pair) { + n -= 1; /* convert to C indexing */ + if ((n < 0) || (n >= t->nnkeys)) { + setnilvalue(pair+0); + setnilvalue(pair+1); + return 0; + } + int index = t->sequence[n]; + if (index < t->sizearray) { + setnvalue(pair + 0, index + 1); + setobj2s(L, pair + 1, &t->array[index].i_val); + return 1; + } else { + index -= t->sizearray; + Node *n = t->node + index; + setobj2s(L, pair + 0, gkey(n)); + setobj2s(L, pair + 1, gval(n)); + return 1; + } +} + +int successorindex (lua_State *L, Table *t, StkId key) { + int i, seqno; + if (ttisnil(key)) { + return 0; + } + i = arrayindex(key); + if (0 < i && i <= t->sizearray) { + seqno = t->array[i-1].i_sequence + 1; + if (seqno == 0) { + if (t->array + (i - 1) == t->lastdeleted) { + return t->lastdelseq; + } + luaG_runerror(L, "next: no such key in table"); + } + return seqno; + } + Node *n = mainposition(t, key); + for (;;) { /* check whether `key' is somewhere in the chain */ + /* key may be dead already, but it is ok to use it in `next' */ + if (luaV_rawequalobj(gkey(n), key) || + (ttisdeadkey(gkey(n)) && iscollectable(key) && + deadvalue(gkey(n)) == gcvalue(key))) { + seqno = gseq(n) + 1; + if (seqno == 0) { + if (ganode(n) == t->lastdeleted) { + return t->lastdelseq; + } + luaG_runerror(L, "next: no such key in table"); + } + return seqno; + } + else n = gnext(n); + if (n == NULL) + luaG_runerror(L, "invalid key to " LUA_QL("next")); /* key not found */ + } +} + +int luaH_next (lua_State *L, Table *t, StkId key) { + int seqno = successorindex(L, t, key); + if (seqno >= t->nnkeys) { + return 0; + } + int i = t->sequence[seqno]; + if (i < t->sizearray) { + setnvalue(key + 0, i+1); + setobj2s(L, key + 1, &t->array[i].i_val); + return 1; + } else { + Node *n = t->node + (i - t->sizearray); + setobj2s(L, key + 0, gkey(n)); + setobj2s(L, key + 1, gval(n)); + return 1; + } +} + +/* +** {============================================================= +** Rehash +** ============================================================== +*/ + + +static int computesizes (int nums[], int *narray) { + int i; + int twotoi; /* 2^i */ + int a = 0; /* number of elements smaller than 2^i */ + int na = 0; /* number of elements to go to array part */ + int n = 0; /* optimal size for array part */ + for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) { + if (nums[i] > 0) { + a += nums[i]; + if (a > twotoi/2) { /* more than half elements present? */ + n = twotoi; /* optimal size (till now) */ + na = a; /* all elements smaller than n will go to array part */ + } + } + if (a == *narray) break; /* all elements already counted */ + } + *narray = n; + lua_assert(*narray/2 <= na && na <= *narray); + return na; +} + + +static int countint (const TValue *key, int *nums) { + int k = arrayindex(key); + if (0 < k && k <= MAXASIZE) { /* is `key' an appropriate array index? */ + nums[luaO_ceillog2(k)]++; /* count as such */ + return 1; + } + else + return 0; +} + + +static int numusearray (const Table *t, int *nums) { + int lg; + int ttlg; /* 2^lg */ + int ause = 0; /* summation of `nums' */ + int i = 1; /* count to traverse all array keys */ + for (lg=0, ttlg=1; lg<=MAXBITS; lg++, ttlg*=2) { /* for each slice */ + int lc = 0; /* counter */ + int lim = ttlg; + if (lim > t->sizearray) { + lim = t->sizearray; /* adjust upper limit */ + if (i > lim) + break; /* no more elements to count */ + } + /* count elements in range (2^(lg-1), 2^lg] */ + for (; i <= lim; i++) { + if (!ttisnil(&t->array[i-1].i_val)) + lc++; + } + nums[lg] += lc; + ause += lc; + } + return ause; +} + + +static int numusehash (const Table *t, int *nums, int *pnasize) { + int totaluse = 0; /* total number of elements */ + int ause = 0; /* summation of `nums' */ + int i = sizenode(t); + while (i--) { + Node *n = &t->node[i]; + if (!ttisnil(gval(n))) { + ause += countint(gkey(n), nums); + totaluse++; + } + } + *pnasize += ause; + return totaluse; +} + + +static void setarrayvector (lua_State *L, Table *t, int size) { + int i; + luaM_reallocvector(L, t->array, t->sizearray, size, ANode); + for (i=t->sizearray; iarray[i].i_val); + t->array[i].i_sequence = -1; + } + t->sizearray = size; +} + + +static void setnodevector (lua_State *L, Table *t, int size) { + int lsize; + if (size == 0) { /* no elements to hash part? */ + t->node = cast(Node *, dummynode); /* use common `dummynode' */ + lsize = 0; + } + else { + int i; + lsize = luaO_ceillog2(size); + if (lsize == 0) lsize = 1; + if (lsize > MAXBITS) + luaG_runerror(L, "table overflow"); + size = twoto(lsize); + t->node = luaM_newvector(L, size, Node); + for (i=0; ilsizenode = cast_byte(lsize); + t->lastfree = gnode(t, size); /* all positions are free */ +} + +static TValue *luaH_newkey(lua_State *L, Table *t, const TValue *key); + +static void storeintanode (lua_State *L, Table *t, int key, ANode *anode) { + const TValue *p = luaH_getint(t, key); + ANode *cell; + if (p != luaO_nilobject) { + cell = cast(ANode *, p); + } else { + TValue nk; + setnvalue(&nk, key); + cell = cast(ANode *, luaH_newkey(L, t, &nk)); + } + setobj2t(L, &cell->i_val, &anode->i_val); + cell->i_sequence = anode->i_sequence; +} + +static void storeanode (lua_State *L, Table *t, const TValue *key, ANode *anode) { + const TValue *p = luaH_get(t, key); + ANode *cell; + if (p != luaO_nilobject) { + cell = cast(ANode *, p); + } else { + cell = cast(ANode *, luaH_newkey(L, t, key)); + } + setobj2t(L, &cell->i_val, &anode->i_val); + cell->i_sequence = anode->i_sequence; +} + + +void luaH_resize (lua_State *L, Table *t, int nasize, int nhsize) { + int i; + int oldasize = t->sizearray; + int oldhsize = truesizenode(t); + Node *nold = t->node; /* save old hash ... */ + if (nasize > oldasize) /* array part must grow? */ + setarrayvector(L, t, nasize); + /* create new hash part with appropriate size */ + setnodevector(L, t, nhsize); + nhsize = truesizenode(t); + /* possibly shrink the array part */ + if (nasize < oldasize) { + t->sizearray = nasize; + /* re-insert elements from vanishing slice */ + for (i=nasize; iarray[i].i_val)) + storeintanode(L, t, i + 1, &t->array[i]); + } + /* shrink array */ + luaM_reallocvector(L, t->array, oldasize, nasize, ANode); + } + /* re-insert elements from hash part */ + for (i = oldhsize - 1; i >= 0; i--) { + Node *old = nold+i; + if (!ttisnil(gval(old))) { + /* doesn't need barrier/invalidate cache, as entry was + already present in the table */ + storeanode(L, t, gkey(old), ganode(old)); + } + } + /* delete the old node vector */ + if (!isdummy(nold)) + luaM_freearray(L, nold, cast(size_t, oldhsize)); + /* resize the sequence array */ + int oldseqlen = oldasize + oldhsize; + int newseqlen = nasize + nhsize; + luaM_reallocvector(L, t->sequence, oldseqlen, newseqlen, int); + /* recalculate the entire sequence vector */ + int total = 0; + for (i = nasize - 1; i >= 0; i--) { + ANode *anode = &t->array[i]; + if (anode->i_sequence >= 0) { + assert(anode->i_sequence < t->nnkeys); + t->sequence[anode->i_sequence] = i; + total += 1; + } + } + for (i = nhsize - 1; i >= 0; i--) { + ANode *anode = &t->node[i].anode; + if (anode->i_sequence >= 0) { + assert(anode->i_sequence < t->nnkeys); + t->sequence[anode->i_sequence] = i + t->sizearray; + total += 1; + } + } + t->nnkeys = total; +} + + +void luaH_resizearray (lua_State *L, Table *t, int nasize) { + luaH_resize(L, t, nasize, truesizenode(t)); +} + + +static void rehash (lua_State *L, Table *t, const TValue *ek) { + int nasize, na; + int nums[MAXBITS+1]; /* nums[i] = number of keys with 2^(i-1) < k <= 2^i */ + int i; + int totaluse; + for (i=0; i<=MAXBITS; i++) nums[i] = 0; /* reset counts */ + nasize = numusearray(t, nums); /* count keys in array part */ + totaluse = nasize; /* all those keys are integer keys */ + totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */ + /* count extra key */ + nasize += countint(ek, nums); + totaluse++; + /* compute new size for array part */ + na = computesizes(nums, &nasize); + /* resize the table to new computed sizes */ + luaH_resize(L, t, nasize, totaluse - na); +} + + + +/* +** }============================================================= +*/ + + +Table *luaH_new (lua_State *L) { + Table *t = &luaC_newobj(L, LUA_TTABLE, sizeof(Table), NULL, 0)->h; + t->metatable = NULL; + t->flags = cast_byte(~0); + t->flagbits = 0; + t->array = NULL; + t->sizearray = 0; + t->node = cast(Node *, dummynode); + t->lsizenode = 0; + t->lastfree = gnode(t, 0); + t->sequence = 0; + t->nnkeys = 0; + t->lastdeleted = NULL; + t->lastdelseq = -1; + return t; +} + + +void luaH_free (lua_State *L, Table *t) { + if (!isdummy(t->node)) + luaM_freearray(L, t->node, cast(size_t, sizenode(t))); + luaM_freearray(L, t->array, t->sizearray); + int seqlen = t->sizearray + truesizenode(t); + luaM_freearray(L, t->sequence, seqlen); + luaM_free(L, t); +} + + +static Node *getfreepos (Table *t) { + while (t->lastfree > t->node) { + t->lastfree--; + if (ttisnil(gkey(t->lastfree))) + return t->lastfree; + } + return NULL; /* could not find a free place */ +} + + + +/* +** inserts a new key into a hash table; first, check whether key's main +** position is free. If not, check whether colliding node is in its main +** position or not: if it is not, move colliding node to an empty place and +** put new key in its main position; otherwise (colliding node is in its main +** position), new key goes to an empty position. +*/ +static TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { + Node *mp; + if (ttisnil(key)) luaG_runerror(L, "table index is nil"); + else if (ttisnumber(key) && luai_numisnan(L, nvalue(key))) + luaG_runerror(L, "table index is NaN"); + mp = mainposition(t, key); + if (!ttisnil(gval(mp)) || isdummy(mp)) { /* main position is taken? */ + Node *othern; + Node *n = getfreepos(t); /* get a free place */ + if (n == NULL) { /* cannot find a free place? */ + rehash(L, t, key); /* grow table */ + /* whatever called 'newkey' take care of TM cache and GC barrier */ + const TValue *p = luaH_get(t, key); + if (p != luaO_nilobject) + return cast(TValue *, p); + return luaH_newkey(L, t, key); + } + lua_assert(!isdummy(n)); + othern = mainposition(t, gkey(mp)); + if (othern != mp) { /* is colliding node out of its main position? */ + /* yes; move colliding node into free position */ + while (gnext(othern) != mp) othern = gnext(othern); /* find previous */ + gnext(othern) = n; /* redo the chain with `n' in place of `mp' */ + *n = *mp; /* copy colliding node into free pos. (mp->next also goes) */ + gnext(mp) = NULL; /* now `mp' is free */ + setnilvalue(gval(mp)); + gseq(mp) = -1; + int seqno = gseq(n); + t->sequence[seqno] = (n - t->node) + t->sizearray; + } + else { /* colliding node is in its own main position */ + /* new node will go into free position */ + gnext(n) = gnext(mp); /* chain new position */ + gnext(mp) = n; + mp = n; + } + } + setobj2t(L, gkey(mp), key); + luaC_barrierback(L, obj2gco(t), key); + lua_assert(ttisnil(gval(mp))); + return gval(mp); +} + + +/* +** search function for integers +*/ +const TValue *luaH_getint (Table *t, int key) { + /* (1 <= key && key <= t->sizearray) */ + if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray)) + return &t->array[key-1].i_val; + else { + lua_Number nk = cast_num(key); + Node *n = hashnum(t, nk); + do { /* check whether `key' is somewhere in the chain */ + if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk)) + return gval(n); /* that's it */ + else n = gnext(n); + } while (n); + return luaO_nilobject; + } +} + + +/* +** search function for short strings +*/ +const TValue *luaH_getstr (Table *t, TString *key) { + Node *n = hashstr(t, key); + lua_assert(key->tsv.tt == LUA_TSHRSTR); + do { /* check whether `key' is somewhere in the chain */ + if (ttisshrstring(gkey(n)) && eqshrstr(rawtsvalue(gkey(n)), key)) + return gval(n); /* that's it */ + else n = gnext(n); + } while (n); + return luaO_nilobject; +} + + +/* +** main search function +*/ +const TValue *luaH_get (Table *t, const TValue *key) { + switch (ttype(key)) { + case LUA_TSHRSTR: return luaH_getstr(t, rawtsvalue(key)); + case LUA_TNIL: return luaO_nilobject; + case LUA_TNUMBER: { + int k; + lua_Number n = nvalue(key); + lua_number2int(k, n); + if (luai_numeq(cast_num(k), n)) /* index is int? */ + return luaH_getint(t, k); /* use specialized version */ + /* else go through */ + } + default: { + Node *n = mainposition(t, key); + do { /* check whether `key' is somewhere in the chain */ + if (luaV_rawequalobj(gkey(n), key)) + return gval(n); /* that's it */ + else n = gnext(n); + } while (n); + return luaO_nilobject; + } + } +} + +/* Change an entry to non-nil. */ +/* This means the entry must be added to the sequence */ +void luaH_setnonnil(Table *t, ANode *anode, const TValue *value) { + assert(ttisnil(&anode->i_val)); + assert(anode->i_sequence == -1); + int index = anodeindex(t, anode); + int totalanodes = t->sizearray + truesizenode(t); + assert((index >= 0) && (index < totalanodes)); + anode->i_sequence = t->nnkeys++; + assert((anode->i_sequence >= 0) && (anode->i_sequence < totalanodes)); + t->sequence[anode->i_sequence] = index; + setobj2t(L, &anode->i_val, value); +} + +/* Change an entry to nil. */ +/* This means the entry must be removed from the sequence */ +void luaH_setnil(Table *t, ANode *anode) { + assert(!ttisnil(&anode->i_val)); + assert((anode->i_sequence >= 0) && (anode->i_sequence < t->nnkeys)); + /* remove the last item from the sequence, and get a pointer to its anode */ + int totalanodes = t->sizearray + truesizenode(t); + assert((t->nnkeys > 0) && (t->nnkeys <= totalanodes)); + int lastanodeindex = t->sequence[t->nnkeys - 1]; + assert((lastanodeindex >= 0) && (lastanodeindex < totalanodes)); + ANode *lastanode = nthanode(t, lastanodeindex); + /* move the last anode to the freed slot */ + int freeslot = anode->i_sequence; + t->sequence[freeslot] = lastanodeindex; + t->nnkeys -= 1; + lastanode->i_sequence = freeslot; + /* clear out the anode */ + anode->i_sequence = -1; + setnilvalue(&anode->i_val); +} + +/* +** beware: when using this function you probably need to check a GC +** barrier and invalidate the TM cache. +*/ +void luaH_setupdate (lua_State *L, Table *t, const TValue *key, TValue *value, const TValue *getres) { + if (!ttisnil(getres)) { + if (ttisnil(value)) { + /* replacing a non-nil value with nil */ + ANode *anode = cast(ANode *, getres); + t->lastdeleted = anode; + t->lastdelseq = anode->i_sequence; + luaH_setnil(t, anode); + } else { + /* replacing a non-nil value with a different non-nil value */ + TValue *cell = cast(TValue *, getres); + setobj2t(L, cell, value); + } + } else if (getres == luaO_nilobject) { + if (!ttisnil(value)) { + /* creating a new key with a non-nil value */ + t->lastdeleted = NULL; + t->lastdelseq = -1; + ANode *anode = cast(ANode *, luaH_newkey(L, t, key)); + luaH_setnonnil(t, anode, value); + } + } else if (!ttisnil(value)) { + /* replacing a nil value with a non-nil value */ + t->lastdeleted = NULL; + t->lastdelseq = -1; + luaH_setnonnil(t, cast(ANode *, getres), value); + } +} + +void luaH_setvalue (lua_State *L, Table *t, const TValue *key, TValue *value) { + luaH_setupdate(L, t, key, value, luaH_get(t, key)); +} + +void luaH_setint (lua_State *L, Table *t, int key, TValue *value) { + TValue kv; + setnvalue(&kv, key); + luaH_setupdate(L, t, &kv, value, luaH_getint(t, key)); +} + +/* +** Try to find a boundary in table `t'. A `boundary' is an integer index +** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). +*/ +int luaH_getn (Table *t) { + unsigned int j = 1; + unsigned int i = 0; + /* find `i' and `j' such that i is present and j is not */ + while (!ttisnil(luaH_getint(t, j))) { + i = j; + j *= 2; + if (j > cast(unsigned int, MAX_INT)) { /* overflow? */ + /* table was built with bad purposes: resort to linear search */ + i = 1; + while (!ttisnil(luaH_getint(t, i))) i++; + return i - 1; + } + } + /* now do a binary search between them */ + while (j - i > 1) { + unsigned int m = (i+j)/2; + if (ttisnil(luaH_getint(t, m))) j = m; + else i = m; + } + return i; +} + + + +#if defined(LUA_DEBUG) + +Node *luaH_mainposition (const Table *t, const TValue *key) { + return mainposition(t, key); +} + +int luaH_isdummy (Node *n) { return isdummy(n); } + +#endif diff --git a/luprex/ext/eris-master/src/ltable.h b/luprex/ext/eris-master/src/ltable.h new file mode 100644 index 00000000..b09353b3 --- /dev/null +++ b/luprex/ext/eris-master/src/ltable.h @@ -0,0 +1,51 @@ +/* +** $Id: ltable.h,v 2.16.1.2 2013/08/30 15:49:41 roberto Exp $ +** Lua tables (hash) +** See Copyright Notice in lua.h +*/ + +#ifndef ltable_h +#define ltable_h + +#include "lobject.h" + + +#define gnode(t,i) (&(t)->node[i]) +#define gkey(n) (&(n)->i_key) +#define gval(n) (&(n)->anode.i_val) +#define ganode(n) (&(n)->anode) +#define gseq(n) ((n)->anode.i_sequence) +#define gnext(n) ((n)->i_next) + +#define invalidateTMcache(t) ((t)->flags = 0) + +/* returns the key, given the value of a table entry */ +#define keyfromval(v) \ + (gkey(cast(Node *, cast(char *, (v)) - offsetof(Node, anode.i_val)))) + + +LUAI_FUNC const TValue *luaH_getint (Table *t, int key); +LUAI_FUNC void luaH_setnil (Table *t, ANode *anode); +LUAI_FUNC void luaH_setnonnil (Table *t, ANode *anode, const TValue *value); +LUAI_FUNC void luaH_setint (lua_State *L, Table *t, int key, TValue *value); +LUAI_FUNC void luaH_setvalue (lua_State *L, Table *t, const TValue *key, TValue *value); +LUAI_FUNC void luaH_setupdate (lua_State *L, Table *t, const TValue *key, TValue *value, const TValue *getres); +LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); +LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); +LUAI_FUNC Table *luaH_new (lua_State *L); +LUAI_FUNC void luaH_resize (lua_State *L, Table *t, int nasize, int nhsize); +LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, int nasize); +LUAI_FUNC void luaH_free (lua_State *L, Table *t); +LUAI_FUNC int luaH_nkeys (Table *t); +LUAI_FUNC int luaH_nthkey (lua_State *L, Table *t, int n, StkId pair); +LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); +LUAI_FUNC int luaH_getn (Table *t); + + +#if defined(LUA_DEBUG) +LUAI_FUNC Node *luaH_mainposition (const Table *t, const TValue *key); +LUAI_FUNC int luaH_isdummy (Node *n); +#endif + + +#endif diff --git a/luprex/ext/eris-master/src/ltablib.c b/luprex/ext/eris-master/src/ltablib.c new file mode 100644 index 00000000..99764d26 --- /dev/null +++ b/luprex/ext/eris-master/src/ltablib.c @@ -0,0 +1,285 @@ +/* +** $Id: ltablib.c,v 1.65.1.2 2014/05/07 16:32:55 roberto Exp $ +** Library for Table Manipulation +** See Copyright Notice in lua.h +*/ + + +#include +#include + +#define ltablib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#define aux_getn(L,n) (luaL_checktype(L, n, LUA_TTABLE), luaL_len(L, n)) + + + +#if defined(LUA_COMPAT_MAXN) +static int maxn (lua_State *L) { + lua_Number max = 0; + luaL_checktype(L, 1, LUA_TTABLE); + lua_pushnil(L); /* first key */ + while (lua_next(L, 1)) { + lua_pop(L, 1); /* remove value */ + if (lua_type(L, -1) == LUA_TNUMBER) { + lua_Number v = lua_tonumber(L, -1); + if (v > max) max = v; + } + } + lua_pushnumber(L, max); + return 1; +} +#endif + + +static int tinsert (lua_State *L) { + int e = aux_getn(L, 1) + 1; /* first empty element */ + int pos; /* where to insert new element */ + switch (lua_gettop(L)) { + case 2: { /* called with only 2 arguments */ + pos = e; /* insert new element at the end */ + break; + } + case 3: { + int i; + pos = luaL_checkint(L, 2); /* 2nd argument is the position */ + luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds"); + for (i = e; i > pos; i--) { /* move up elements */ + lua_rawgeti(L, 1, i-1); + lua_rawseti(L, 1, i); /* t[i] = t[i-1] */ + } + break; + } + default: { + return luaL_error(L, "wrong number of arguments to " LUA_QL("insert")); + } + } + lua_rawseti(L, 1, pos); /* t[pos] = v */ + return 0; +} + + +static int tremove (lua_State *L) { + int size = aux_getn(L, 1); + int pos = luaL_optint(L, 2, size); + if (pos != size) /* validate 'pos' if given */ + luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds"); + lua_rawgeti(L, 1, pos); /* result = t[pos] */ + for ( ; pos < size; pos++) { + lua_rawgeti(L, 1, pos+1); + lua_rawseti(L, 1, pos); /* t[pos] = t[pos+1] */ + } + lua_pushnil(L); + lua_rawseti(L, 1, pos); /* t[pos] = nil */ + return 1; +} + + +static void addfield (lua_State *L, luaL_Buffer *b, int i) { + lua_rawgeti(L, 1, i); + if (!lua_isstring(L, -1)) + luaL_error(L, "invalid value (%s) at index %d in table for " + LUA_QL("concat"), luaL_typename(L, -1), i); + luaL_addvalue(b); +} + + +static int tconcat (lua_State *L) { + luaL_Buffer b; + size_t lsep; + int i, last; + const char *sep = luaL_optlstring(L, 2, "", &lsep); + luaL_checktype(L, 1, LUA_TTABLE); + i = luaL_optint(L, 3, 1); + last = luaL_opt(L, luaL_checkint, 4, luaL_len(L, 1)); + luaL_buffinit(L, &b); + for (; i < last; i++) { + addfield(L, &b, i); + luaL_addlstring(&b, sep, lsep); + } + if (i == last) /* add last value (if interval was not empty) */ + addfield(L, &b, i); + luaL_pushresult(&b); + return 1; +} + + +/* +** {====================================================== +** Pack/unpack +** ======================================================= +*/ + +static int pack (lua_State *L) { + int n = lua_gettop(L); /* number of elements to pack */ + lua_createtable(L, n, 1); /* create result table */ + lua_pushinteger(L, n); + lua_setfield(L, -2, "n"); /* t.n = number of elements */ + if (n > 0) { /* at least one element? */ + int i; + lua_pushvalue(L, 1); + lua_rawseti(L, -2, 1); /* insert first element */ + lua_replace(L, 1); /* move table into index 1 */ + for (i = n; i >= 2; i--) /* assign other elements */ + lua_rawseti(L, 1, i); + } + return 1; /* return table */ +} + + +static int unpack (lua_State *L) { + int i, e; + unsigned int n; + luaL_checktype(L, 1, LUA_TTABLE); + i = luaL_optint(L, 2, 1); + e = luaL_opt(L, luaL_checkint, 3, luaL_len(L, 1)); + if (i > e) return 0; /* empty range */ + n = (unsigned int)e - (unsigned int)i; /* number of elements minus 1 */ + if (n > (INT_MAX - 10) || !lua_checkstack(L, ++n)) + return luaL_error(L, "too many results to unpack"); + lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */ + while (i++ < e) /* push arg[i + 1...e] */ + lua_rawgeti(L, 1, i); + return n; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Quicksort +** (based on `Algorithms in MODULA-3', Robert Sedgewick; +** Addison-Wesley, 1993.) +** ======================================================= +*/ + + +static void set2 (lua_State *L, int i, int j) { + lua_rawseti(L, 1, i); + lua_rawseti(L, 1, j); +} + +static int sort_comp (lua_State *L, int a, int b) { + if (!lua_isnil(L, 2)) { /* function? */ + int res; + lua_pushvalue(L, 2); + lua_pushvalue(L, a-1); /* -1 to compensate function */ + lua_pushvalue(L, b-2); /* -2 to compensate function and `a' */ + lua_call(L, 2, 1); + res = lua_toboolean(L, -1); + lua_pop(L, 1); + return res; + } + else /* a < b? */ + return lua_compare(L, a, b, LUA_OPLT); +} + +static void auxsort (lua_State *L, int l, int u) { + while (l < u) { /* for tail recursion */ + int i, j; + /* sort elements a[l], a[(l+u)/2] and a[u] */ + lua_rawgeti(L, 1, l); + lua_rawgeti(L, 1, u); + if (sort_comp(L, -1, -2)) /* a[u] < a[l]? */ + set2(L, l, u); /* swap a[l] - a[u] */ + else + lua_pop(L, 2); + if (u-l == 1) break; /* only 2 elements */ + i = (l+u)/2; + lua_rawgeti(L, 1, i); + lua_rawgeti(L, 1, l); + if (sort_comp(L, -2, -1)) /* a[i]= P */ + while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2)) { + if (i>=u) luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[i] */ + } + /* repeat --j until a[j] <= P */ + while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1)) { + if (j<=l) luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[j] */ + } + if (j + +#define ltm_c +#define LUA_CORE + +#include "lua.h" + +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + +static const char udatatypename[] = "userdata"; + +LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTAGS] = { + "no value", + "nil", "boolean", udatatypename, "number", + "string", "table", "function", udatatypename, "thread", + "proto", "upval" /* these last two cases are used for tests only */ +}; + + +void luaT_init (lua_State *L) { + static const char *const luaT_eventname[] = { /* ORDER TM */ + "__index", "__newindex", + "__gc", "__mode", "__len", "__eq", + "__add", "__sub", "__mul", "__div", "__mod", + "__pow", "__unm", "__lt", "__le", + "__concat", "__call" + }; + int i; + for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]); + luaS_fix(G(L)->tmname[i]); /* never collect these names */ + } +} + + +/* +** function to be used with macro "fasttm": optimized for absence of +** tag methods +*/ +const TValue *luaT_gettm (Table *events, TMS event, TString *ename) { + const TValue *tm = luaH_getstr(events, ename); + lua_assert(event <= TM_EQ); + if (ttisnil(tm)) { /* no tag method? */ + events->flags |= cast_byte(1u<metatable; + break; + case LUA_TUSERDATA: + mt = uvalue(o)->metatable; + break; + default: + mt = G(L)->mt[ttypenv(o)]; + } + return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject); +} + diff --git a/luprex/ext/eris-master/src/ltm.h b/luprex/ext/eris-master/src/ltm.h new file mode 100644 index 00000000..7f89c841 --- /dev/null +++ b/luprex/ext/eris-master/src/ltm.h @@ -0,0 +1,57 @@ +/* +** $Id: ltm.h,v 2.11.1.1 2013/04/12 18:48:47 roberto Exp $ +** Tag methods +** See Copyright Notice in lua.h +*/ + +#ifndef ltm_h +#define ltm_h + + +#include "lobject.h" + + +/* +* WARNING: if you change the order of this enumeration, +* grep "ORDER TM" +*/ +typedef enum { + TM_INDEX, + TM_NEWINDEX, + TM_GC, + TM_MODE, + TM_LEN, + TM_EQ, /* last tag method with `fast' access */ + TM_ADD, + TM_SUB, + TM_MUL, + TM_DIV, + TM_MOD, + TM_POW, + TM_UNM, + TM_LT, + TM_LE, + TM_CONCAT, + TM_CALL, + TM_N /* number of elements in the enum */ +} TMS; + + + +#define gfasttm(g,et,e) ((et) == NULL ? NULL : \ + ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e])) + +#define fasttm(l,et,e) gfasttm(G(l), et, e) + +#define ttypename(x) luaT_typenames_[(x) + 1] +#define objtypename(x) ttypename(ttypenv(x)) + +LUAI_DDEC const char *const luaT_typenames_[LUA_TOTALTAGS]; + + +LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); +LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, + TMS event); +LUAI_FUNC void luaT_init (lua_State *L); + +#endif diff --git a/luprex/ext/eris-master/src/lua.c b/luprex/ext/eris-master/src/lua.c new file mode 100644 index 00000000..3b009fa0 --- /dev/null +++ b/luprex/ext/eris-master/src/lua.c @@ -0,0 +1,495 @@ +/* +** $Id: lua.c,v 1.206.1.1 2013/04/12 18:48:47 roberto Exp $ +** Lua stand-alone interpreter +** See Copyright Notice in lua.h +*/ + + +#include +#include +#include +#include + +#define lua_c + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#if !defined(LUA_PROMPT) +#define LUA_PROMPT "> " +#define LUA_PROMPT2 ">> " +#endif + +#if !defined(LUA_PROGNAME) +#define LUA_PROGNAME "lua" +#endif + +#if !defined(LUA_MAXINPUT) +#define LUA_MAXINPUT 512 +#endif + +#if !defined(LUA_INIT) +#define LUA_INIT "LUA_INIT" +#endif + +#define LUA_INITVERSION \ + LUA_INIT "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR + + +/* +** lua_stdin_is_tty detects whether the standard input is a 'tty' (that +** is, whether we're running lua interactively). +*/ +#if defined(LUA_USE_ISATTY) +#include +#define lua_stdin_is_tty() isatty(0) +#elif defined(LUA_WIN) +#include +#include +#define lua_stdin_is_tty() _isatty(_fileno(stdin)) +#else +#define lua_stdin_is_tty() 1 /* assume stdin is a tty */ +#endif + + +/* +** lua_readline defines how to show a prompt and then read a line from +** the standard input. +** lua_saveline defines how to "save" a read line in a "history". +** lua_freeline defines how to free a line read by lua_readline. +*/ +#if defined(LUA_USE_READLINE) + +#include +#include +#include +#define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL) +#define lua_saveline(L,idx) \ + if (lua_rawlen(L,idx) > 0) /* non-empty line? */ \ + add_history(lua_tostring(L, idx)); /* add it to history */ +#define lua_freeline(L,b) ((void)L, free(b)) + +#elif !defined(lua_readline) + +#define lua_readline(L,b,p) \ + ((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \ + fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */ +#define lua_saveline(L,idx) { (void)L; (void)idx; } +#define lua_freeline(L,b) { (void)L; (void)b; } + +#endif + + +static lua_State *globalL = NULL; + +static const char *progname = LUA_PROGNAME; + + + +static void lstop (lua_State *L, lua_Debug *ar) { + (void)ar; /* unused arg. */ + lua_sethook(L, NULL, 0, 0); + luaL_error(L, "interrupted!"); +} + + +static void laction (int i) { + signal(i, SIG_DFL); /* if another SIGINT happens before lstop, + terminate process (default action) */ + lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); +} + + +static void print_usage (const char *badoption) { + luai_writestringerror("%s: ", progname); + if (badoption[1] == 'e' || badoption[1] == 'l') + luai_writestringerror("'%s' needs argument\n", badoption); + else + luai_writestringerror("unrecognized option '%s'\n", badoption); + luai_writestringerror( + "usage: %s [options] [script [args]]\n" + "Available options are:\n" + " -e stat execute string " LUA_QL("stat") "\n" + " -i enter interactive mode after executing " LUA_QL("script") "\n" + " -l name require library " LUA_QL("name") "\n" + " -v show version information\n" + " -E ignore environment variables\n" + " -- stop handling options\n" + " - stop handling options and execute stdin\n" + , + progname); +} + + +static void l_message (const char *pname, const char *msg) { + if (pname) luai_writestringerror("%s: ", pname); + luai_writestringerror("%s\n", msg); +} + + +static int report (lua_State *L, int status) { + if (status != LUA_OK && !lua_isnil(L, -1)) { + const char *msg = lua_tostring(L, -1); + if (msg == NULL) msg = "(error object is not a string)"; + l_message(progname, msg); + lua_pop(L, 1); + /* force a complete garbage collection in case of errors */ + lua_gc(L, LUA_GCCOLLECT, 0); + } + return status; +} + + +/* the next function is called unprotected, so it must avoid errors */ +static void finalreport (lua_State *L, int status) { + if (status != LUA_OK) { + const char *msg = (lua_type(L, -1) == LUA_TSTRING) ? lua_tostring(L, -1) + : NULL; + if (msg == NULL) msg = "(error object is not a string)"; + l_message(progname, msg); + lua_pop(L, 1); + } +} + + +static int traceback (lua_State *L) { + const char *msg = lua_tostring(L, 1); + if (msg) + luaL_traceback(L, L, msg, 1); + else if (!lua_isnoneornil(L, 1)) { /* is there an error object? */ + if (!luaL_callmeta(L, 1, "__tostring")) /* try its 'tostring' metamethod */ + lua_pushliteral(L, "(no error message)"); + } + return 1; +} + + +static int docall (lua_State *L, int narg, int nres) { + int status; + int base = lua_gettop(L) - narg; /* function index */ + lua_pushcfunction(L, traceback); /* push traceback function */ + lua_insert(L, base); /* put it under chunk and args */ + globalL = L; /* to be available to 'laction' */ + signal(SIGINT, laction); + status = lua_pcall(L, narg, nres, base); + signal(SIGINT, SIG_DFL); + lua_remove(L, base); /* remove traceback function */ + return status; +} + + +static void print_version (void) { + luai_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT)); + luai_writeline(); +} + + +static int getargs (lua_State *L, char **argv, int n) { + int narg; + int i; + int argc = 0; + while (argv[argc]) argc++; /* count total number of arguments */ + narg = argc - (n + 1); /* number of arguments to the script */ + luaL_checkstack(L, narg + 3, "too many arguments to script"); + for (i=n+1; i < argc; i++) + lua_pushstring(L, argv[i]); + lua_createtable(L, narg, n + 1); + for (i=0; i < argc; i++) { + lua_pushstring(L, argv[i]); + lua_rawseti(L, -2, i - n); + } + return narg; +} + + +static int dofile (lua_State *L, const char *name) { + int status = luaL_loadfile(L, name); + if (status == LUA_OK) status = docall(L, 0, 0); + return report(L, status); +} + + +static int dostring (lua_State *L, const char *s, const char *name) { + int status = luaL_loadbuffer(L, s, strlen(s), name); + if (status == LUA_OK) status = docall(L, 0, 0); + return report(L, status); +} + + +static int dolibrary (lua_State *L, const char *name) { + int status; + lua_getglobal(L, "require"); + lua_pushstring(L, name); + status = docall(L, 1, 1); /* call 'require(name)' */ + if (status == LUA_OK) + lua_setglobal(L, name); /* global[name] = require return */ + return report(L, status); +} + + +static const char *get_prompt (lua_State *L, int firstline) { + const char *p; + lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2"); + p = lua_tostring(L, -1); + if (p == NULL) p = (firstline ? LUA_PROMPT : LUA_PROMPT2); + return p; +} + +/* mark in error messages for incomplete statements */ +#define EOFMARK "" +#define marklen (sizeof(EOFMARK)/sizeof(char) - 1) + +static int incomplete (lua_State *L, int status) { + if (status == LUA_ERRSYNTAX) { + size_t lmsg; + const char *msg = lua_tolstring(L, -1, &lmsg); + if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) { + lua_pop(L, 1); + return 1; + } + } + return 0; /* else... */ +} + + +static int pushline (lua_State *L, int firstline) { + char buffer[LUA_MAXINPUT]; + char *b = buffer; + size_t l; + const char *prmt = get_prompt(L, firstline); + int readstatus = lua_readline(L, b, prmt); + lua_pop(L, 1); /* remove result from 'get_prompt' */ + if (readstatus == 0) + return 0; /* no input */ + l = strlen(b); + if (l > 0 && b[l-1] == '\n') /* line ends with newline? */ + b[l-1] = '\0'; /* remove it */ + if (firstline && b[0] == '=') /* first line starts with `=' ? */ + lua_pushfstring(L, "return %s", b+1); /* change it to `return' */ + else + lua_pushstring(L, b); + lua_freeline(L, b); + return 1; +} + + +static int loadline (lua_State *L) { + int status; + lua_settop(L, 0); + if (!pushline(L, 1)) + return -1; /* no input */ + for (;;) { /* repeat until gets a complete line */ + size_t l; + const char *line = lua_tolstring(L, 1, &l); + status = luaL_loadbuffer(L, line, l, "=stdin"); + if (!incomplete(L, status)) break; /* cannot try to add lines? */ + if (!pushline(L, 0)) /* no more input? */ + return -1; + lua_pushliteral(L, "\n"); /* add a new line... */ + lua_insert(L, -2); /* ...between the two lines */ + lua_concat(L, 3); /* join them */ + } + lua_saveline(L, 1); + lua_remove(L, 1); /* remove line */ + return status; +} + + +static void dotty (lua_State *L) { + int status; + const char *oldprogname = progname; + progname = NULL; + while ((status = loadline(L)) != -1) { + if (status == LUA_OK) status = docall(L, 0, LUA_MULTRET); + report(L, status); + if (status == LUA_OK && lua_gettop(L) > 0) { /* any result to print? */ + luaL_checkstack(L, LUA_MINSTACK, "too many results to print"); + lua_getglobal(L, "print"); + lua_insert(L, 1); + if (lua_pcall(L, lua_gettop(L)-1, 0, 0) != LUA_OK) + l_message(progname, lua_pushfstring(L, + "error calling " LUA_QL("print") " (%s)", + lua_tostring(L, -1))); + } + } + lua_settop(L, 0); /* clear stack */ + luai_writeline(); + progname = oldprogname; +} + + +static int handle_script (lua_State *L, char **argv, int n) { + int status; + const char *fname; + int narg = getargs(L, argv, n); /* collect arguments */ + lua_setglobal(L, "arg"); + fname = argv[n]; + if (strcmp(fname, "-") == 0 && strcmp(argv[n-1], "--") != 0) + fname = NULL; /* stdin */ + status = luaL_loadfile(L, fname); + lua_insert(L, -(narg+1)); + if (status == LUA_OK) + status = docall(L, narg, LUA_MULTRET); + else + lua_pop(L, narg); + return report(L, status); +} + + +/* check that argument has no extra characters at the end */ +#define noextrachars(x) {if ((x)[2] != '\0') return -1;} + + +/* indices of various argument indicators in array args */ +#define has_i 0 /* -i */ +#define has_v 1 /* -v */ +#define has_e 2 /* -e */ +#define has_E 3 /* -E */ + +#define num_has 4 /* number of 'has_*' */ + + +static int collectargs (char **argv, int *args) { + int i; + for (i = 1; argv[i] != NULL; i++) { + if (argv[i][0] != '-') /* not an option? */ + return i; + switch (argv[i][1]) { /* option */ + case '-': + noextrachars(argv[i]); + return (argv[i+1] != NULL ? i+1 : 0); + case '\0': + return i; + case 'E': + args[has_E] = 1; + break; + case 'i': + noextrachars(argv[i]); + args[has_i] = 1; /* go through */ + case 'v': + noextrachars(argv[i]); + args[has_v] = 1; + break; + case 'e': + args[has_e] = 1; /* go through */ + case 'l': /* both options need an argument */ + if (argv[i][2] == '\0') { /* no concatenated argument? */ + i++; /* try next 'argv' */ + if (argv[i] == NULL || argv[i][0] == '-') + return -(i - 1); /* no next argument or it is another option */ + } + break; + default: /* invalid option; return its index... */ + return -i; /* ...as a negative value */ + } + } + return 0; +} + + +static int runargs (lua_State *L, char **argv, int n) { + int i; + for (i = 1; i < n; i++) { + lua_assert(argv[i][0] == '-'); + switch (argv[i][1]) { /* option */ + case 'e': { + const char *chunk = argv[i] + 2; + if (*chunk == '\0') chunk = argv[++i]; + lua_assert(chunk != NULL); + if (dostring(L, chunk, "=(command line)") != LUA_OK) + return 0; + break; + } + case 'l': { + const char *filename = argv[i] + 2; + if (*filename == '\0') filename = argv[++i]; + lua_assert(filename != NULL); + if (dolibrary(L, filename) != LUA_OK) + return 0; /* stop if file fails */ + break; + } + default: break; + } + } + return 1; +} + + +static int handle_luainit (lua_State *L) { + const char *name = "=" LUA_INITVERSION; + const char *init = getenv(name + 1); + if (init == NULL) { + name = "=" LUA_INIT; + init = getenv(name + 1); /* try alternative name */ + } + if (init == NULL) return LUA_OK; + else if (init[0] == '@') + return dofile(L, init+1); + else + return dostring(L, init, name); +} + + +static int pmain (lua_State *L) { + int argc = (int)lua_tointeger(L, 1); + char **argv = (char **)lua_touserdata(L, 2); + int script; + int args[num_has]; + args[has_i] = args[has_v] = args[has_e] = args[has_E] = 0; + if (argv[0] && argv[0][0]) progname = argv[0]; + script = collectargs(argv, args); + if (script < 0) { /* invalid arg? */ + print_usage(argv[-script]); + return 0; + } + if (args[has_v]) print_version(); + if (args[has_E]) { /* option '-E'? */ + lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */ + lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); + } + /* open standard libraries */ + luaL_checkversion(L); + lua_gc(L, LUA_GCSTOP, 0); /* stop collector during initialization */ + luaL_openlibs(L); /* open libraries */ + lua_gc(L, LUA_GCRESTART, 0); + if (!args[has_E] && handle_luainit(L) != LUA_OK) + return 0; /* error running LUA_INIT */ + /* execute arguments -e and -l */ + if (!runargs(L, argv, (script > 0) ? script : argc)) return 0; + /* execute main script (if there is one) */ + if (script && handle_script(L, argv, script) != LUA_OK) return 0; + if (args[has_i]) /* -i option? */ + dotty(L); + else if (script == 0 && !args[has_e] && !args[has_v]) { /* no arguments? */ + if (lua_stdin_is_tty()) { + print_version(); + dotty(L); + } + else dofile(L, NULL); /* executes stdin as a file */ + } + lua_pushboolean(L, 1); /* signal no errors */ + return 1; +} + + +int main (int argc, char **argv) { + int status, result; + lua_State *L = luaL_newstate(); /* create state */ + if (L == NULL) { + l_message(argv[0], "cannot create state: not enough memory"); + return EXIT_FAILURE; + } + /* call 'pmain' in protected mode */ + lua_pushcfunction(L, &pmain); + lua_pushinteger(L, argc); /* 1st argument */ + lua_pushlightuserdata(L, argv); /* 2nd argument */ + status = lua_pcall(L, 2, 1, 0); + result = lua_toboolean(L, -1); /* get result */ + finalreport(L, status); + lua_close(L); + return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE; +} + diff --git a/luprex/ext/eris-master/src/lua.h b/luprex/ext/eris-master/src/lua.h new file mode 100644 index 00000000..85a6e472 --- /dev/null +++ b/luprex/ext/eris-master/src/lua.h @@ -0,0 +1,453 @@ +/* +** $Id: lua.h,v 1.285.1.4 2015/02/21 14:04:50 roberto Exp $ +** Lua - A Scripting Language +** Lua.org, PUC-Rio, Brazil (http://www.lua.org) +** See Copyright Notice at the end of this file +*/ + + +#ifndef lua_h +#define lua_h + +#include +#include + + +#include "luaconf.h" + + +#define LUA_VERSION_MAJOR "5" +#define LUA_VERSION_MINOR "2" +#define LUA_VERSION_NUM 502 +#define LUA_VERSION_RELEASE "4" + +#define LUA_VERSION "Lua+Eris " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2015 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" + + +/* mark for precompiled code ('Lua') */ +#define LUA_SIGNATURE "\033Lua" + +/* option for multiple returns in 'lua_pcall' and 'lua_call' */ +#define LUA_MULTRET (-1) + + +/* +** pseudo-indices +*/ +#define LUA_REGISTRYINDEX LUAI_FIRSTPSEUDOIDX +#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) + + +/* thread status */ +#define LUA_OK 0 +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRGCMM 5 +#define LUA_ERRERR 6 + + +typedef struct lua_State lua_State; + +typedef int (*lua_CFunction) (lua_State *L); + + +/* +** functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud); + + +/* +** prototype for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + + +/* +** basic types +*/ +#define LUA_TNONE (-1) + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + +#define LUA_NUMTAGS 9 + + + +/* minimum Lua stack available to a C function */ +#define LUA_MINSTACK 20 + + +/* predefined values in the registry */ +#define LUA_RIDX_MAINTHREAD 1 +#define LUA_RIDX_GLOBALS 2 +#define LUA_RIDX_LAST LUA_RIDX_GLOBALS + + +/* type of numbers in Lua */ +typedef LUA_NUMBER lua_Number; + + +/* type for integer functions */ +typedef LUA_INTEGER lua_Integer; + +/* unsigned integer type */ +typedef LUA_UNSIGNED lua_Unsigned; + + + +/* +** generic extra include file +*/ +#if defined(LUA_USER_H) +#include LUA_USER_H +#endif + + +/* +** RCS ident string +*/ +extern const char lua_ident[]; + + +/* +** state manipulation +*/ +LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); +LUA_API void (lua_close) (lua_State *L); +LUA_API lua_State *(lua_newthread) (lua_State *L); + +LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); + + +LUA_API const lua_Number *(lua_version) (lua_State *L); + + +/* +** basic stack manipulation +*/ +LUA_API int (lua_absindex) (lua_State *L, int idx); +LUA_API int (lua_gettop) (lua_State *L); +LUA_API void (lua_settop) (lua_State *L, int idx); +LUA_API void (lua_pushvalue) (lua_State *L, int idx); +LUA_API void (lua_remove) (lua_State *L, int idx); +LUA_API void (lua_insert) (lua_State *L, int idx); +LUA_API void (lua_insert_frame) (lua_State *L, int count); +LUA_API void (lua_replace) (lua_State *L, int idx); +LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx); +LUA_API int (lua_checkstack) (lua_State *L, int sz); +LUA_API int (lua_isthrowing) (lua_State *L); + +LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); + + +/* +** access functions (stack -> C) +*/ + +LUA_API int (lua_isnumber) (lua_State *L, int idx); +LUA_API int (lua_isstring) (lua_State *L, int idx); +LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isuserdata) (lua_State *L, int idx); +LUA_API int (lua_type) (lua_State *L, int idx); +LUA_API const char *(lua_typename) (lua_State *L, int tp); + +LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); +LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); +LUA_API lua_Unsigned (lua_tounsignedx) (lua_State *L, int idx, int *isnum); +LUA_API int (lua_toboolean) (lua_State *L, int idx); +LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); +LUA_API size_t (lua_rawlen) (lua_State *L, int idx); +LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); +LUA_API void *(lua_touserdata) (lua_State *L, int idx); +LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); +LUA_API const void *(lua_topointer) (lua_State *L, int idx); + +LUA_API lua_Integer (lua_getnextid) (lua_State *L); +LUA_API void (lua_setnextid) (lua_State *L, lua_Integer id); + +/* +** Comparison and arithmetic functions +*/ + +#define LUA_OPADD 0 /* ORDER TM */ +#define LUA_OPSUB 1 +#define LUA_OPMUL 2 +#define LUA_OPDIV 3 +#define LUA_OPMOD 4 +#define LUA_OPPOW 5 +#define LUA_OPUNM 6 + +LUA_API void (lua_arith) (lua_State *L, int op); + +#define LUA_OPEQ 0 +#define LUA_OPLT 1 +#define LUA_OPLE 2 + +LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op); +LUA_API int (lua_genlt) (lua_State *L, int idx1, int idx2); + +/* +** push functions (C -> stack) +*/ +LUA_API void (lua_pushnil) (lua_State *L); +LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); +LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); +LUA_API void (lua_pushunsigned) (lua_State *L, lua_Unsigned n); +LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t l); +LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); +LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, + va_list argp); +LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); +LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); +LUA_API void (lua_pushboolean) (lua_State *L, int b); +LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); +LUA_API int (lua_pushthread) (lua_State *L); + + +/* +** get functions (Lua -> stack) +*/ +LUA_API void (lua_getglobal) (lua_State *L, const char *var); +LUA_API void (lua_gettable) (lua_State *L, int idx); +LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_rawget) (lua_State *L, int idx); +LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n); +LUA_API void (lua_rawgetp) (lua_State *L, int idx, const void *p); +LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); +LUA_API int (lua_getmetatable) (lua_State *L, int objindex); +LUA_API void (lua_getuservalue) (lua_State *L, int idx); + + +/* +** set functions (stack -> Lua) +*/ +LUA_API void (lua_setglobal) (lua_State *L, const char *var); +LUA_API void (lua_settable) (lua_State *L, int idx); +LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_rawset) (lua_State *L, int idx); +LUA_API void (lua_rawseti) (lua_State *L, int idx, int n); +LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p); +LUA_API int (lua_setmetatable) (lua_State *L, int objindex); +LUA_API void (lua_setuservalue) (lua_State *L, int idx); +LUA_API void (lua_setflagbits) (lua_State *L, int idx, unsigned short bits); +LUA_API void (lua_modflagbits) (lua_State *L, int idx, unsigned short clear, unsigned short set); +LUA_API unsigned short (lua_getflagbits) (lua_State *L, int idx); + +/* +** 'load' and 'call' functions (load and run Lua code) +*/ +LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, int ctx, + lua_CFunction k); +#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) + +LUA_API int (lua_getctx) (lua_State *L, int *ctx); + +LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, + int ctx, lua_CFunction k); +#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL) + +LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, + const char *chunkname, + const char *mode); + +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data); + + +/* +** coroutine functions +*/ +LUA_API int (lua_yieldk) (lua_State *L, int nresults, int ctx, + lua_CFunction k); +#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) +LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); +LUA_API int (lua_status) (lua_State *L); +LUA_API int (lua_isyieldable) (lua_State *L); + +/* +** garbage-collection function and options +*/ + +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 +#define LUA_GCSETMAJORINC 8 +#define LUA_GCISRUNNING 9 +#define LUA_GCGEN 10 +#define LUA_GCINC 11 + +LUA_API int (lua_gc) (lua_State *L, int what, int data); + + +/* +** miscellaneous functions +*/ + +LUA_API int (lua_error) (lua_State *L); + +LUA_API int (lua_next) (lua_State *L, int idx); +LUA_API int (lua_altnext) (lua_State *L, int idx); + +LUA_API void (lua_concat) (lua_State *L, int n); +LUA_API void (lua_len) (lua_State *L, int idx); + +LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); +LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); + +LUA_API int (lua_nkeys) (lua_State *L, int idx); +LUA_API int (lua_nthkey) (lua_State *L, int idx, int n); + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + +#define lua_tonumber(L,i) lua_tonumberx(L,i,NULL) +#define lua_tointeger(L,i) lua_tointegerx(L,i,NULL) +#define lua_tounsigned(L,i) lua_tounsignedx(L,i,NULL) + +#define lua_pop(L,n) lua_settop(L, -(n)-1) + +#define lua_newtable(L) lua_createtable(L, 0, 0) + +#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) + +#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) + +#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) + +#define lua_pushliteral(L, s) \ + lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1) + +#define lua_pushglobaltable(L) \ + lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) + +#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) + + + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + + +/* +** Event codes +*/ +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 +#define LUA_HOOKTAILCALL 4 + + +/* +** Event masks +*/ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) + +typedef struct lua_Debug lua_Debug; /* activation record */ + + +/* Functions to be called by the debugger in specific events */ +typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); + + +LUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar); +LUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *(lua_getlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_setlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_getupvalue) (lua_State *L, int funcindex, int n); +LUA_API const char *(lua_setupvalue) (lua_State *L, int funcindex, int n); + +LUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n); +LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1, + int fidx2, int n2); + +LUA_API int (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook (lua_gethook) (lua_State *L); +LUA_API int (lua_gethookmask) (lua_State *L); +LUA_API int (lua_gethookcount) (lua_State *L); + + +struct lua_Debug { + int event; + const char *name; /* (n) */ + const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ + const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ + const char *source; /* (S) */ + int currentline; /* (l) */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + unsigned char nups; /* (u) number of upvalues */ + unsigned char nparams;/* (u) number of parameters */ + char isvararg; /* (u) */ + char istailcall; /* (t) */ + char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + struct CallInfo *i_ci; /* active function */ +}; + +/* }====================================================================== */ + + +/****************************************************************************** +* Copyright (C) 1994-2015 Lua.org, PUC-Rio. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ + + +#endif diff --git a/luprex/ext/eris-master/src/lua.hpp b/luprex/ext/eris-master/src/lua.hpp new file mode 100644 index 00000000..ec417f59 --- /dev/null +++ b/luprex/ext/eris-master/src/lua.hpp @@ -0,0 +1,9 @@ +// lua.hpp +// Lua header files for C++ +// <> not supplied automatically because Lua also compiles as C++ + +extern "C" { +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" +} diff --git a/luprex/ext/eris-master/src/luac.c b/luprex/ext/eris-master/src/luac.c new file mode 100644 index 00000000..7409706e --- /dev/null +++ b/luprex/ext/eris-master/src/luac.c @@ -0,0 +1,432 @@ +/* +** $Id: luac.c,v 1.69 2011/11/29 17:46:33 lhf Exp $ +** Lua compiler (saves bytecodes to files; also list bytecodes) +** See Copyright Notice in lua.h +*/ + +#include +#include +#include +#include + +#define luac_c +#define LUA_CORE + +#include "lua.h" +#include "lauxlib.h" + +#include "lobject.h" +#include "lstate.h" +#include "lundump.h" + +static void PrintFunction(const Proto* f, int full); +#define luaU_print PrintFunction + +#define PROGNAME "luac" /* default program name */ +#define OUTPUT PROGNAME ".out" /* default output file */ + +static int listing=0; /* list bytecodes? */ +static int dumping=1; /* dump bytecodes? */ +static int stripping=0; /* strip debug information? */ +static char Output[]={ OUTPUT }; /* default output file name */ +static const char* output=Output; /* actual output file name */ +static const char* progname=PROGNAME; /* actual program name */ + +static void fatal(const char* message) +{ + fprintf(stderr,"%s: %s\n",progname,message); + exit(EXIT_FAILURE); +} + +static void cannot(const char* what) +{ + fprintf(stderr,"%s: cannot %s %s: %s\n",progname,what,output,strerror(errno)); + exit(EXIT_FAILURE); +} + +static void usage(const char* message) +{ + if (*message=='-') + fprintf(stderr,"%s: unrecognized option " LUA_QS "\n",progname,message); + else + fprintf(stderr,"%s: %s\n",progname,message); + fprintf(stderr, + "usage: %s [options] [filenames]\n" + "Available options are:\n" + " -l list (use -l -l for full listing)\n" + " -o name output to file " LUA_QL("name") " (default is \"%s\")\n" + " -p parse only\n" + " -s strip debug information\n" + " -v show version information\n" + " -- stop handling options\n" + " - stop handling options and process stdin\n" + ,progname,Output); + exit(EXIT_FAILURE); +} + +#define IS(s) (strcmp(argv[i],s)==0) + +static int doargs(int argc, char* argv[]) +{ + int i; + int version=0; + if (argv[0]!=NULL && *argv[0]!=0) progname=argv[0]; + for (i=1; itop+(i)) + +static const Proto* combine(lua_State* L, int n) +{ + if (n==1) + return toproto(L,-1); + else + { + Proto* f; + int i=n; + if (lua_load(L,reader,&i,"=(" PROGNAME ")",NULL)!=LUA_OK) fatal(lua_tostring(L,-1)); + f=toproto(L,-1); + for (i=0; ip[i]=toproto(L,i-n-1); + if (f->p[i]->sizeupvalues>0) f->p[i]->upvalues[0].instack=0; + } + f->sizelineinfo=0; + return f; + } +} + +static int writer(lua_State* L, const void* p, size_t size, void* u) +{ + UNUSED(L); + return (fwrite(p,size,1,(FILE*)u)!=1) && (size!=0); +} + +static int pmain(lua_State* L) +{ + int argc=(int)lua_tointeger(L,1); + char** argv=(char**)lua_touserdata(L,2); + const Proto* f; + int i; + if (!lua_checkstack(L,argc)) fatal("too many input files"); + for (i=0; i1); + if (dumping) + { + FILE* D= (output==NULL) ? stdout : fopen(output,"wb"); + if (D==NULL) cannot("open"); + lua_lock(L); + luaU_dump(L,f,writer,D,stripping); + lua_unlock(L); + if (ferror(D)) cannot("write"); + if (fclose(D)) cannot("close"); + } + return 0; +} + +int main(int argc, char* argv[]) +{ + lua_State* L; + int i=doargs(argc,argv); + argc-=i; argv+=i; + if (argc<=0) usage("no input files given"); + L=luaL_newstate(); + if (L==NULL) fatal("cannot create state: not enough memory"); + lua_pushcfunction(L,&pmain); + lua_pushinteger(L,argc); + lua_pushlightuserdata(L,argv); + if (lua_pcall(L,2,0,0)!=LUA_OK) fatal(lua_tostring(L,-1)); + lua_close(L); + return EXIT_SUCCESS; +} + +/* +** $Id: print.c,v 1.69 2013/07/04 01:03:46 lhf Exp $ +** print bytecodes +** See Copyright Notice in lua.h +*/ + +#include +#include + +#define luac_c +#define LUA_CORE + +#include "ldebug.h" +#include "lobject.h" +#include "lopcodes.h" + +#define VOID(p) ((const void*)(p)) + +static void PrintString(const TString* ts) +{ + const char* s=getstr(ts); + size_t i,n=ts->tsv.len; + printf("%c",'"'); + for (i=0; ik[i]; + switch (ttypenv(o)) + { + case LUA_TNIL: + printf("nil"); + break; + case LUA_TBOOLEAN: + printf(bvalue(o) ? "true" : "false"); + break; + case LUA_TNUMBER: + printf(LUA_NUMBER_FMT,nvalue(o)); + break; + case LUA_TSTRING: + PrintString(rawtsvalue(o)); + break; + default: /* cannot happen */ + printf("? type=%d",ttype(o)); + break; + } +} + +#define UPVALNAME(x) ((f->upvalues[x].name) ? getstr(f->upvalues[x].name) : "-") +#define MYK(x) (-1-(x)) + +static void PrintCode(const Proto* f) +{ + const Instruction* code=f->code; + int pc,n=f->sizecode; + for (pc=0; pc0) printf("[%d]\t",line); else printf("[-]\t"); + printf("%-9s\t",luaP_opnames[o]); + switch (getOpMode(o)) + { + case iABC: + printf("%d",a); + if (getBMode(o)!=OpArgN) printf(" %d",ISK(b) ? (MYK(INDEXK(b))) : b); + if (getCMode(o)!=OpArgN) printf(" %d",ISK(c) ? (MYK(INDEXK(c))) : c); + break; + case iABx: + printf("%d",a); + if (getBMode(o)==OpArgK) printf(" %d",MYK(bx)); + if (getBMode(o)==OpArgU) printf(" %d",bx); + break; + case iAsBx: + printf("%d %d",a,sbx); + break; + case iAx: + printf("%d",MYK(ax)); + break; + } + switch (o) + { + case OP_LOADK: + printf("\t; "); PrintConstant(f,bx); + break; + case OP_GETUPVAL: + case OP_SETUPVAL: + printf("\t; %s",UPVALNAME(b)); + break; + case OP_GETTABUP: + printf("\t; %s",UPVALNAME(b)); + if (ISK(c)) { printf(" "); PrintConstant(f,INDEXK(c)); } + break; + case OP_SETTABUP: + printf("\t; %s",UPVALNAME(a)); + if (ISK(b)) { printf(" "); PrintConstant(f,INDEXK(b)); } + if (ISK(c)) { printf(" "); PrintConstant(f,INDEXK(c)); } + break; + case OP_GETTABLE: + case OP_SELF: + if (ISK(c)) { printf("\t; "); PrintConstant(f,INDEXK(c)); } + break; + case OP_SETTABLE: + case OP_ADD: + case OP_SUB: + case OP_MUL: + case OP_DIV: + case OP_POW: + case OP_EQ: + case OP_LT: + case OP_LE: + if (ISK(b) || ISK(c)) + { + printf("\t; "); + if (ISK(b)) PrintConstant(f,INDEXK(b)); else printf("-"); + printf(" "); + if (ISK(c)) PrintConstant(f,INDEXK(c)); else printf("-"); + } + break; + case OP_JMP: + case OP_FORLOOP: + case OP_FORPREP: + case OP_TFORLOOP: + printf("\t; to %d",sbx+pc+2); + break; + case OP_CLOSURE: + printf("\t; %p",VOID(f->p[bx])); + break; + case OP_SETLIST: + if (c==0) printf("\t; %d",(int)code[++pc]); else printf("\t; %d",c); + break; + case OP_EXTRAARG: + printf("\t; "); PrintConstant(f,ax); + break; + default: + break; + } + printf("\n"); + } +} + +#define SS(x) ((x==1)?"":"s") +#define S(x) (int)(x),SS(x) + +static void PrintHeader(const Proto* f) +{ + const char* s=f->source ? getstr(f->source) : "=?"; + if (*s=='@' || *s=='=') + s++; + else if (*s==LUA_SIGNATURE[0]) + s="(bstring)"; + else + s="(string)"; + printf("\n%s <%s:%d,%d> (%d instruction%s at %p)\n", + (f->linedefined==0)?"main":"function",s, + f->linedefined,f->lastlinedefined, + S(f->sizecode),VOID(f)); + printf("%d%s param%s, %d slot%s, %d upvalue%s, ", + (int)(f->numparams),f->is_vararg?"+":"",SS(f->numparams), + S(f->maxstacksize),S(f->sizeupvalues)); + printf("%d local%s, %d constant%s, %d function%s\n", + S(f->sizelocvars),S(f->sizek),S(f->sizep)); +} + +static void PrintDebug(const Proto* f) +{ + int i,n; + n=f->sizek; + printf("constants (%d) for %p:\n",n,VOID(f)); + for (i=0; isizelocvars; + printf("locals (%d) for %p:\n",n,VOID(f)); + for (i=0; ilocvars[i].varname),f->locvars[i].startpc+1,f->locvars[i].endpc+1); + } + n=f->sizeupvalues; + printf("upvalues (%d) for %p:\n",n,VOID(f)); + for (i=0; iupvalues[i].instack,f->upvalues[i].idx); + } +} + +static void PrintFunction(const Proto* f, int full) +{ + int i,n=f->sizep; + PrintHeader(f); + PrintCode(f); + if (full) PrintDebug(f); + for (i=0; ip[i],full); +} diff --git a/luprex/ext/eris-master/src/luaconf.h b/luprex/ext/eris-master/src/luaconf.h new file mode 100644 index 00000000..e5e103cc --- /dev/null +++ b/luprex/ext/eris-master/src/luaconf.h @@ -0,0 +1,550 @@ +/* +** $Id: luaconf.h,v 1.176.1.2 2013/11/21 17:26:16 roberto Exp $ +** Configuration file for Lua +** See Copyright Notice in lua.h +*/ + + +#ifndef lconfig_h +#define lconfig_h + +#include +#include + + +/* +** ================================================================== +** Search for "@@" to find all configurable definitions. +** =================================================================== +*/ + + +/* +@@ LUA_ANSI controls the use of non-ansi features. +** CHANGE it (define it) if you want Lua to avoid the use of any +** non-ansi feature or library. +*/ +#if !defined(LUA_ANSI) && defined(__STRICT_ANSI__) +#define LUA_ANSI +#endif + + +#if !defined(LUA_ANSI) && defined(_WIN32) && !defined(_WIN32_WCE) +#define LUA_WIN /* enable goodies for regular Windows platforms */ +#endif + +#if defined(LUA_WIN) +#define LUA_DL_DLL +#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */ +#endif + + + +#if defined(LUA_USE_LINUX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* needs an extra library: -ldl */ +#define LUA_USE_READLINE /* needs some extra libraries */ +#define LUA_USE_STRTODHEX /* assume 'strtod' handles hex formats */ +#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */ +#define LUA_USE_LONGLONG /* assume support for long long */ +#endif + +#if defined(LUA_USE_MACOSX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* does not need -ldl */ +#define LUA_USE_READLINE /* needs an extra library: -lreadline */ +#define LUA_USE_STRTODHEX /* assume 'strtod' handles hex formats */ +#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */ +#define LUA_USE_LONGLONG /* assume support for long long */ +#endif + + + +/* +@@ LUA_USE_POSIX includes all functionality listed as X/Open System +@* Interfaces Extension (XSI). +** CHANGE it (define it) if your system is XSI compatible. +*/ +#if defined(LUA_USE_POSIX) +#define LUA_USE_MKSTEMP +#define LUA_USE_ISATTY +#define LUA_USE_POPEN +#define LUA_USE_ULONGJMP +#define LUA_USE_GMTIME_R +#endif + + + +/* +@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for +@* Lua libraries. +@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for +@* C libraries. +** CHANGE them if your machine has a non-conventional directory +** hierarchy or if you want to install your libraries in +** non-conventional directories. +*/ +#if defined(_WIN32) /* { */ +/* +** In Windows, any exclamation mark ('!') in the path is replaced by the +** path of the directory of the executable file of the current process. +*/ +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" ".\\?.lua" +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll;" ".\\?.dll" + +#else /* }{ */ + +#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR "/" +#define LUA_ROOT "/usr/local/" +#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR +#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" "./?.lua" +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so" +#endif /* } */ + + +/* +@@ LUA_DIRSEP is the directory separator (for submodules). +** CHANGE it if your machine does not use "/" as the directory separator +** and is not Windows. (On Windows Lua automatically uses "\".) +*/ +#if defined(_WIN32) +#define LUA_DIRSEP "\\" +#else +#define LUA_DIRSEP "/" +#endif + + +/* +@@ LUA_ENV is the name of the variable that holds the current +@@ environment, used to access global names. +** CHANGE it if you do not like this name. +*/ +#define LUA_ENV "_ENV" + + +/* +@@ LUA_API is a mark for all core API functions. +@@ LUALIB_API is a mark for all auxiliary library functions. +@@ LUAMOD_API is a mark for all standard library opening functions. +** CHANGE them if you need to define those functions in some special way. +** For instance, if you want to create one Windows DLL with the core and +** the libraries, you may want to use the following definition (define +** LUA_BUILD_AS_DLL to get it). +*/ +#if defined(LUA_BUILD_AS_DLL) /* { */ + +#if defined(LUA_CORE) || defined(LUA_LIB) /* { */ +#define LUA_API __declspec(dllexport) +#else /* }{ */ +#define LUA_API __declspec(dllimport) +#endif /* } */ + +#else /* }{ */ + +#define LUA_API extern + +#endif /* } */ + + +/* more often than not the libs go together with the core */ +#define LUALIB_API LUA_API +#define LUAMOD_API LUALIB_API + + +/* +@@ LUAI_FUNC is a mark for all extern functions that are not to be +@* exported to outside modules. +@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables +@* that are not to be exported to outside modules (LUAI_DDEF for +@* definitions and LUAI_DDEC for declarations). +** CHANGE them if you need to mark them in some special way. Elf/gcc +** (versions 3.2 and later) mark them as "hidden" to optimize access +** when Lua is compiled as a shared library. Not all elf targets support +** this attribute. Unfortunately, gcc does not offer a way to check +** whether the target offers that support, and those without support +** give a warning about it. To avoid these warnings, change to the +** default definition. +*/ +#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ + defined(__ELF__) /* { */ +#define LUAI_FUNC __attribute__((visibility("hidden"))) extern +#define LUAI_DDEC LUAI_FUNC +#define LUAI_DDEF /* empty */ + +#else /* }{ */ +#define LUAI_FUNC extern +#define LUAI_DDEC extern +#define LUAI_DDEF /* empty */ +#endif /* } */ + + + +/* +@@ LUA_QL describes how error messages quote program elements. +** CHANGE it if you want a different appearance. +*/ +#define LUA_QL(x) "'" x "'" +#define LUA_QS LUA_QL("%s") + + +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@* of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 + + +/* +@@ luai_writestring/luai_writeline define how 'print' prints its results. +** They are only used in libraries and the stand-alone program. (The #if +** avoids including 'stdio.h' everywhere.) +*/ +#if defined(LUA_LIB) || defined(lua_c) +#include +#define luai_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) +#define luai_writeline() (luai_writestring("\n", 1), fflush(stdout)) +#endif + +/* +@@ luai_writestringerror defines how to print error messages. +** (A format string with one argument is enough for Lua...) +*/ +#define luai_writestringerror(s,p) \ + (fprintf(stderr, (s), (p)), fflush(stderr)) + +/* +@@ LUAI_MAXSHORTLEN is the maximum length for short strings, that is, +** strings that are internalized. (Cannot be smaller than reserved words +** or tags for metamethods, as these strings must be internalized; +** #("function") = 8, #("__newindex") = 10.) +*/ +#define LUAI_MAXSHORTLEN 40 + + + +/* +** {================================================================== +** Compatibility with previous versions +** =================================================================== +*/ + +/* +@@ LUA_COMPAT_ALL controls all compatibility options. +** You can define it to get all options, or change specific options +** to fit your specific needs. +*/ +#if defined(LUA_COMPAT_ALL) /* { */ + +/* +@@ LUA_COMPAT_UNPACK controls the presence of global 'unpack'. +** You can replace it with 'table.unpack'. +*/ +#define LUA_COMPAT_UNPACK + +/* +@@ LUA_COMPAT_LOADERS controls the presence of table 'package.loaders'. +** You can replace it with 'package.searchers'. +*/ +#define LUA_COMPAT_LOADERS + +/* +@@ macro 'lua_cpcall' emulates deprecated function lua_cpcall. +** You can call your C function directly (with light C functions). +*/ +#define lua_cpcall(L,f,u) \ + (lua_pushcfunction(L, (f)), \ + lua_pushlightuserdata(L,(u)), \ + lua_pcall(L,1,0,0)) + + +/* +@@ LUA_COMPAT_LOG10 defines the function 'log10' in the math library. +** You can rewrite 'log10(x)' as 'log(x, 10)'. +*/ +#define LUA_COMPAT_LOG10 + +/* +@@ LUA_COMPAT_LOADSTRING defines the function 'loadstring' in the base +** library. You can rewrite 'loadstring(s)' as 'load(s)'. +*/ +#define LUA_COMPAT_LOADSTRING + +/* +@@ LUA_COMPAT_MAXN defines the function 'maxn' in the table library. +*/ +#define LUA_COMPAT_MAXN + +/* +@@ The following macros supply trivial compatibility for some +** changes in the API. The macros themselves document how to +** change your code to avoid using them. +*/ +#define lua_strlen(L,i) lua_rawlen(L, (i)) + +#define lua_objlen(L,i) lua_rawlen(L, (i)) + +#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT) + +/* +@@ LUA_COMPAT_MODULE controls compatibility with previous +** module functions 'module' (Lua) and 'luaL_register' (C). +*/ +#define LUA_COMPAT_MODULE + +#endif /* } */ + +/* }================================================================== */ + + + +/* +@@ LUAI_BITSINT defines the number of bits in an int. +** CHANGE here if Lua cannot automatically detect the number of bits of +** your machine. Probably you do not need to change this. +*/ +/* avoid overflows in comparison */ +#if INT_MAX-20 < 32760 /* { */ +#define LUAI_BITSINT 16 +#elif INT_MAX > 2147483640L /* }{ */ +/* int has at least 32 bits */ +#define LUAI_BITSINT 32 +#else /* }{ */ +#error "you must define LUA_BITSINT with number of bits in an integer" +#endif /* } */ + + +/* +@@ LUA_INT32 is a signed integer with exactly 32 bits. +@@ LUAI_UMEM is an unsigned integer big enough to count the total +@* memory used by Lua. +@@ LUAI_MEM is a signed integer big enough to count the total memory +@* used by Lua. +** CHANGE here if for some weird reason the default definitions are not +** good enough for your machine. Probably you do not need to change +** this. +*/ +#if LUAI_BITSINT >= 32 /* { */ +#define LUA_INT32 int +#define LUAI_UMEM size_t +#define LUAI_MEM ptrdiff_t +#else /* }{ */ +/* 16-bit ints */ +#define LUA_INT32 long +#define LUAI_UMEM unsigned long +#define LUAI_MEM long +#endif /* } */ + + +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +*/ +#if LUAI_BITSINT >= 32 +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif + +/* reserve some space for error handling */ +#define LUAI_FIRSTPSEUDOIDX (-LUAI_MAXSTACK - 1000) + + + + +/* +@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. +** CHANGE it if it uses too much C-stack space. +*/ +#define LUAL_BUFFERSIZE BUFSIZ + + + + +/* +** {================================================================== +@@ LUA_NUMBER is the type of numbers in Lua. +** CHANGE the following definitions only if you want to build Lua +** with a number type different from double. You may also need to +** change lua_number2int & lua_number2integer. +** =================================================================== +*/ + +#define LUA_NUMBER_DOUBLE +#define LUA_NUMBER double + +/* +@@ LUAI_UACNUMBER is the result of an 'usual argument conversion' +@* over a number. +*/ +#define LUAI_UACNUMBER double + + +/* +@@ LUA_NUMBER_SCAN is the format for reading numbers. +@@ LUA_NUMBER_FMT is the format for writing numbers. +@@ lua_number2str converts a number to a string. +@@ LUAI_MAXNUMBER2STR is maximum size of previous conversion. +*/ +#define LUA_NUMBER_SCAN "%lf" +#define LUA_NUMBER_FMT "%.16g" +#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n)) +#define LUAI_MAXNUMBER2STR 32 /* 18 digits, sign, point, and \0 */ + + +/* +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations +*/ +#define l_mathop(x) (x) + + +/* +@@ lua_str2number converts a decimal numeric string to a number. +@@ lua_strx2number converts an hexadecimal numeric string to a number. +** In C99, 'strtod' does both conversions. C89, however, has no function +** to convert floating hexadecimal strings to numbers. For these +** systems, you can leave 'lua_strx2number' undefined and Lua will +** provide its own implementation. +*/ +#define lua_str2number(s,p) strtod((s), (p)) + +#if defined(LUA_USE_STRTODHEX) +#define lua_strx2number(s,p) strtod((s), (p)) +#endif + + +/* +@@ The luai_num* macros define the primitive operations over numbers. +*/ + +/* the following operations need the math library */ +#if defined(lobject_c) || defined(lvm_c) +#include +#define luai_nummod(L,a,b) ((a) - l_mathop(floor)((a)/(b))*(b)) +#define luai_numpow(L,a,b) (l_mathop(pow)(a,b)) +#endif + +/* these are quite standard operations */ +#if defined(LUA_CORE) +#define luai_numadd(L,a,b) ((a)+(b)) +#define luai_numsub(L,a,b) ((a)-(b)) +#define luai_nummul(L,a,b) ((a)*(b)) +#define luai_numdiv(L,a,b) ((a)/(b)) +#define luai_numunm(L,a) (-(a)) +#define luai_numeq(a,b) ((a)==(b)) +#define luai_numlt(L,a,b) ((a)<(b)) +#define luai_numle(L,a,b) ((a)<=(b)) +#define luai_numisnan(L,a) (!luai_numeq((a), (a))) +#endif + + + +/* +@@ LUA_INTEGER is the integral type used by lua_pushinteger/lua_tointeger. +** CHANGE that if ptrdiff_t is not adequate on your machine. (On most +** machines, ptrdiff_t gives a good choice between int or long.) +*/ +#define LUA_INTEGER ptrdiff_t + +/* +@@ LUA_UNSIGNED is the integral type used by lua_pushunsigned/lua_tounsigned. +** It must have at least 32 bits. +*/ +#define LUA_UNSIGNED unsigned LUA_INT32 + + + +/* +** Some tricks with doubles +*/ + +#if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) /* { */ +/* +** The next definitions activate some tricks to speed up the +** conversion from doubles to integer types, mainly to LUA_UNSIGNED. +** +@@ LUA_MSASMTRICK uses Microsoft assembler to avoid clashes with a +** DirectX idiosyncrasy. +** +@@ LUA_IEEE754TRICK uses a trick that should work on any machine +** using IEEE754 with a 32-bit integer type. +** +@@ LUA_IEEELL extends the trick to LUA_INTEGER; should only be +** defined when LUA_INTEGER is a 32-bit integer. +** +@@ LUA_IEEEENDIAN is the endianness of doubles in your machine +** (0 for little endian, 1 for big endian); if not defined, Lua will +** check it dynamically for LUA_IEEE754TRICK (but not for LUA_NANTRICK). +** +@@ LUA_NANTRICK controls the use of a trick to pack all types into +** a single double value, using NaN values to represent non-number +** values. The trick only works on 32-bit machines (ints and pointers +** are 32-bit values) with numbers represented as IEEE 754-2008 doubles +** with conventional endianess (12345678 or 87654321), in CPUs that do +** not produce signaling NaN values (all NaNs are quiet). +*/ + +/* Microsoft compiler on a Pentium (32 bit) ? */ +#if defined(LUA_WIN) && defined(_MSC_VER) && defined(_M_IX86) /* { */ + +#define LUA_MSASMTRICK +#define LUA_IEEEENDIAN 0 +#define LUA_NANTRICK + + +/* pentium 32 bits? */ +#elif defined(__i386__) || defined(__i386) || defined(__X86__) /* }{ */ + +#define LUA_IEEE754TRICK +#define LUA_IEEELL +#define LUA_IEEEENDIAN 0 +#define LUA_NANTRICK + +/* pentium 64 bits? */ +#elif defined(__x86_64) /* }{ */ + +#define LUA_IEEE754TRICK +#define LUA_IEEEENDIAN 0 + +#elif defined(__POWERPC__) || defined(__ppc__) /* }{ */ + +#define LUA_IEEE754TRICK +#define LUA_IEEEENDIAN 1 + +#else /* }{ */ + +/* assume IEEE754 and a 32-bit integer type */ +#define LUA_IEEE754TRICK + +#endif /* } */ + +#endif /* } */ + +/* }================================================================== */ + + + + +/* =================================================================== */ + +/* +** Local configuration. You can use this space to add your redefinitions +** without modifying the main part of the file. +*/ + + + +#endif + diff --git a/luprex/ext/eris-master/src/lualib.h b/luprex/ext/eris-master/src/lualib.h new file mode 100644 index 00000000..5b2232d5 --- /dev/null +++ b/luprex/ext/eris-master/src/lualib.h @@ -0,0 +1,58 @@ +/* +** $Id: lualib.h,v 1.43.1.1 2013/04/12 18:48:47 roberto Exp $ +** Lua standard libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lualib_h +#define lualib_h + +#include "lua.h" + + + +LUAMOD_API int (luaopen_base) (lua_State *L); + +#define LUA_COLIBNAME "coroutine" +LUAMOD_API int (luaopen_coroutine) (lua_State *L); + +#define LUA_TABLIBNAME "table" +LUAMOD_API int (luaopen_table) (lua_State *L); + +#define LUA_IOLIBNAME "io" +LUAMOD_API int (luaopen_io) (lua_State *L); + +#define LUA_OSLIBNAME "os" +LUAMOD_API int (luaopen_os) (lua_State *L); + +#define LUA_STRLIBNAME "string" +LUAMOD_API int (luaopen_string) (lua_State *L); + +#define LUA_BITLIBNAME "bit32" +LUAMOD_API int (luaopen_bit32) (lua_State *L); + +#define LUA_MATHLIBNAME "math" +LUAMOD_API int (luaopen_math) (lua_State *L); + +#define LUA_DBLIBNAME "debug" +LUAMOD_API int (luaopen_debug) (lua_State *L); + +#define LUA_LOADLIBNAME "package" +LUAMOD_API int (luaopen_package) (lua_State *L); + +#define LUA_ERISLIBNAME "eris" +LUAMOD_API int (luaopen_eris) (lua_State *L); + + +/* open all previous libraries */ +LUALIB_API void (luaL_openlibs) (lua_State *L); + + + +#if !defined(lua_assert) +#define lua_assert(x) ((void)0) +#endif + + +#endif diff --git a/luprex/ext/eris-master/src/lundump.c b/luprex/ext/eris-master/src/lundump.c new file mode 100644 index 00000000..4163cb5d --- /dev/null +++ b/luprex/ext/eris-master/src/lundump.c @@ -0,0 +1,258 @@ +/* +** $Id: lundump.c,v 2.22.1.1 2013/04/12 18:48:47 roberto Exp $ +** load precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#include + +#define lundump_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstring.h" +#include "lundump.h" +#include "lzio.h" + +typedef struct { + lua_State* L; + ZIO* Z; + Mbuffer* b; + const char* name; +} LoadState; + +static l_noret error(LoadState* S, const char* why) +{ + luaO_pushfstring(S->L,"%s: %s precompiled chunk",S->name,why); + luaD_throw(S->L,LUA_ERRSYNTAX); +} + +#define LoadMem(S,b,n,size) LoadBlock(S,b,(n)*(size)) +#define LoadByte(S) (lu_byte)LoadChar(S) +#define LoadVar(S,x) LoadMem(S,&x,1,sizeof(x)) +#define LoadVector(S,b,n,size) LoadMem(S,b,n,size) + +#if !defined(luai_verifycode) +#define luai_verifycode(L,b,f) /* empty */ +#endif + +static void LoadBlock(LoadState* S, void* b, size_t size) +{ + if (luaZ_read(S->Z,b,size)!=0) error(S,"truncated"); +} + +static int LoadChar(LoadState* S) +{ + char x; + LoadVar(S,x); + return x; +} + +static int LoadInt(LoadState* S) +{ + int x; + LoadVar(S,x); + if (x<0) error(S,"corrupted"); + return x; +} + +static lua_Number LoadNumber(LoadState* S) +{ + lua_Number x; + LoadVar(S,x); + return x; +} + +static TString* LoadString(LoadState* S) +{ + size_t size; + LoadVar(S,size); + if (size==0) + return NULL; + else + { + char* s=luaZ_openspace(S->L,S->b,size); + LoadBlock(S,s,size*sizeof(char)); + return luaS_newlstr(S->L,s,size-1); /* remove trailing '\0' */ + } +} + +static void LoadCode(LoadState* S, Proto* f) +{ + int n=LoadInt(S); + f->code=luaM_newvector(S->L,n,Instruction); + f->sizecode=n; + LoadVector(S,f->code,n,sizeof(Instruction)); +} + +static void LoadFunction(LoadState* S, Proto* f); + +static void LoadConstants(LoadState* S, Proto* f) +{ + int i,n; + n=LoadInt(S); + f->k=luaM_newvector(S->L,n,TValue); + f->sizek=n; + for (i=0; ik[i]); + for (i=0; ik[i]; + int t=LoadChar(S); + switch (t) + { + case LUA_TNIL: + setnilvalue(o); + break; + case LUA_TBOOLEAN: + setbvalue(o,LoadChar(S)); + break; + case LUA_TNUMBER: + setnvalue(o,LoadNumber(S)); + break; + case LUA_TSTRING: + setsvalue2n(S->L,o,LoadString(S)); + break; + default: lua_assert(0); + } + } + n=LoadInt(S); + f->p=luaM_newvector(S->L,n,Proto*); + f->sizep=n; + for (i=0; ip[i]=NULL; + for (i=0; ip[i]=luaF_newproto(S->L); + LoadFunction(S,f->p[i]); + } +} + +static void LoadUpvalues(LoadState* S, Proto* f) +{ + int i,n; + n=LoadInt(S); + f->upvalues=luaM_newvector(S->L,n,Upvaldesc); + f->sizeupvalues=n; + for (i=0; iupvalues[i].name=NULL; + for (i=0; iupvalues[i].instack=LoadByte(S); + f->upvalues[i].idx=LoadByte(S); + } +} + +static void LoadDebug(LoadState* S, Proto* f) +{ + int i,n; + f->source=LoadString(S); + n=LoadInt(S); + f->lineinfo=luaM_newvector(S->L,n,int); + f->sizelineinfo=n; + LoadVector(S,f->lineinfo,n,sizeof(int)); + n=LoadInt(S); + f->locvars=luaM_newvector(S->L,n,LocVar); + f->sizelocvars=n; + for (i=0; ilocvars[i].varname=NULL; + for (i=0; ilocvars[i].varname=LoadString(S); + f->locvars[i].startpc=LoadInt(S); + f->locvars[i].endpc=LoadInt(S); + } + n=LoadInt(S); + for (i=0; iupvalues[i].name=LoadString(S); +} + +static void LoadFunction(LoadState* S, Proto* f) +{ + f->linedefined=LoadInt(S); + f->lastlinedefined=LoadInt(S); + f->numparams=LoadByte(S); + f->is_vararg=LoadByte(S); + f->maxstacksize=LoadByte(S); + LoadCode(S,f); + LoadConstants(S,f); + LoadUpvalues(S,f); + LoadDebug(S,f); +} + +/* the code below must be consistent with the code in luaU_header */ +#define N0 LUAC_HEADERSIZE +#define N1 (sizeof(LUA_SIGNATURE)-sizeof(char)) +#define N2 N1+2 +#define N3 N2+6 + +static void LoadHeader(LoadState* S) +{ + lu_byte h[LUAC_HEADERSIZE]; + lu_byte s[LUAC_HEADERSIZE]; + luaU_header(h); + memcpy(s,h,sizeof(char)); /* first char already read */ + LoadBlock(S,s+sizeof(char),LUAC_HEADERSIZE-sizeof(char)); + if (memcmp(h,s,N0)==0) return; + if (memcmp(h,s,N1)!=0) error(S,"not a"); + if (memcmp(h,s,N2)!=0) error(S,"version mismatch in"); + if (memcmp(h,s,N3)!=0) error(S,"incompatible"); else error(S,"corrupted"); +} + +/* +** load precompiled chunk +*/ +Closure* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name) +{ + LoadState S; + Closure* cl; + if (*name=='@' || *name=='=') + S.name=name+1; + else if (*name==LUA_SIGNATURE[0]) + S.name="binary string"; + else + S.name=name; + S.L=L; + S.Z=Z; + S.b=buff; + LoadHeader(&S); + cl=luaF_newLclosure(L,1); + setclLvalue(L,L->top,cl); incr_top(L); + cl->l.p=luaF_newproto(L); + LoadFunction(&S,cl->l.p); + if (cl->l.p->sizeupvalues != 1) + { + Proto* p=cl->l.p; + cl=luaF_newLclosure(L,cl->l.p->sizeupvalues); + cl->l.p=p; + setclLvalue(L,L->top-1,cl); + } + luai_verifycode(L,buff,cl->l.p); + return cl; +} + +#define MYINT(s) (s[0]-'0') +#define VERSION MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR) +#define FORMAT 0 /* this is the official format */ + +/* +* make header for precompiled chunks +* if you change the code below be sure to update LoadHeader and FORMAT above +* and LUAC_HEADERSIZE in lundump.h +*/ +void luaU_header (lu_byte* h) +{ + int x=1; + memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-sizeof(char)); + h+=sizeof(LUA_SIGNATURE)-sizeof(char); + *h++=cast_byte(VERSION); + *h++=cast_byte(FORMAT); + *h++=cast_byte(*(char*)&x); /* endianness */ + *h++=cast_byte(sizeof(int)); + *h++=cast_byte(sizeof(size_t)); + *h++=cast_byte(sizeof(Instruction)); + *h++=cast_byte(sizeof(lua_Number)); + *h++=cast_byte(((lua_Number)0.5)==0); /* is lua_Number integral? */ + memcpy(h,LUAC_TAIL,sizeof(LUAC_TAIL)-sizeof(char)); +} diff --git a/luprex/ext/eris-master/src/lundump.h b/luprex/ext/eris-master/src/lundump.h new file mode 100644 index 00000000..5255db25 --- /dev/null +++ b/luprex/ext/eris-master/src/lundump.h @@ -0,0 +1,28 @@ +/* +** $Id: lundump.h,v 1.39.1.1 2013/04/12 18:48:47 roberto Exp $ +** load precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#ifndef lundump_h +#define lundump_h + +#include "lobject.h" +#include "lzio.h" + +/* load one chunk; from lundump.c */ +LUAI_FUNC Closure* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name); + +/* make header; from lundump.c */ +LUAI_FUNC void luaU_header (lu_byte* h); + +/* dump one chunk; from ldump.c */ +LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip); + +/* data to catch conversion errors */ +#define LUAC_TAIL "\x19\x93\r\n\x1a\n" + +/* size in bytes of header of binary files */ +#define LUAC_HEADERSIZE (sizeof(LUA_SIGNATURE)-sizeof(char)+2+6+sizeof(LUAC_TAIL)-sizeof(char)) + +#endif diff --git a/luprex/ext/eris-master/src/lvm.c b/luprex/ext/eris-master/src/lvm.c new file mode 100644 index 00000000..5dcd3c54 --- /dev/null +++ b/luprex/ext/eris-master/src/lvm.c @@ -0,0 +1,861 @@ +/* +** $Id: lvm.c,v 2.155.1.1 2013/04/12 18:48:47 roberto Exp $ +** Lua virtual machine +** See Copyright Notice in lua.h +*/ + + +#include +#include +#include + +#define lvm_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + + +/* limit for table tag-method chains (to avoid loops) */ +#define MAXTAGLOOP 100 + + +const TValue *luaV_tonumber (const TValue *obj, TValue *n) { + lua_Number num; + if (ttisnumber(obj)) return obj; + if (ttisstring(obj) && luaO_str2d(svalue(obj), tsvalue(obj)->len, &num)) { + setnvalue(n, num); + return n; + } + else + return NULL; +} + + +int luaV_tostring (lua_State *L, StkId obj) { + if (!ttisnumber(obj)) + return 0; + else { + char s[LUAI_MAXNUMBER2STR]; + lua_Number n = nvalue(obj); + int l = lua_number2str(s, n); + setsvalue2s(L, obj, luaS_newlstr(L, s, l)); + return 1; + } +} + + +static void traceexec (lua_State *L) { + CallInfo *ci = L->ci; + lu_byte mask = L->hookmask; + int counthook = ((mask & LUA_MASKCOUNT) && L->hookcount == 0); + if (counthook) + resethookcount(L); /* reset count */ + if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ + ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ + return; /* do not call hook again (VM yielded, so it did not move) */ + } + if (counthook) + luaD_hook(L, LUA_HOOKCOUNT, -1); /* call count hook */ + if (mask & LUA_MASKLINE) { + Proto *p = ci_func(ci)->p; + int npc = pcRel(ci->u.l.savedpc, p); + int newline = getfuncline(p, npc); + if (npc == 0 || /* call linehook when enter a new function, */ + ci->u.l.savedpc <= L->oldpc || /* when jump back (loop), or when */ + newline != getfuncline(p, pcRel(L->oldpc, p))) /* enter a new line */ + luaD_hook(L, LUA_HOOKLINE, newline); /* call line hook */ + } + L->oldpc = ci->u.l.savedpc; + if (L->status == LUA_YIELD) { /* did hook yield? */ + if (counthook) + L->hookcount = 1; /* undo decrement to zero */ + ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ + ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ + ci->func = L->top - 1; /* protect stack below results */ + luaD_throw(L, LUA_YIELD); + } +} + + +static void callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, TValue *p3, int hasres) { + ptrdiff_t result = savestack(L, p3); + setobj2s(L, L->top++, f); /* push function */ + setobj2s(L, L->top++, p1); /* 1st argument */ + setobj2s(L, L->top++, p2); /* 2nd argument */ + if (!hasres) /* no result? 'p3' is third argument */ + setobj2s(L, L->top++, p3); /* 3rd argument */ + /* metamethod may yield only when called from Lua code */ + luaD_call(L, L->top - (4 - hasres), hasres, isLua(L->ci)); + if (hasres) { /* if has result, move it to its place */ + p3 = restorestack(L, result); + setobjs2s(L, p3, --L->top); + } +} + + +void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) { + int loop; + for (loop = 0; loop < MAXTAGLOOP; loop++) { + const TValue *tm; + if (ttistable(t)) { /* `t' is a table? */ + Table *h = hvalue(t); + const TValue *res = luaH_get(h, key); /* do a primitive get */ + if (!ttisnil(res) || /* result is not nil? */ + (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */ + setobj2s(L, val, res); + return; + } + /* else will try the tag method */ + } + else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) + luaG_typeerror(L, t, "index"); + if (ttisfunction(tm)) { + callTM(L, tm, t, key, val, 1); + return; + } + t = tm; /* else repeat with 'tm' */ + } + luaG_runerror(L, "loop in gettable"); +} + + +void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { + int loop; + for (loop = 0; loop < MAXTAGLOOP; loop++) { + const TValue *tm; + if (ttistable(t)) { /* `t' is a table? */ + Table *h = hvalue(t); + const TValue *oldval = luaH_get(h, key); + /* if previous value is not nil, there must be a previous entry + in the table; moreover, a metamethod has no relevance */ + if (!ttisnil(oldval) || + /* previous value is nil; must check the metamethod */ + ((tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL)) { + luaH_setupdate(L, h, key, val, oldval); + invalidateTMcache(h); + luaC_barrierback(L, obj2gco(h), val); + return; + } + /* else will try the metamethod */ + } + else /* not a table; check metamethod */ + if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) + luaG_typeerror(L, t, "index"); + /* there is a metamethod */ + if (ttisfunction(tm)) { + callTM(L, tm, t, key, val, 0); + return; + } + t = tm; /* else repeat with 'tm' */ + } + luaG_runerror(L, "loop in settable"); +} + + +static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ + if (ttisnil(tm)) + tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ + if (ttisnil(tm)) return 0; + callTM(L, tm, p1, p2, res, 1); + return 1; +} + + +static const TValue *get_equalTM (lua_State *L, Table *mt1, Table *mt2, + TMS event) { + const TValue *tm1 = fasttm(L, mt1, event); + const TValue *tm2; + if (tm1 == NULL) return NULL; /* no metamethod */ + if (mt1 == mt2) return tm1; /* same metatables => same metamethods */ + tm2 = fasttm(L, mt2, event); + if (tm2 == NULL) return NULL; /* no metamethod */ + if (luaV_rawequalobj(tm1, tm2)) /* same metamethods? */ + return tm1; + return NULL; +} + + +static int call_orderTM (lua_State *L, const TValue *p1, const TValue *p2, + TMS event) { + if (!call_binTM(L, p1, p2, L->top, event)) + return -1; /* no metamethod */ + else + return !l_isfalse(L->top); +} + + +static int l_strcmp (const TString *ls, const TString *rs) { + const char *l = getstr(ls); + size_t ll = ls->tsv.len; + const char *r = getstr(rs); + size_t lr = rs->tsv.len; + for (;;) { + int temp = strcoll(l, r); + if (temp != 0) return temp; + else { /* strings are equal up to a `\0' */ + size_t len = strlen(l); /* index of first `\0' in both strings */ + if (len == lr) /* r is finished? */ + return (len == ll) ? 0 : 1; + else if (len == ll) /* l is finished? */ + return -1; /* l is smaller than r (because r is not finished) */ + /* both strings longer than `len'; go on comparing (after the `\0') */ + len++; + l += len; ll -= len; r += len; lr -= len; + } + } +} + + +int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { + int res; + if (ttisnumber(l) && ttisnumber(r)) + return luai_numlt(L, nvalue(l), nvalue(r)); + else if (ttisstring(l) && ttisstring(r)) + return l_strcmp(rawtsvalue(l), rawtsvalue(r)) < 0; + else if ((res = call_orderTM(L, l, r, TM_LT)) < 0) + luaG_ordererror(L, l, r); + return res; +} + + +int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) { + int res; + if (ttisnumber(l) && ttisnumber(r)) + return luai_numle(L, nvalue(l), nvalue(r)); + else if (ttisstring(l) && ttisstring(r)) + return l_strcmp(rawtsvalue(l), rawtsvalue(r)) <= 0; + else if ((res = call_orderTM(L, l, r, TM_LE)) >= 0) /* first try `le' */ + return res; + else if ((res = call_orderTM(L, r, l, TM_LT)) < 0) /* else try `lt' */ + luaG_ordererror(L, l, r); + return !res; +} + + +/* +** equality of Lua values. L == NULL means raw equality (no metamethods) +*/ +int luaV_equalobj_ (lua_State *L, const TValue *t1, const TValue *t2) { + const TValue *tm; + lua_assert(ttisequal(t1, t2)); + switch (ttype(t1)) { + case LUA_TNIL: return 1; + case LUA_TNUMBER: return luai_numeq(nvalue(t1), nvalue(t2)); + case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ + case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); + case LUA_TLCF: return fvalue(t1) == fvalue(t2); + case LUA_TSHRSTR: return eqshrstr(rawtsvalue(t1), rawtsvalue(t2)); + case LUA_TLNGSTR: return luaS_eqlngstr(rawtsvalue(t1), rawtsvalue(t2)); + case LUA_TUSERDATA: { + if (uvalue(t1) == uvalue(t2)) return 1; + else if (L == NULL) return 0; + tm = get_equalTM(L, uvalue(t1)->metatable, uvalue(t2)->metatable, TM_EQ); + break; /* will try TM */ + } + case LUA_TTABLE: { + if (hvalue(t1) == hvalue(t2)) return 1; + else if (L == NULL) return 0; + tm = get_equalTM(L, hvalue(t1)->metatable, hvalue(t2)->metatable, TM_EQ); + break; /* will try TM */ + } + default: + lua_assert(iscollectable(t1)); + return gcvalue(t1) == gcvalue(t2); + } + if (tm == NULL) return 0; /* no TM? */ + callTM(L, tm, t1, t2, L->top, 1); /* call TM */ + return !l_isfalse(L->top); +} + + +void luaV_concat (lua_State *L, int total) { + lua_assert(total >= 2); + do { + StkId top = L->top; + int n = 2; /* number of elements handled in this pass (at least 2) */ + if (!(ttisstring(top-2) || ttisnumber(top-2)) || !tostring(L, top-1)) { + if (!call_binTM(L, top-2, top-1, top-2, TM_CONCAT)) + luaG_concaterror(L, top-2, top-1); + } + else if (tsvalue(top-1)->len == 0) /* second operand is empty? */ + (void)tostring(L, top - 2); /* result is first operand */ + else if (ttisstring(top-2) && tsvalue(top-2)->len == 0) { + setobjs2s(L, top - 2, top - 1); /* result is second op. */ + } + else { + /* at least two non-empty string values; get as many as possible */ + size_t tl = tsvalue(top-1)->len; + char *buffer; + int i; + /* collect total length */ + for (i = 1; i < total && tostring(L, top-i-1); i++) { + size_t l = tsvalue(top-i-1)->len; + if (l >= (MAX_SIZET/sizeof(char)) - tl) + luaG_runerror(L, "string length overflow"); + tl += l; + } + buffer = luaZ_openspace(L, &G(L)->buff, tl); + tl = 0; + n = i; + do { /* concat all strings */ + size_t l = tsvalue(top-i)->len; + memcpy(buffer+tl, svalue(top-i), l * sizeof(char)); + tl += l; + } while (--i > 0); + setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl)); + } + total -= n-1; /* got 'n' strings to create 1 new */ + L->top -= n-1; /* popped 'n' strings and pushed one */ + } while (total > 1); /* repeat until only 1 result left */ +} + + +void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { + const TValue *tm; + switch (ttypenv(rb)) { + case LUA_TTABLE: { + Table *h = hvalue(rb); + tm = fasttm(L, h->metatable, TM_LEN); + if (tm) break; /* metamethod? break switch to call it */ + setnvalue(ra, cast_num(luaH_getn(h))); /* else primitive len */ + return; + } + case LUA_TSTRING: { + setnvalue(ra, cast_num(tsvalue(rb)->len)); + return; + } + default: { /* try metamethod */ + tm = luaT_gettmbyobj(L, rb, TM_LEN); + if (ttisnil(tm)) /* no metamethod? */ + luaG_typeerror(L, rb, "get length of"); + break; + } + } + callTM(L, tm, rb, rb, ra, 1); +} + + +void luaV_arith (lua_State *L, StkId ra, const TValue *rb, + const TValue *rc, TMS op) { + TValue tempb, tempc; + const TValue *b, *c; + if ((b = luaV_tonumber(rb, &tempb)) != NULL && + (c = luaV_tonumber(rc, &tempc)) != NULL) { + lua_Number res = luaO_arith(op - TM_ADD + LUA_OPADD, nvalue(b), nvalue(c)); + setnvalue(ra, res); + } + else if (!call_binTM(L, rb, rc, ra, op)) + luaG_aritherror(L, rb, rc); +} + + +/* +** check whether cached closure in prototype 'p' may be reused, that is, +** whether there is a cached closure with the same upvalues needed by +** new closure to be created. +*/ +static Closure *getcached (Proto *p, UpVal **encup, StkId base) { + Closure *c = p->cache; + if (c != NULL) { /* is there a cached closure? */ + int nup = p->sizeupvalues; + Upvaldesc *uv = p->upvalues; + int i; + for (i = 0; i < nup; i++) { /* check whether it has right upvalues */ + TValue *v = uv[i].instack ? base + uv[i].idx : encup[uv[i].idx]->v; + if (c->l.upvals[i]->v != v) + return NULL; /* wrong upvalue; cannot reuse closure */ + } + } + return c; /* return cached closure (or NULL if no cached closure) */ +} + + +/* +** create a new Lua closure, push it in the stack, and initialize +** its upvalues. Note that the call to 'luaC_barrierproto' must come +** before the assignment to 'p->cache', as the function needs the +** original value of that field. +*/ +static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, + StkId ra) { + int nup = p->sizeupvalues; + Upvaldesc *uv = p->upvalues; + int i; + Closure *ncl = luaF_newLclosure(L, nup); + ncl->l.p = p; + setclLvalue(L, ra, ncl); /* anchor new closure in stack */ + for (i = 0; i < nup; i++) { /* fill in its upvalues */ + if (uv[i].instack) /* upvalue refers to local variable? */ + ncl->l.upvals[i] = luaF_findupval(L, base + uv[i].idx); + else /* get upvalue from enclosing function */ + ncl->l.upvals[i] = encup[uv[i].idx]; + } + luaC_barrierproto(L, p, ncl); + p->cache = ncl; /* save it on cache for reuse */ +} + + +/* +** finish execution of an opcode interrupted by an yield +*/ +void luaV_finishOp (lua_State *L) { + CallInfo *ci = L->ci; + StkId base = ci->u.l.base; + Instruction inst = *(ci->u.l.savedpc - 1); /* interrupted instruction */ + OpCode op = GET_OPCODE(inst); + switch (op) { /* finish its execution */ + case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: + case OP_MOD: case OP_POW: case OP_UNM: case OP_LEN: + case OP_GETTABUP: case OP_GETTABLE: case OP_SELF: { + setobjs2s(L, base + GETARG_A(inst), --L->top); + break; + } + case OP_LE: case OP_LT: case OP_EQ: { + int res = !l_isfalse(L->top - 1); + L->top--; + /* metamethod should not be called when operand is K */ + lua_assert(!ISK(GETARG_B(inst))); + if (op == OP_LE && /* "<=" using "<" instead? */ + ttisnil(luaT_gettmbyobj(L, base + GETARG_B(inst), TM_LE))) + res = !res; /* invert result */ + lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); + if (res != GETARG_A(inst)) /* condition failed? */ + ci->u.l.savedpc++; /* skip jump instruction */ + break; + } + case OP_CONCAT: { + StkId top = L->top - 1; /* top when 'call_binTM' was called */ + int b = GETARG_B(inst); /* first element to concatenate */ + int total = cast_int(top - 1 - (base + b)); /* yet to concatenate */ + setobj2s(L, top - 2, top); /* put TM result in proper position */ + if (total > 1) { /* are there elements to concat? */ + L->top = top - 1; /* top is one after last element (at top-2) */ + luaV_concat(L, total); /* concat them (may yield again) */ + } + /* move final result to final position */ + setobj2s(L, ci->u.l.base + GETARG_A(inst), L->top - 1); + L->top = ci->top; /* restore top */ + break; + } + case OP_TFORCALL: { + lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_TFORLOOP); + L->top = ci->top; /* correct top */ + break; + } + case OP_CALL: { + if (GETARG_C(inst) - 1 >= 0) /* nresults >= 0? */ + L->top = ci->top; /* adjust results */ + break; + } + case OP_TAILCALL: case OP_SETTABUP: case OP_SETTABLE: + break; + default: lua_assert(0); + } +} + + + +/* +** some macros for common tasks in `luaV_execute' +*/ + +#if !defined luai_runtimecheck +#define luai_runtimecheck(L, c) /* void */ +#endif + + +#define RA(i) (base+GETARG_A(i)) +/* to be used after possible stack reallocation */ +#define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i)) +#define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i)) +#define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \ + ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i)) +#define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \ + ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i)) +#define KBx(i) \ + (k + (GETARG_Bx(i) != 0 ? GETARG_Bx(i) - 1 : GETARG_Ax(*ci->u.l.savedpc++))) + + +/* execute a jump instruction */ +#define dojump(ci,i,e) \ + { int a = GETARG_A(i); \ + if (a > 0) luaF_close(L, ci->u.l.base + a - 1); \ + ci->u.l.savedpc += GETARG_sBx(i) + e; } + +/* for test instructions, execute the jump instruction that follows it */ +#define donextjump(ci) { i = *ci->u.l.savedpc; dojump(ci, i, 1); } + + +#define Protect(x) { {x;}; base = ci->u.l.base; } + +#define checkGC(L,c) \ + Protect( luaC_condGC(L,{L->top = (c); /* limit of live values */ \ + luaC_step(L); \ + L->top = ci->top;}) /* restore top */ \ + luai_threadyield(L); ) + + +#define arith_op(op,tm) { \ + TValue *rb = RKB(i); \ + TValue *rc = RKC(i); \ + if (ttisnumber(rb) && ttisnumber(rc)) { \ + lua_Number nb = nvalue(rb), nc = nvalue(rc); \ + setnvalue(ra, op(L, nb, nc)); \ + } \ + else { Protect(luaV_arith(L, ra, rb, rc, tm)); } } + + +#define vmdispatch(o) switch(o) +#define vmcase(l,b) case l: {b} break; +#define vmcasenb(l,b) case l: {b} /* nb = no break */ + +void luaV_execute (lua_State *L) { + CallInfo *ci = L->ci; + LClosure *cl; + TValue *k; + StkId base; + newframe: /* reentry point when frame changes (call/return) */ + lua_assert(ci == L->ci); + cl = clLvalue(ci->func); + k = cl->p->k; + base = ci->u.l.base; + /* main loop of interpreter */ + for (;;) { + Instruction i = *(ci->u.l.savedpc++); + StkId ra; + if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && + (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) { + Protect(traceexec(L)); + } + /* WARNING: several calls may realloc the stack and invalidate `ra' */ + ra = RA(i); + lua_assert(base == ci->u.l.base); + lua_assert(base <= L->top && L->top < L->stack + L->stacksize); + vmdispatch (GET_OPCODE(i)) { + vmcase(OP_MOVE, + setobjs2s(L, ra, RB(i)); + ) + vmcase(OP_LOADK, + TValue *rb = k + GETARG_Bx(i); + setobj2s(L, ra, rb); + ) + vmcase(OP_LOADKX, + TValue *rb; + lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG); + rb = k + GETARG_Ax(*ci->u.l.savedpc++); + setobj2s(L, ra, rb); + ) + vmcase(OP_LOADBOOL, + setbvalue(ra, GETARG_B(i)); + if (GETARG_C(i)) ci->u.l.savedpc++; /* skip next instruction (if C) */ + ) + vmcase(OP_LOADNIL, + int b = GETARG_B(i); + do { + setnilvalue(ra++); + } while (b--); + ) + vmcase(OP_GETUPVAL, + int b = GETARG_B(i); + setobj2s(L, ra, cl->upvals[b]->v); + ) + vmcase(OP_GETTABUP, + int b = GETARG_B(i); + Protect(luaV_gettable(L, cl->upvals[b]->v, RKC(i), ra)); + ) + vmcase(OP_GETTABLE, + Protect(luaV_gettable(L, RB(i), RKC(i), ra)); + ) + vmcase(OP_SETTABUP, + int a = GETARG_A(i); + Protect(luaV_settable(L, cl->upvals[a]->v, RKB(i), RKC(i))); + ) + vmcase(OP_SETUPVAL, + UpVal *uv = cl->upvals[GETARG_B(i)]; + setobj(L, uv->v, ra); + luaC_barrier(L, uv, ra); + ) + vmcase(OP_SETTABLE, + Protect(luaV_settable(L, ra, RKB(i), RKC(i))); + ) + vmcase(OP_NEWTABLE, + int b = GETARG_B(i); + int c = GETARG_C(i); + Table *t = luaH_new(L); + sethvalue(L, ra, t); + if (b != 0 || c != 0) + luaH_resize(L, t, luaO_fb2int(b), luaO_fb2int(c)); + checkGC(L, ra + 1); + ) + vmcase(OP_SELF, + StkId rb = RB(i); + setobjs2s(L, ra+1, rb); + Protect(luaV_gettable(L, rb, RKC(i), ra)); + ) + vmcase(OP_ADD, + arith_op(luai_numadd, TM_ADD); + ) + vmcase(OP_SUB, + arith_op(luai_numsub, TM_SUB); + ) + vmcase(OP_MUL, + arith_op(luai_nummul, TM_MUL); + ) + vmcase(OP_DIV, + arith_op(luai_numdiv, TM_DIV); + ) + vmcase(OP_MOD, + arith_op(luai_nummod, TM_MOD); + ) + vmcase(OP_POW, + arith_op(luai_numpow, TM_POW); + ) + vmcase(OP_UNM, + TValue *rb = RB(i); + if (ttisnumber(rb)) { + lua_Number nb = nvalue(rb); + setnvalue(ra, luai_numunm(L, nb)); + } + else { + Protect(luaV_arith(L, ra, rb, rb, TM_UNM)); + } + ) + vmcase(OP_NOT, + TValue *rb = RB(i); + int res = l_isfalse(rb); /* next assignment may change this value */ + setbvalue(ra, res); + ) + vmcase(OP_LEN, + Protect(luaV_objlen(L, ra, RB(i))); + ) + vmcase(OP_CONCAT, + int b = GETARG_B(i); + int c = GETARG_C(i); + StkId rb; + L->top = base + c + 1; /* mark the end of concat operands */ + Protect(luaV_concat(L, c - b + 1)); + ra = RA(i); /* 'luav_concat' may invoke TMs and move the stack */ + rb = b + base; + setobjs2s(L, ra, rb); + checkGC(L, (ra >= rb ? ra + 1 : rb)); + L->top = ci->top; /* restore top */ + ) + vmcase(OP_JMP, + dojump(ci, i, 0); + ) + vmcase(OP_EQ, + TValue *rb = RKB(i); + TValue *rc = RKC(i); + Protect( + if (cast_int(equalobj(L, rb, rc)) != GETARG_A(i)) + ci->u.l.savedpc++; + else + donextjump(ci); + ) + ) + vmcase(OP_LT, + Protect( + if (luaV_lessthan(L, RKB(i), RKC(i)) != GETARG_A(i)) + ci->u.l.savedpc++; + else + donextjump(ci); + ) + ) + vmcase(OP_LE, + Protect( + if (luaV_lessequal(L, RKB(i), RKC(i)) != GETARG_A(i)) + ci->u.l.savedpc++; + else + donextjump(ci); + ) + ) + vmcase(OP_TEST, + if (GETARG_C(i) ? l_isfalse(ra) : !l_isfalse(ra)) + ci->u.l.savedpc++; + else + donextjump(ci); + ) + vmcase(OP_TESTSET, + TValue *rb = RB(i); + if (GETARG_C(i) ? l_isfalse(rb) : !l_isfalse(rb)) + ci->u.l.savedpc++; + else { + setobjs2s(L, ra, rb); + donextjump(ci); + } + ) + vmcase(OP_CALL, + int b = GETARG_B(i); + int nresults = GETARG_C(i) - 1; + if (b != 0) L->top = ra+b; /* else previous instruction set top */ + if (luaD_precall(L, ra, nresults)) { /* C function? */ + if (nresults >= 0) L->top = ci->top; /* adjust results */ + base = ci->u.l.base; + } + else { /* Lua function */ + ci = L->ci; + ci->callstatus |= CIST_REENTRY; + goto newframe; /* restart luaV_execute over new Lua function */ + } + ) + vmcase(OP_TAILCALL, + int b = GETARG_B(i); + if (b != 0) L->top = ra+b; /* else previous instruction set top */ + lua_assert(GETARG_C(i) - 1 == LUA_MULTRET); + if (luaD_precall(L, ra, LUA_MULTRET)) /* C function? */ + base = ci->u.l.base; + else { + /* tail call: put called frame (n) in place of caller one (o) */ + CallInfo *nci = L->ci; /* called frame */ + CallInfo *oci = nci->previous; /* caller frame */ + StkId nfunc = nci->func; /* called function */ + StkId ofunc = oci->func; /* caller function */ + /* last stack slot filled by 'precall' */ + StkId lim = nci->u.l.base + getproto(nfunc)->numparams; + int aux; + /* close all upvalues from previous call */ + if (cl->p->sizep > 0) luaF_close(L, oci->u.l.base); + /* move new frame into old one */ + for (aux = 0; nfunc + aux < lim; aux++) + setobjs2s(L, ofunc + aux, nfunc + aux); + oci->u.l.base = ofunc + (nci->u.l.base - nfunc); /* correct base */ + oci->top = L->top = ofunc + (L->top - nfunc); /* correct top */ + oci->u.l.savedpc = nci->u.l.savedpc; + oci->callstatus |= CIST_TAIL; /* function was tail called */ + ci = L->ci = oci; /* remove new frame */ + lua_assert(L->top == oci->u.l.base + getproto(ofunc)->maxstacksize); + goto newframe; /* restart luaV_execute over new Lua function */ + } + ) + vmcasenb(OP_RETURN, + int b = GETARG_B(i); + if (b != 0) L->top = ra+b-1; + if (cl->p->sizep > 0) luaF_close(L, base); + b = luaD_poscall(L, ra); + if (!(ci->callstatus & CIST_REENTRY)) /* 'ci' still the called one */ + return; /* external invocation: return */ + else { /* invocation via reentry: continue execution */ + ci = L->ci; + if (b) L->top = ci->top; + lua_assert(isLua(ci)); + lua_assert(GET_OPCODE(*((ci)->u.l.savedpc - 1)) == OP_CALL); + goto newframe; /* restart luaV_execute over new Lua function */ + } + ) + vmcase(OP_FORLOOP, + lua_Number step = nvalue(ra+2); + lua_Number idx = luai_numadd(L, nvalue(ra), step); /* increment index */ + lua_Number limit = nvalue(ra+1); + if (luai_numlt(L, 0, step) ? luai_numle(L, idx, limit) + : luai_numle(L, limit, idx)) { + ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ + setnvalue(ra, idx); /* update internal index... */ + setnvalue(ra+3, idx); /* ...and external index */ + } + ) + vmcase(OP_FORPREP, + const TValue *init = ra; + const TValue *plimit = ra+1; + const TValue *pstep = ra+2; + if (!tonumber(init, ra)) + luaG_runerror(L, LUA_QL("for") " initial value must be a number"); + else if (!tonumber(plimit, ra+1)) + luaG_runerror(L, LUA_QL("for") " limit must be a number"); + else if (!tonumber(pstep, ra+2)) + luaG_runerror(L, LUA_QL("for") " step must be a number"); + setnvalue(ra, luai_numsub(L, nvalue(ra), nvalue(pstep))); + ci->u.l.savedpc += GETARG_sBx(i); + ) + vmcasenb(OP_TFORCALL, + StkId cb = ra + 3; /* call base */ + setobjs2s(L, cb+2, ra+2); + setobjs2s(L, cb+1, ra+1); + setobjs2s(L, cb, ra); + L->top = cb + 3; /* func. + 2 args (state and index) */ + Protect(luaD_call(L, cb, GETARG_C(i), 1)); + L->top = ci->top; + i = *(ci->u.l.savedpc++); /* go to next instruction */ + ra = RA(i); + lua_assert(GET_OPCODE(i) == OP_TFORLOOP); + goto l_tforloop; + ) + vmcase(OP_TFORLOOP, + l_tforloop: + if (!ttisnil(ra + 1)) { /* continue loop? */ + setobjs2s(L, ra, ra + 1); /* save control variable */ + ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ + } + ) + vmcase(OP_SETLIST, + int n = GETARG_B(i); + int c = GETARG_C(i); + int last; + Table *h; + if (n == 0) n = cast_int(L->top - ra) - 1; + if (c == 0) { + lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG); + c = GETARG_Ax(*ci->u.l.savedpc++); + } + luai_runtimecheck(L, ttistable(ra)); + h = hvalue(ra); + last = ((c-1)*LFIELDS_PER_FLUSH) + n; + if (last > h->sizearray) /* needs more space? */ + luaH_resizearray(L, h, last); /* pre-allocate it at once */ + for (; n > 0; n--) { + TValue *val = ra+n; + luaH_setint(L, h, last--, val); + luaC_barrierback(L, obj2gco(h), val); + } + L->top = ci->top; /* correct top (in case of previous open call) */ + ) + vmcase(OP_CLOSURE, + Proto *p = cl->p->p[GETARG_Bx(i)]; + Closure *ncl = getcached(p, cl->upvals, base); /* cached closure */ + if (ncl == NULL) /* no match? */ + pushclosure(L, p, cl->upvals, base, ra); /* create a new one */ + else + setclLvalue(L, ra, ncl); /* push cashed closure */ + checkGC(L, ra + 1); + ) + vmcase(OP_VARARG, + int b = GETARG_B(i) - 1; + int j; + int n = cast_int(base - ci->func) - cl->p->numparams - 1; + if (b < 0) { /* B == 0? */ + b = n; /* get all var. arguments */ + Protect(luaD_checkstack(L, n)); + ra = RA(i); /* previous call may change the stack */ + L->top = ra + n; + } + for (j = 0; j < b; j++) { + if (j < n) { + setobjs2s(L, ra + j, base - n + j); + } + else { + setnilvalue(ra + j); + } + } + ) + vmcase(OP_EXTRAARG, + lua_assert(0); + ) + } + } +} + diff --git a/luprex/ext/eris-master/src/lvm.h b/luprex/ext/eris-master/src/lvm.h new file mode 100644 index 00000000..5380270d --- /dev/null +++ b/luprex/ext/eris-master/src/lvm.h @@ -0,0 +1,44 @@ +/* +** $Id: lvm.h,v 2.18.1.1 2013/04/12 18:48:47 roberto Exp $ +** Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#ifndef lvm_h +#define lvm_h + + +#include "ldo.h" +#include "lobject.h" +#include "ltm.h" + + +#define tostring(L,o) (ttisstring(o) || (luaV_tostring(L, o))) + +#define tonumber(o,n) (ttisnumber(o) || (((o) = luaV_tonumber(o,n)) != NULL)) + +#define equalobj(L,o1,o2) (ttisequal(o1, o2) && luaV_equalobj_(L, o1, o2)) + +#define luaV_rawequalobj(o1,o2) equalobj(NULL,o1,o2) + + +/* not to called directly */ +LUAI_FUNC int luaV_equalobj_ (lua_State *L, const TValue *t1, const TValue *t2); + + +LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); +LUAI_FUNC int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r); +LUAI_FUNC const TValue *luaV_tonumber (const TValue *obj, TValue *n); +LUAI_FUNC int luaV_tostring (lua_State *L, StkId obj); +LUAI_FUNC void luaV_gettable (lua_State *L, const TValue *t, TValue *key, + StkId val); +LUAI_FUNC void luaV_settable (lua_State *L, const TValue *t, TValue *key, + StkId val); +LUAI_FUNC void luaV_finishOp (lua_State *L); +LUAI_FUNC void luaV_execute (lua_State *L); +LUAI_FUNC void luaV_concat (lua_State *L, int total); +LUAI_FUNC void luaV_arith (lua_State *L, StkId ra, const TValue *rb, + const TValue *rc, TMS op); +LUAI_FUNC void luaV_objlen (lua_State *L, StkId ra, const TValue *rb); + +#endif diff --git a/luprex/ext/eris-master/src/lzio.c b/luprex/ext/eris-master/src/lzio.c new file mode 100644 index 00000000..20efea98 --- /dev/null +++ b/luprex/ext/eris-master/src/lzio.c @@ -0,0 +1,76 @@ +/* +** $Id: lzio.c,v 1.35.1.1 2013/04/12 18:48:47 roberto Exp $ +** Buffered streams +** See Copyright Notice in lua.h +*/ + + +#include + +#define lzio_c +#define LUA_CORE + +#include "lua.h" + +#include "llimits.h" +#include "lmem.h" +#include "lstate.h" +#include "lzio.h" + + +int luaZ_fill (ZIO *z) { + size_t size; + lua_State *L = z->L; + const char *buff; + lua_unlock(L); + buff = z->reader(L, z->data, &size); + lua_lock(L); + if (buff == NULL || size == 0) + return EOZ; + z->n = size - 1; /* discount char being returned */ + z->p = buff; + return cast_uchar(*(z->p++)); +} + + +void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data) { + z->L = L; + z->reader = reader; + z->data = data; + z->n = 0; + z->p = NULL; +} + + +/* --------------------------------------------------------------- read --- */ +size_t luaZ_read (ZIO *z, void *b, size_t n) { + while (n) { + size_t m; + if (z->n == 0) { /* no bytes in buffer? */ + if (luaZ_fill(z) == EOZ) /* try to read more */ + return n; /* no more input; return number of missing bytes */ + else { + z->n++; /* luaZ_fill consumed first byte; put it back */ + z->p--; + } + } + m = (n <= z->n) ? n : z->n; /* min. between n and z->n */ + memcpy(b, z->p, m); + z->n -= m; + z->p += m; + b = (char *)b + m; + n -= m; + } + return 0; +} + +/* ------------------------------------------------------------------------ */ +char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n) { + if (n > buff->buffsize) { + if (n < LUA_MINBUFFER) n = LUA_MINBUFFER; + luaZ_resizebuffer(L, buff, n); + } + return buff->buffer; +} + + diff --git a/luprex/ext/eris-master/src/lzio.h b/luprex/ext/eris-master/src/lzio.h new file mode 100644 index 00000000..441f7479 --- /dev/null +++ b/luprex/ext/eris-master/src/lzio.h @@ -0,0 +1,65 @@ +/* +** $Id: lzio.h,v 1.26.1.1 2013/04/12 18:48:47 roberto Exp $ +** Buffered streams +** See Copyright Notice in lua.h +*/ + + +#ifndef lzio_h +#define lzio_h + +#include "lua.h" + +#include "lmem.h" + + +#define EOZ (-1) /* end of stream */ + +typedef struct Zio ZIO; + +#define zgetc(z) (((z)->n--)>0 ? cast_uchar(*(z)->p++) : luaZ_fill(z)) + + +typedef struct Mbuffer { + char *buffer; + size_t n; + size_t buffsize; +} Mbuffer; + +#define luaZ_initbuffer(L, buff) ((buff)->buffer = NULL, (buff)->buffsize = 0) + +#define luaZ_buffer(buff) ((buff)->buffer) +#define luaZ_sizebuffer(buff) ((buff)->buffsize) +#define luaZ_bufflen(buff) ((buff)->n) + +#define luaZ_resetbuffer(buff) ((buff)->n = 0) + + +#define luaZ_resizebuffer(L, buff, size) \ + (luaM_reallocvector(L, (buff)->buffer, (buff)->buffsize, size, char), \ + (buff)->buffsize = size) + +#define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) + + +LUAI_FUNC char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n); +LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, + void *data); +LUAI_FUNC size_t luaZ_read (ZIO* z, void* b, size_t n); /* read next n bytes */ + + + +/* --------- Private Part ------------------ */ + +struct Zio { + size_t n; /* bytes still unread */ + const char *p; /* current position in buffer */ + lua_Reader reader; /* reader function */ + void* data; /* additional data */ + lua_State *L; /* Lua state (for reader) */ +}; + + +LUAI_FUNC int luaZ_fill (ZIO *z); + +#endif diff --git a/luprex/ext/eris-master/test/persist.c b/luprex/ext/eris-master/test/persist.c new file mode 100644 index 00000000..4f52de43 --- /dev/null +++ b/luprex/ext/eris-master/test/persist.c @@ -0,0 +1,99 @@ +#include +#include + +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + +static int LUAF_createludata(lua_State *L) +{ + lua_pushlightuserdata(L, (void*)321); + return 1; +} + +/* A userdata that may be literally persisted */ +static int LUAF_boxinteger(lua_State *L) +{ + /* num */ + int* ptr = (int*)lua_newuserdata(L, sizeof(int)); + /* num udata */ + *ptr = luaL_checkint(L, 1); + lua_newtable(L); + /* num udata mt */ + lua_pushstring(L, "__persist"); + /* num udata mt "__persist" */ + lua_pushboolean(L, 1); + /* num udata mt "__persist" true */ + lua_rawset(L, 3); + /* num udata mt */ + lua_setmetatable(L, 2); + /* num udata */ + return 1; +} + +static int LUAF_boxboolean(lua_State *L) +{ + /* bool */ + char* ptr = (char*)lua_newuserdata(L, sizeof(char)); + /* bool udata */ + *ptr = (char)lua_toboolean(L, 1); + lua_newtable(L); + /* num udata mt */ + lua_pushstring(L, "__persist"); + /* num udata mt "__persist" */ + lua_getglobal(L, "booleanpersist"); + /* num udata mt "__persist" booleanpersist */ + lua_rawset(L, 3); + /* num udata mt */ + lua_setmetatable(L, 2); + /* num udata */ + return 1; +} + +static int LUAF_unboxboolean(lua_State *L) +{ + /* udata */ + lua_pushboolean(L, *(char*)lua_touserdata(L, 1)); + /* udata bool */ + return 1; +} + +static int LUAF_onerror(lua_State *L) +{ + + const char* str = 0; + if(lua_gettop(L) != 0) + { + str = lua_tostring(L, -1); + printf("%s\n",str); + } + return 0; +} + +int main(int argc, char** argv) +{ + if (argc < 2) { + printf("Usage: persist