diff options
author | IronClawTrem <louie.nutman@gmail.com> | 2020-02-16 03:40:06 +0000 |
---|---|---|
committer | IronClawTrem <louie.nutman@gmail.com> | 2020-02-16 03:40:06 +0000 |
commit | 425decdf7e9284d15aa726e3ae96b9942fb0e3ea (patch) | |
tree | 6c0dd7edfefff1be7b9e75fe0b3a0a85fe1595f3 /src/script | |
parent | ccb0b2e4d6674a7a00c9bf491f08fc73b6898c54 (diff) |
create tremded branch
Diffstat (limited to 'src/script')
-rw-r--r-- | src/script/CMakeLists.txt | 35 | ||||
-rw-r--r-- | src/script/bind.h | 107 | ||||
-rw-r--r-- | src/script/client.h | 54 | ||||
-rw-r--r-- | src/script/cmd.h | 50 | ||||
-rw-r--r-- | src/script/cvar.h | 146 | ||||
-rw-r--r-- | src/script/http_client.h | 65 | ||||
-rw-r--r-- | src/script/lnettlelib.c | 94 | ||||
-rw-r--r-- | src/script/lnettlelib.h | 15 | ||||
-rw-r--r-- | src/script/nettle.h | 41 | ||||
-rw-r--r-- | src/script/rapidjson.h | 52 | ||||
-rw-r--r-- | src/script/rapidjson/LICENSE | 19 | ||||
-rw-r--r-- | src/script/rapidjson/document.cpp | 194 | ||||
-rw-r--r-- | src/script/rapidjson/file.hpp | 21 | ||||
-rw-r--r-- | src/script/rapidjson/luax.hpp | 82 | ||||
-rw-r--r-- | src/script/rapidjson/rapidjson.cpp | 414 | ||||
-rw-r--r-- | src/script/rapidjson/schema.cpp | 116 | ||||
-rw-r--r-- | src/script/rapidjson/userdata.hpp | 112 | ||||
-rw-r--r-- | src/script/rapidjson/values.cpp | 108 | ||||
-rw-r--r-- | src/script/rapidjson/values.hpp | 245 |
19 files changed, 1970 insertions, 0 deletions
diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt new file mode 100644 index 0000000..d7741f7 --- /dev/null +++ b/src/script/CMakeLists.txt @@ -0,0 +1,35 @@ +add_library( + script_api STATIC + client.h + cvar.h + http_client.h + lnettlelib.c + lnettlelib.h + lnettlelib.h + nettle.h + rapidjson.h + rapidjson/document.cpp + rapidjson/file.hpp + rapidjson/luax.hpp + rapidjson/rapidjson.cpp + rapidjson/schema.cpp + rapidjson/userdata.hpp + rapidjson/values.cpp + rapidjson/values.hpp + ) + + +include_directories( + # these are in global namespace + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/lua-5.3.3/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/sol + ${CMAKE_CURRENT_SOURCE_DIR}/../common + # these have they're own namespace + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/nettle-3.3 + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/rapidjson + ) + +target_link_libraries( + script_api + lua + ) diff --git a/src/script/bind.h b/src/script/bind.h new file mode 100644 index 0000000..a1e93e9 --- /dev/null +++ b/src/script/bind.h @@ -0,0 +1,107 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org> +// Copyright (C) 2015-2019 GrangerHub +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see <http://www.gnu.org/licenses/>. +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_KEY_H +#define SCRIPT_KEY_H + +#include "client/client.h" +#include "client/keys.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + class InvalidKey : public std::exception { + public: + virtual const char* what() const throw() + { return "Invalid Key"; } + }; + + struct Bind { + private: + qkey_t *my = nullptr; + int keynum = 0; + + Bind() = delete; + + public: + Bind(qkey_t* key) + : my(key) + { } + + Bind(std::string key) + { + keynum = Key_StringToKeynum(key.c_str()); + if ( keynum < 0 ) throw InvalidKey(); + my = &keys[ keynum ]; + } + + Bind(std::string key, std::string cmd) + { + keynum = Key_StringToKeynum(key.c_str()); + if ( keynum < 0 ) throw InvalidKey(); + my = &keys[ keynum ]; + Key_SetBinding(keynum, cmd.c_str()); + } + + Bind(std::string key, std::string cmd, bool overwrite) + { + keynum = Key_StringToKeynum(key.c_str()); + if ( keynum < 0 ) throw InvalidKey(); + my = &keys[ keynum ]; + if ( !my->binding || overwrite ) + Key_SetBinding(keynum, cmd.c_str()); + } + + std::string get_value() + { return my->binding; } + + void set_value(std::string cmd) { + if ( my->binding ) Z_Free( my->binding ); + my->binding = CopyString(cmd.c_str()); + // consider this like modifying an archived cvar, so the file + // write will be triggered at the next oportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } + + }; + + namespace keybind + { + static inline void init(sol::state&& lua) + { + lua.new_usertype<Bind>( + "bind", sol::constructors<sol::types<std::string>, + sol::types<std::string, std::string>, + sol::types<std::string, std::string, bool>>(), + "value", sol::property(&Bind::get_value, &Bind::set_value) + //"key", sol::property(&Bind::get_key) + ); + } + }; +}; + +#endif diff --git a/src/script/client.h b/src/script/client.h new file mode 100644 index 0000000..ee90771 --- /dev/null +++ b/src/script/client.h @@ -0,0 +1,54 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org> +// Copyright (C) 2015-2019 GrangerHub +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see <http://www.gnu.org/licenses/>. +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_CLIENT_H +#define SCRIPT_CLIENT_H + +#include <iostream> + +#include "client/client.h" +#include "client/keys.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + struct Client {}; + + namespace client + { + static inline void init(sol::state&& lua) + { + lua.new_usertype<Client>( "client", + "addReliableCommand", &CL_AddReliableCommand + //"disconnect", &CL_Disconnect_f, + //"reconnect", &CL_Reconnect_f, + ); + } + }; +}; + +#endif diff --git a/src/script/cmd.h b/src/script/cmd.h new file mode 100644 index 0000000..718658a --- /dev/null +++ b/src/script/cmd.h @@ -0,0 +1,50 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org> +// Copyright (C) 2015-2019 GrangerHub +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see <http://www.gnu.org/licenses/>. +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_CMD_H +#define SCRIPT_CMD_H + +#include "qcommon/cmd.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + struct Cmd { }; + + namespace cmd + { + static inline void init(sol::state&& lua) + { + lua.new_usertype<Cmd>( + "cmd", + "execute", &Cmd_ExecuteString + ); + } + }; +}; + +#endif diff --git a/src/script/cvar.h b/src/script/cvar.h new file mode 100644 index 0000000..a5eb90c --- /dev/null +++ b/src/script/cvar.h @@ -0,0 +1,146 @@ +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org> +// Copyright (C) 2015-2019 GrangerHub +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see <http://www.gnu.org/licenses/>. + +// It's quite possible this is _way over the top_ but I've been toying +// with several ideas. -Victor + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_CVAR_H +#define SCRIPT_CVAR_H + +#include <iostream> +#include <exception> + +#include "qcommon/cvar.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + ////////////////////////////////////////////// Exceptions //////////// + class CvarInvalidName : public std::exception { + public: + virtual const char* what() const throw() + { return "cvar name is invalid"; } + }; + + class CvarNotFound : public std::exception { + public: + virtual const char* what() const throw() + { return "cvar does not exist"; } + }; + + class CvarReadOnly : public std::exception { + public: + virtual const char *what() const throw() + { return "cvar is readonly"; } + }; + + class CvarWriteProtected : public std::exception { + public: + virtual const char *what() const throw() + { return "cvar is write protected"; } + }; + + class CvarCheatProtected : public std::exception { + public: + virtual const char *what() const throw() + { return "cvar is cheat protected"; } + }; + + class CvarLatchedUnsupported : public std::exception { + public: + virtual const char *what() const throw() + { return "modifying latched cvars is unsupported"; } + }; + + extern "C" cvar_t *cvar_cheats; + ////////////////////////////////////////////// class Cvar //////////// + struct Cvar { + private: + cvar_s *my = nullptr; + + Cvar() = delete; + + public: + Cvar(cvar_s* var) + : my(var) + { } + + Cvar(std::string name) + { my = Cvar_Get(name.c_str(), "", 0); } + + Cvar(std::string name, std::string value) + { my = Cvar_Get(name.c_str(), value.c_str(), CVAR_ARCHIVE); } + + Cvar(std::string name, std::string value, int flags) + { my = Cvar_Get(name.c_str(), value.c_str(), flags); } + + void set_value(const char*value) + { + if ( !my ) + throw CvarNotFound(); + else if ( my->flags & CVAR_ROM ) + throw CvarReadOnly(); + else if ( my->flags & CVAR_INIT ) + throw CvarWriteProtected(); + else if ( my->flags & CVAR_CHEAT && !cvar_cheats->integer ) + throw CvarCheatProtected(); + else if ( (my->flags & CVAR_LATCH) ) + throw CvarLatchedUnsupported(); + else if ( !value ) + value = my->resetString; + value = Cvar_Validate(my, value, false); + + cvar_modifiedFlags |= my->flags; + + my->modified = true; + my->modificationCount++; + my->string = CopyString(value); + my->value = atof(my->string); + my->integer = atoi(my->string); + } + + std::string get_value() + { return my->string; } + + std::string get_key() + { return my->name; } + }; + + namespace cvar { + static inline void init(sol::state&& lua) + { + lua.new_usertype<Cvar>( + "cvar", sol::constructors<sol::types<std::string>, + sol::types<std::string, std::string>, + sol::types<std::string, std::string, int>>(), + "value", sol::property(&Cvar::get_value, &Cvar::set_value), + "key", sol::property(&Cvar::get_key) + ); + } + }; +}; +#endif diff --git a/src/script/http_client.h b/src/script/http_client.h new file mode 100644 index 0000000..2556090 --- /dev/null +++ b/src/script/http_client.h @@ -0,0 +1,65 @@ +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org> +// Copyright (C) 2015-2019 GrangerHub +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see <http://www.gnu.org/licenses/>. + +// It's quite possible this is _way over the top_ but I've been toying +// with several ideas. -Victor + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_RESTCLIENT_H +#define SCRIPT_RESTCLIENT_H + +#include <iostream> +#include "restclient/restclient.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + // No Exceptions at this time + + struct HttpClient + { }; + + namespace http_client + { + //using namespace RestClient; + static inline void init(sol::state&& lua) + { + + lua.new_usertype<RestClient::Response>( "HttpResponse", + "code", &RestClient::Response::code, + "body", &RestClient::Response::body + //"headers", &RestClient::Response::headers + ); + + lua.new_usertype<HttpClient>( "http", + "get", &RestClient::get, + "post", &RestClient::post, + "put", &RestClient::put, + "delete", &RestClient::del + ); + } + }; +}; + +#endif diff --git a/src/script/lnettlelib.c b/src/script/lnettlelib.c new file mode 100644 index 0000000..f1aedfa --- /dev/null +++ b/src/script/lnettlelib.c @@ -0,0 +1,94 @@ +#include <string.h> + +#include "lauxlib.h" +#include "lua.h" +#include "nettle/sha2.h" + +//#include "../qcommon/q3_lauxlib.h" FIXME? This doesn't seem to be hooked correctly into tremded.exe + +#define SHA256_CTX "sha256_ctx*" + +static int lsha256(lua_State *L) +{ + struct sha256_ctx *ctx; + + ctx = (struct sha256_ctx *)lua_newuserdata(L, sizeof(struct sha256_ctx)); + luaL_setmetatable(L, SHA256_CTX); + sha256_init(ctx); + return 1; +} + +static int lsha256_digest(lua_State *L) +{ + struct sha256_ctx *ctx; + char digest[SHA256_DIGEST_SIZE]; + + ctx = luaL_checkudata(L, 1, SHA256_CTX); + sha256_digest(ctx, sizeof(digest), digest); + lua_pushlstring(L, digest, sizeof(digest)); + return 1; +} + +static int lsha256_update(lua_State *L) +{ + struct sha256_ctx *ctx; + const char *data; + size_t len; + + ctx = luaL_checkudata(L, 1, SHA256_CTX); + if (lua_isnil(L, 2)) { + return 0; + } + data = luaL_tolstring(L, 2, &len); + nettle_sha256_update(ctx, len, data); + return 0; +} + +static int lsha256_tostring(lua_State *L) +{ + struct sha256_ctx *ctx, ctx2; + char digest[SHA256_DIGEST_SIZE]; + char hex[SHA256_DIGEST_SIZE*2+1]; + int i; + + ctx = luaL_checkudata(L, 1, SHA256_CTX); + memcpy(&ctx2, ctx, sizeof(struct sha256_ctx)); + + sha256_digest(&ctx2, sizeof(digest), digest); + for (i = 0; i < sizeof(digest); i++) { + sprintf(hex + 2 * i, "%02hhx", digest[i]); + } + + lua_pushstring(L, hex); + return 1; +} + +/* functions for 'nettle' library */ +static const luaL_Reg nettlelib[] = { + {"sha256", lsha256}, + {NULL, NULL} +}; + +/* functions for sha256 objects */ +static const luaL_Reg lsha256_methods[] = { + {"digest", lsha256_digest}, + {"update", lsha256_update}, + {"__tostring", lsha256_tostring}, + {NULL, NULL} +}; + +static void createmeta (lua_State *L) +{ + luaL_newmetatable(L, SHA256_CTX); /* create metatable for file handles */ + lua_pushvalue(L, -1); /* push metatable */ + lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */ + luaL_setfuncs(L, lsha256_methods, 0); /* add file methods to new metatable */ + lua_pop(L, 1); /* pop new metatable */ +} + +LUAMOD_API int luaopen_nettle (lua_State *L) +{ + luaL_newlib(L, nettlelib); + createmeta(L); + return 1; +} diff --git a/src/script/lnettlelib.h b/src/script/lnettlelib.h new file mode 100644 index 0000000..c9609f9 --- /dev/null +++ b/src/script/lnettlelib.h @@ -0,0 +1,15 @@ +#ifndef lnettlelib_h +#define lnettlelib_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define LUA_NETTLELIBNAME "nettle" +LUAMOD_API int (luaopen_nettle) (lua_State *L); + +#ifdef __cplusplus +} +#endif + +#endif /* lnettlelib_h */ diff --git a/src/script/nettle.h b/src/script/nettle.h new file mode 100644 index 0000000..87e8c26 --- /dev/null +++ b/src/script/nettle.h @@ -0,0 +1,41 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org> +// Copyright (C) 2015-2019 GrangerHub +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see <http://www.gnu.org/licenses/>. +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_NETTLE_H +#define SCRIPT_NETTLE_H + +#include <iostream> + +#include "lua.hpp" +#include "sol.hpp" + +#include "lnettlelib.h" + +namespace script { + namespace nettle { + static inline void init(sol::state&& lua) + { lua.require("nettle", luaopen_nettle, 1); } + }; +}; + +#endif diff --git a/src/script/rapidjson.h b/src/script/rapidjson.h new file mode 100644 index 0000000..0603bb7 --- /dev/null +++ b/src/script/rapidjson.h @@ -0,0 +1,52 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org> +// Copyright (C) 2015-2019 GrangerHub +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see <http://www.gnu.org/licenses/>. +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_RAPIDJSON_H +#define SCRIPT_RAPIDJSON_H + +#include <iostream> + +#include "lua.hpp" +#include "sol.hpp" + +namespace sol +{ + class state; +}; + +extern "C" int luaopen_rapidjson(lua_State* L); + +namespace script +{ + // No Exceptions at this time + + namespace rapidjson + { + static inline void init(sol::state&& lua) + { + lua.require("rapidjson", luaopen_rapidjson, 1); + } + }; +}; + +#endif diff --git a/src/script/rapidjson/LICENSE b/src/script/rapidjson/LICENSE new file mode 100644 index 0000000..5a331f8 --- /dev/null +++ b/src/script/rapidjson/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2015 Xpol Wan + +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/src/script/rapidjson/document.cpp b/src/script/rapidjson/document.cpp new file mode 100644 index 0000000..2487eb6 --- /dev/null +++ b/src/script/rapidjson/document.cpp @@ -0,0 +1,194 @@ + +#include <cstdio> +#include <vector> +#include <fstream> + +#include "userdata.hpp" +#include "values.hpp" +#include "file.hpp" + +#include "lua.hpp" +#include "rapidjson.h" + +using namespace rapidjson; + +template<> +const char* const Userdata<Document>::metatable = "rapidjson.Document"; + +template<> +Document* Userdata<Document>::construct(lua_State * L) +{ + auto t = lua_type(L, 1); + if (t != LUA_TNONE && t != LUA_TSTRING && t != LUA_TTABLE) + { + luax::typerror(L, 1, "none, string or table"); + return nullptr; + } + + auto doc = new Document(); + if (t == LUA_TSTRING) + { + size_t len; + const char* s = luaL_checklstring(L, 1, &len); + doc->Parse(s, len); + } + else if (t == LUA_TTABLE) + { + values::toDocument(L, 1, doc); + } + return doc; +} + +static int pushParseResult(lua_State* L, Document* doc) +{ + auto err = doc->GetParseError(); + if (err != kParseErrorNone) + { + lua_pushnil(L); + lua_pushfstring(L, "%s (at Offset %d)", GetParseError_En(err), doc->GetErrorOffset()); + return 2; + } + + lua_pushboolean(L, 1); + return 1; +} + +static int Document_parse(lua_State* L) +{ + Document* doc = Userdata<Document>::get(L, 1); + + size_t l = 0; + auto s = luaL_checklstring(L, 2, &l); + doc->Parse(s, l); + + return pushParseResult(L, doc); +} + +static int Document_parseFile(lua_State* L) +{ + auto doc = Userdata<Document>::get(L, 1); + + auto s = luaL_checkstring(L, 2); + std::ifstream ifs(s); + IStreamWrapper isw(ifs); + + doc->ParseStream(isw); + + return pushParseResult(L, doc);; +} + +/** + * doc:get('path'[, default]) + */ +static int Document_get(lua_State* L) +{ + auto doc = Userdata<Document>::check(L, 1); + auto s = luaL_checkstring(L, 2); + Pointer ptr(s); + auto v = ptr.Get(*doc); + + if (!v) + { + if (lua_gettop(L) >= 3) + { + lua_pushvalue(L, 3); + } + else + { + lua_pushnil(L); + } + } + else + { + values::push(L, *v); + } + return 1; +} + +static int Document_set(lua_State* L) +{ + auto doc = Userdata<Document>::check(L, 1); + Pointer ptr(luaL_checkstring(L, 2)); + auto v = values::toValue(L, 3, doc->GetAllocator()); + + ptr.Set(*doc, v, doc->GetAllocator()); + + return 0; +} + +/** + * local jsonstr = doc:stringify( + {pretty=false}) + */ +static int Document_stringify(lua_State* L) +{ + auto doc = Userdata<Document>::check(L, 1); + + auto pretty = luax::optboolfield(L, 2, "pretty", false); + + StringBuffer sb; + if (pretty) + { + PrettyWriter<StringBuffer> writer(sb); + doc->Accept(writer); + } + else + { + Writer<StringBuffer> writer(sb); + doc->Accept(writer); + } + lua_pushlstring(L, sb.GetString(), sb.GetSize()); + + return 1; +} + +/** + * doc:save(filename, + {pretty=false, sort_keys=false}) + */ +static int Document_save(lua_State* L) +{ + auto doc = Userdata<Document>::check(L, 1); + auto filename = luaL_checkstring(L, 2); + auto pretty = luax::optboolfield(L, 3, "pretty", false); + + auto fp = file::open(filename, "wb"); + char buffer[512]; + FileWriteStream fs(fp, buffer, sizeof(buffer)); + + if (pretty) + { + PrettyWriter<FileWriteStream> writer(fs); + doc->Accept(writer); + } + else + { + Writer<FileWriteStream> writer(fs); + doc->Accept(writer); + } + fclose(fp); + + return 0; +} + +template <> +const luaL_Reg* Userdata<Document>::methods() +{ + static const luaL_Reg reg[] = + { + { "parse", Document_parse }, + { "parseFile", Document_parseFile }, + + { "__gc", metamethod_gc }, + { "__tostring", metamethod_tostring }, + + { "get", Document_get }, + { "set", Document_set }, + + { "stringify", Document_stringify }, + { "save", Document_save }, + + { nullptr, nullptr } + }; + return reg; +} diff --git a/src/script/rapidjson/file.hpp b/src/script/rapidjson/file.hpp new file mode 100644 index 0000000..5e098d4 --- /dev/null +++ b/src/script/rapidjson/file.hpp @@ -0,0 +1,21 @@ +#ifndef __LUA_RAPIDJSION_FILE_HPP__ +#define __LUA_RAPIDJSION_FILE_HPP__ + +#include <cstdio> + +namespace file +{ + inline FILE* open(const char* filename, const char* mode) + { +#if WIN32 + FILE* fp = nullptr; + fopen_s(&fp, filename, mode); + return fp; +#else + return fopen(filename, mode); +#endif + } +} + +#endif + diff --git a/src/script/rapidjson/luax.hpp b/src/script/rapidjson/luax.hpp new file mode 100644 index 0000000..e8ea7c6 --- /dev/null +++ b/src/script/rapidjson/luax.hpp @@ -0,0 +1,82 @@ +#ifndef __LUA_RAPIDJSION_LUACOMPAT_H__ +#define __LUA_RAPIDJSION_LUACOMPAT_H__ + +#include <cmath> +#include <limits> +#include <cstdint> + +#include "qcommon/q3_lauxlib.h" + +#include "lua.hpp" + +namespace luax +{ + inline void setfuncs(lua_State* L, const luaL_Reg* funcs) + { luaL_setfuncs(L, funcs, 0); } + + inline size_t rawlen(lua_State* L, int idx) + { return lua_rawlen(L, idx); } + + inline bool isinteger(lua_State* L, int idx, int64_t* out = nullptr) + { + if (lua_isinteger(L, idx)) // but it maybe not detect all integers. + { + if (out) + *out = lua_tointeger(L, idx); + return true; + } + + double intpart; + if (std::modf(lua_tonumber(L, idx), &intpart) == 0.0) + { + if (std::numeric_limits<lua_Integer>::min() <= intpart + && intpart <= std::numeric_limits<lua_Integer>::max()) + { + if (out) + *out = static_cast<int64_t>(intpart); + return true; + } + } + return false; + } + + inline int typerror(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); + } + + inline bool optboolfield(lua_State* L, int idx, const char* name, bool def) + { + auto v = def; + auto t = lua_type(L, idx); + + if (t != LUA_TTABLE && t != LUA_TNONE) + luax::typerror(L, idx, "table"); + + if (t != LUA_TNONE) + { + lua_getfield(L, idx, name); + if (!lua_isnoneornil(L, -1)) + v = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + } + + return v; + } + + inline int optintfield(lua_State* L, int idx, const char* name, int def) + { + auto v = def; + lua_getfield(L, idx, name); + + if (lua_isnumber(L, -1)) + v = static_cast<int>(lua_tointeger(L, -1)); + + lua_pop(L, 1); + return v; + } + +} + +#endif diff --git a/src/script/rapidjson/rapidjson.cpp b/src/script/rapidjson/rapidjson.cpp new file mode 100644 index 0000000..9e87812 --- /dev/null +++ b/src/script/rapidjson/rapidjson.cpp @@ -0,0 +1,414 @@ + +#include <limits> +#include <cstdio> +#include <vector> +#include <algorithm> + +#include "userdata.hpp" +#include "values.hpp" +#include "luax.hpp" +#include "file.hpp" + +#include "lua.hpp" +#include "rapidjson.h" + +using namespace rapidjson; + +#ifndef LUA_RAPIDJSON_VERSION +#define LUA_RAPIDJSON_VERSION "1.0.0" +#endif + +static void createSharedMeta(lua_State* L, const char* meta, const char* type) +{ + luaL_newmetatable(L, meta); + lua_pushstring(L, type); + lua_setfield(L, -2, "__jsontype"); + lua_pop(L, 1); +} + +static int makeTableType(lua_State* L, int idx, const char* meta, const char* type) +{ + auto isnoarg = lua_isnoneornil(L, idx); + auto istable = lua_istable(L, idx); + if (!isnoarg && !istable) + return luaL_argerror(L, idx, "optional table excepted"); + + if (isnoarg) + lua_createtable(L, 0, 0); + else // is table. + { + lua_pushvalue(L, idx); + if (lua_getmetatable(L, -1)) + { + // already have a metatable, just set the __jsontype field. + + lua_pushstring(L, type); + lua_setfield(L, -2, "__jsontype"); + lua_pop(L, 1); + return 1; + } + // else fall through + } + + // Now we have a table without meta table + luaL_getmetatable(L, meta); + lua_setmetatable(L, -2); + return 1; +} + +static int json_object(lua_State* L) +{ + return makeTableType(L, 1, "json.object", "object"); +} + +static int json_array(lua_State* L) +{ + return makeTableType(L, 1, "json.array", "array"); +} + +template<typename Stream> +int decode(lua_State* L, Stream* s) +{ + auto top = lua_gettop(L); + values::ToLuaHandler handler(L); + Reader reader; + + ParseResult r = reader.Parse(*s, handler); + if (!r) + { + lua_settop(L, top); + lua_pushnil(L); + lua_pushfstring(L, "%s (%d)", GetParseError_En(r.Code()), r.Offset()); + return 2; + } + + return 1; +} + +static int json_decode(lua_State* L) +{ + size_t len = 0; + auto contents = luaL_checklstring(L, 1, &len); + StringStream s(contents); + return decode(L, &s); +} + +static int json_load(lua_State* L) +{ + auto filename = luaL_checklstring(L, 1, nullptr); + auto fp = file::open(filename, "rb"); + if (!fp) + luaL_error(L, "error while open file: %s", filename); + + char buffer[512]; + FileReadStream fs(fp, buffer, sizeof(buffer)); + AutoUTFInputStream<unsigned, FileReadStream> eis(fs); + + auto n = decode(L, &eis); + + fclose(fp); + return n; +} + +struct Key +{ + Key(const char* k, SizeType l) : key(k), size(l) + {} + bool operator<(const Key& rhs) const + { + return strcmp(key, rhs.key) < 0; + } + const char* key; + SizeType size; +}; + +class Encoder +{ + bool pretty; + bool sort_keys; + int max_depth; + static const int MAX_DEPTH_DEFAULT = 128; + public: + Encoder(lua_State*L, int opt) : pretty(false), sort_keys(false), max_depth(MAX_DEPTH_DEFAULT) + { + if (lua_isnoneornil(L, opt)) + return; + luaL_checktype(L, opt, LUA_TTABLE); + + pretty = luax::optboolfield(L, opt, "pretty", false); + sort_keys = luax::optboolfield(L, opt, "sort_keys", false); + max_depth = luax::optintfield(L, opt, "max_depth", MAX_DEPTH_DEFAULT); + } + + private: + template<typename Writer> + void encodeValue(lua_State* L, Writer* writer, int idx, int depth = 0) + { + size_t len; + const char* s; + int64_t integer; + auto t = lua_type(L, idx); + switch (t) + { + case LUA_TBOOLEAN: + writer->Bool(lua_toboolean(L, idx) != 0); + return; + case LUA_TNUMBER: + if (luax::isinteger(L, idx, &integer)) + writer->Int64(integer); + else + { + if (!writer->Double(lua_tonumber(L, idx))) + luaL_error(L, "error while encode double value."); + } + return; + case LUA_TSTRING: + s = lua_tolstring(L, idx, &len); + writer->String(s, static_cast<SizeType>(len)); + return; + case LUA_TTABLE: + return encodeTable(L, writer, idx, depth + 1); + case LUA_TNIL: + writer->Null(); + return; + case LUA_TFUNCTION: + if (values::isnull(L, idx)) + { + writer->Null(); + return; + } + // otherwise fall thought + case LUA_TLIGHTUSERDATA: // fall thought + case LUA_TUSERDATA: // fall thought + case LUA_TTHREAD: // fall thought + case LUA_TNONE: // fall thought + default: + luaL_error(L, "value type : %s", lua_typename(L, t)); + } + } + + template<typename Writer> + void encodeTable(lua_State* L, Writer* writer, int idx, int depth) + { + if (depth > max_depth) + luaL_error(L, "nested too depth"); + + if (!lua_checkstack(L, 4)) // requires at least 4 slots in stack: table, key, value, key + luaL_error(L, "stack overflow"); + + lua_pushvalue(L, idx); + if (values::isarray(L, -1)) + { + encodeArray(L, writer, depth); + lua_pop(L, 1); + return; + } + + // is object. + if (!sort_keys) + { + encodeObject(L, writer, depth); + lua_pop(L, 1); + return; + } + + lua_pushnil(L); + std::vector<Key> keys; + + while (lua_next(L, -2)) + { + + + if (lua_type(L, -2) == LUA_TSTRING) + { + size_t len = 0; + auto key = lua_tolstring(L, -2, &len); + keys.push_back(Key(key, static_cast<SizeType>(len))); + } + + // pop value, leaving original key + lua_pop(L, 1); + + } + + encodeObject(L, writer, depth, keys); + lua_pop(L, 1); + } + + template<typename Writer> + void encodeObject(lua_State* L, Writer* writer, int depth) + { + writer->StartObject(); + + + lua_pushnil(L); + while (lua_next(L, -2)) + { + + if (lua_type(L, -2) == LUA_TSTRING) + { + size_t len = 0; + auto key = lua_tolstring(L, -2, &len); + writer->Key(key, static_cast<SizeType>(len)); + encodeValue(L, writer, -1, depth); + } + + // pop value, leaving original key + lua_pop(L, 1); + + } + + writer->EndObject(); + } + + template<typename Writer> + void encodeObject(lua_State* L, Writer* writer, int depth, std::vector<Key> &keys) + { + + writer->StartObject(); + std::sort(keys.begin(), keys.end()); + + std::vector<Key>::const_iterator i = keys.begin(); + std::vector<Key>::const_iterator e = keys.end(); + for ( auto const& i : keys ) + { + writer->Key(i.key, static_cast<SizeType>(i.size)); + lua_pushlstring(L, i.key, i.size); + lua_gettable(L, -2); + encodeValue(L, writer, -1, depth); + lua_pop(L, 1); + } + + writer->EndObject(); + } + + template<typename Writer> + void encodeArray(lua_State* L, Writer* writer, int depth) + { + + writer->StartArray(); + auto MAX = static_cast<int>(luax::rawlen(L, -1)); // lua_rawlen always returns value >= 0 + for (auto n = 1; n <= MAX; ++n) + { + lua_rawgeti(L, -1, n); + encodeValue(L, writer, -1, depth); + lua_pop(L, 1); + } + writer->EndArray(); + + } + + public: + template<typename Stream> + void encode(lua_State* L, Stream* s, int idx) + { + if (pretty) + { + PrettyWriter<Stream> writer(*s); + encodeValue(L, &writer, idx); + } + else + { + Writer<Stream> writer(*s); + encodeValue(L, &writer, idx); + } + } +}; + +static int json_encode(lua_State* L) +{ + try + { + Encoder encode(L, 2); + StringBuffer s; + encode.encode(L, &s, 1); + lua_pushlstring(L, s.GetString(), s.GetSize()); + return 1; + } + catch (...) + { + luaL_error(L, "error while encoding"); + } + return 0; +} + +static int json_dump(lua_State* L) +{ + Encoder encoder(L, 3); + + auto filename = luaL_checkstring(L, 2); + auto fp = file::open(filename, "wb"); + if (fp == nullptr) + luaL_error(L, "error while open file: %s", filename); + + char buffer[512]; + FileWriteStream fs(fp, buffer, sizeof(buffer)); + encoder.encode(L, &fs, 1); + fclose(fp); + return 0; +} + +namespace values +{ + static auto nullref = LUA_NOREF; + /** + * Returns rapidjson.null. + */ + int json_null(lua_State* L) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, nullref); + return 1; + } +} + +static const luaL_Reg methods[] = +{ + // string <--> lua table + { "decode", json_decode }, + { "encode", json_encode }, + + // file <--> lua table + { "load", json_load }, + { "dump", json_dump }, + + // special tags and functions + { "null", values::json_null }, + { "object", json_object }, + { "array", json_array }, + + // JSON types + { "Document", Userdata<Document>::create }, + { "SchemaDocument", Userdata<SchemaDocument>::create }, + { "SchemaValidator", Userdata<SchemaValidator>::create }, + + {nullptr, nullptr } +}; + +extern "C" +{ + LUALIB_API int luaopen_rapidjson(lua_State* L) + { + lua_newtable(L); + + luax::setfuncs(L, methods); + + lua_pushliteral(L, "rapidjson"); + lua_setfield(L, -2, "_NAME"); + + lua_pushliteral(L, LUA_RAPIDJSON_VERSION); + lua_setfield(L, -2, "_VERSION"); + + lua_getfield(L, -1, "null"); + values::nullref = luaL_ref(L, LUA_REGISTRYINDEX); + + + createSharedMeta(L, "json.object", "object"); + createSharedMeta(L, "json.array", "array"); + + Userdata<Document>::luaopen(L); + Userdata<SchemaDocument>::luaopen(L); + Userdata<SchemaValidator>::luaopen(L); + + return 1; + } +} diff --git a/src/script/rapidjson/schema.cpp b/src/script/rapidjson/schema.cpp new file mode 100644 index 0000000..ade5736 --- /dev/null +++ b/src/script/rapidjson/schema.cpp @@ -0,0 +1,116 @@ + +#include "userdata.hpp" +#include "values.hpp" + +#include "lua.hpp" +#include "rapidjson.h" + +using namespace rapidjson; + +template<> +const char* const Userdata<SchemaDocument>::metatable = "rapidjson.SchemaDocument"; + +template<> +SchemaDocument* Userdata<SchemaDocument>::construct(lua_State * L) +{ + switch (lua_type(L, 1)) + { + case LUA_TNONE: + return new SchemaDocument(Document()); // empty schema + case LUA_TSTRING: + { + auto d = Document(); + size_t len = 0; + auto s = lua_tolstring(L, 1, &len); + d.Parse(s, len); + return new SchemaDocument(d); + } + case LUA_TTABLE: + { + auto doc = Document(); + values::toDocument(L, 1, &doc); + return new SchemaDocument(doc); + } + case LUA_TUSERDATA: + { + auto doc = Userdata<Document>::check(L, 1); + return new SchemaDocument(*doc); + } + default: + luax::typerror(L, 1, "none, string, table or rapidjson.Document"); + return nullptr; // Just make compiler happy + } +} + +template <> +const luaL_Reg* Userdata<SchemaDocument>::methods() +{ + static const luaL_Reg reg[] = + { + { "__gc", metamethod_gc }, + { nullptr, nullptr } + }; + return reg; +} + +template<> +const char* const Userdata<SchemaValidator>::metatable = "rapidjson.SchemaValidator"; + +template<> +SchemaValidator* Userdata<SchemaValidator>::construct(lua_State * L) +{ + auto sd = Userdata<SchemaDocument>::check(L, 1); + return new SchemaValidator(*sd); +} + +static void pushValidator_error(lua_State* L, SchemaValidator* validator) +{ + luaL_Buffer b; + luaL_buffinit(L, &b); + + luaL_addstring(&b, "invalid \""); + luaL_addstring(&b, validator->GetInvalidSchemaKeyword()); + luaL_addstring(&b, "\" in docuement at pointer \""); + + // docuement pointer + StringBuffer sb; + validator->GetInvalidDocumentPointer().StringifyUriFragment(sb); + luaL_addlstring(&b, sb.GetString(), sb.GetSize()); + luaL_addchar(&b, '"'); + + luaL_pushresult(&b); +} + +static int SchemaValidator_validate(lua_State* L) +{ + auto validator = Userdata<SchemaValidator>::check(L, 1); + auto doc = Userdata<Document>::check(L, 2); + auto ok = doc->Accept(*validator); + lua_pushboolean(L, ok); + int nr; + + if (ok) + { + nr = 1; + } + else + { + pushValidator_error(L, validator); + nr = 2; + } + validator->Reset(); + + return nr; +} + +template <> +const luaL_Reg* Userdata<SchemaValidator>::methods() +{ + static const luaL_Reg reg[] = + { + { "__gc", metamethod_gc }, + { "validate", SchemaValidator_validate }, + { nullptr, nullptr } + }; + return reg; +} diff --git a/src/script/rapidjson/userdata.hpp b/src/script/rapidjson/userdata.hpp new file mode 100644 index 0000000..1127bac --- /dev/null +++ b/src/script/rapidjson/userdata.hpp @@ -0,0 +1,112 @@ +#ifndef __LUA_RAPIDJSON_USERDATA_HPP__ +#define __LUA_RAPIDJSON_USERDATA_HPP__ + + +#include "qcommon/q3_lauxlib.h" + +#include "luax.hpp" + +#include "lua.hpp" +#include "rapidjson.h" + +template <typename T> +struct Userdata +{ + static int create(lua_State* L) + { + push(L, construct(L)); + return 1; + } + + static T* construct(lua_State* L); + + static void luaopen(lua_State* L) + { + luaL_newmetatable(L, metatable); + lua_pushvalue(L, -1); + luax::setfuncs(L, methods()); + lua_setfield(L, -2, "__index"); + lua_pop(L, 1); + } + + static const luaL_Reg* methods(); + + static void push(lua_State* L, T* c) + { + if (!c) + { + lua_pushnil(L); + return; + } + + T** ud = reinterpret_cast<T**>(lua_newuserdata(L, sizeof(*ud))); + if (!ud) + luaL_error(L, "Out of memory"); + + *ud = c; + + luaL_getmetatable(L, metatable); + lua_setmetatable(L, -2); + } + + static T** getUserdata(lua_State* L, int idx) + { + return reinterpret_cast<T**>(lua_touserdata(L, idx)); + } + + + static T* get(lua_State* L, int idx) + { + auto p = getUserdata(L, idx); + if (p && *p ) + { + if (lua_getmetatable(L, idx)) + { /* does it have a metatable? */ + luaL_getmetatable(L, metatable); /* get correct metatable */ + if (lua_rawequal(L, -1, -2)) + { /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return *p; + } + } + } + return nullptr; + } + + static T* check(lua_State* L, int idx) + { + auto ud = reinterpret_cast<T**>(luaL_checkudata(L, idx, metatable)); + if (!*ud) + luaL_error(L, "%s already closed", metatable); + return *ud; + } + + static int metamethod_gc(lua_State* L) + { + T** ud = reinterpret_cast<T**>(luaL_checkudata(L, 1, metatable)); + if (*ud) + { + delete *ud; + *ud = nullptr; + } + return 0; + } + + static int metamethod_tostring(lua_State* L) + { + auto ud = getUserdata(L, 1); + if (*ud) + { + lua_pushfstring(L, "%s (%p)", metatable, *ud); + } + else + { + lua_pushfstring(L, "%s (closed)", metatable); + } + return 1; + } + + static const char* const metatable; +}; + +#endif diff --git a/src/script/rapidjson/values.cpp b/src/script/rapidjson/values.cpp new file mode 100644 index 0000000..1d9c892 --- /dev/null +++ b/src/script/rapidjson/values.cpp @@ -0,0 +1,108 @@ + +#include "values.hpp" +#include "luax.hpp" + +using rapidjson::Value; +using rapidjson::SizeType; + +namespace values +{ + namespace details + { + static Value NumberValue(lua_State* L, int idx); + static Value StringValue(lua_State* L, int idx, Allocator& allocator); + static Value TableValue(lua_State* L, int idx, int depth, Allocator& allocator); + static Value ObjectValue(lua_State* L, int idx, int depth, Allocator& allocator); + static Value ArrayValue(lua_State* L, int idx, int depth, Allocator& allocator); + + Value toValue(lua_State* L, int idx, int depth, Allocator& allocator) + { + auto t = lua_type(L, idx); + switch (t) + { + case LUA_TBOOLEAN: + return Value(lua_toboolean(L, idx) != 0); + case LUA_TNUMBER: + return NumberValue(L, idx); + case LUA_TSTRING: + return StringValue(L, idx, allocator); + case LUA_TTABLE: + return TableValue(L, idx, depth + 1, allocator); + case LUA_TNIL: + return Value(); + case LUA_TFUNCTION: + if (isnull(L, idx)) + return Value(); + // otherwise fall thought + case LUA_TLIGHTUSERDATA: // fall thought + case LUA_TUSERDATA: // fall thought + case LUA_TTHREAD: // fall thought + case LUA_TNONE: // fall thought + default: + luaL_error(L, "value type %s is not a valid json value", lua_typename(L, t)); + return Value(); // Just make compiler happy + } + } + + Value NumberValue(lua_State* L, int idx) + { + int64_t integer; + return luax::isinteger(L, idx, &integer) ? Value(integer) : Value(lua_tonumber(L, idx)); + } + + Value StringValue(lua_State* L, int idx, Allocator& allocator) + { + size_t len; + const char* s = lua_tolstring(L, idx, &len); + return Value(s, static_cast<SizeType>(len), allocator); + } + + Value TableValue(lua_State* L, int idx, int depth, Allocator& allocator) + { + if (depth > 1024) + luaL_error(L, "nested too depth"); + + if (!lua_checkstack(L, 4)) // requires at least 4 slots in stack: table, key, value, key + luaL_error(L, "stack overflow"); + + return isarray(L, idx) ? ArrayValue(L, idx, depth, allocator) : ObjectValue(L, idx, depth, allocator); + } + + Value ObjectValue(lua_State* L, int idx, int depth, Allocator& allocator) + { + Value object(rapidjson::kObjectType); + lua_pushvalue(L, idx); + lua_pushnil(L); + while (lua_next(L, -2)) + { + + if (lua_type(L, -2) == LUA_TSTRING) + { + object.AddMember(StringValue(L, -2, allocator), toValue(L, -1, depth, allocator), allocator); + } + + // pop value, leaving original key + lua_pop(L, 1); + + } + + lua_pop(L, 1); + return object; + } + + Value ArrayValue(lua_State* L, int idx, int depth, Allocator& allocator) + { + Value array(rapidjson::kArrayType); + auto MAX = static_cast<int>(luax::rawlen(L, idx)); // luax::rawlen always returns size_t (>= 0) + for (auto n = 1; n <= MAX; ++n) + { + lua_rawgeti(L, idx, n); + array.PushBack(toValue(L, -1, depth, allocator), allocator); + lua_pop(L, 1); + } + + + return array; + } + } +} diff --git a/src/script/rapidjson/values.hpp b/src/script/rapidjson/values.hpp new file mode 100644 index 0000000..7fb3abe --- /dev/null +++ b/src/script/rapidjson/values.hpp @@ -0,0 +1,245 @@ +#ifndef __LUA_RAPIDJSON_TOLUAHANDLER_HPP__ +#define __LUA_RAPIDJSON_TOLUAHANDLER_HPP__ + +#include <vector> + + +#include "qcommon/q3_lauxlib.h" +#include "lua.hpp" +#include "rapidjson.h" + +#include "luax.hpp" +namespace values +{ + typedef rapidjson::Document::AllocatorType Allocator; + + int json_null(lua_State* L); + + inline bool isnull(lua_State* L, int idx) + { + lua_pushvalue(L, idx); // [value] + + json_null(L); // [value, json.null] + auto is = lua_rawequal(L, -1, -2) != 0; + lua_pop(L, 2); // [] + + return is; + } + + inline bool hasJsonType(lua_State* L, int idx, bool& isarray) + { + auto has = false; + if (lua_getmetatable(L, idx)) + { + // [metatable] + lua_getfield(L, -1, "__jsontype"); // [metatable, metatable.__jsontype] + if (lua_isstring(L, -1)) + { + size_t len; + auto s = lua_tolstring(L, -1, &len); + isarray = strncmp(s, "array", 6) == 0; + has = true; + } + lua_pop(L, 2); // [] + } + + return has; + } + + inline bool isarray(lua_State* L, int idx) + { + auto arr = false; + if (hasJsonType(L, idx, arr)) // any table with a meta field __jsontype set to 'array' are arrays + return arr; + + return luax::rawlen(L, idx) > 0; // any table has length > 0 are treat as array. + } + + /** + * Handle json SAX events and create Lua object. + */ + struct ToLuaHandler + { + explicit ToLuaHandler(lua_State* aL) : L(aL) + { stack_.reserve(32); } + + bool Null() + { + json_null(L); + context_.submit(L); + return true; + } + bool Bool(bool b) + { + lua_pushboolean(L, b); + context_.submit(L); + return true; + } + bool Int(int i) + { + lua_pushinteger(L, i); + context_.submit(L); + return true; + } + bool Uint(unsigned u) + { + if (sizeof(lua_Integer) > sizeof(unsigned int) || u <= static_cast<unsigned>(std::numeric_limits<lua_Integer>::max())) + lua_pushinteger(L, static_cast<lua_Integer>(u)); + else + lua_pushnumber(L, static_cast<lua_Number>(u)); + context_.submit(L); + return true; + } + bool Int64(int64_t i) + { + if (sizeof(lua_Integer) >= sizeof(int64_t) || (i <= std::numeric_limits<lua_Integer>::max() && i >= std::numeric_limits<lua_Integer>::min())) + lua_pushinteger(L, static_cast<lua_Integer>(i)); + else + lua_pushnumber(L, static_cast<lua_Number>(i)); + context_.submit(L); + return true; + } + bool Uint64(uint64_t u) + { + if (sizeof(lua_Integer) > sizeof(uint64_t) || u <= static_cast<uint64_t>(std::numeric_limits<lua_Integer>::max())) + lua_pushinteger(L, static_cast<lua_Integer>(u)); + else + lua_pushnumber(L, static_cast<lua_Number>(u)); + context_.submit(L); + return true; + } + bool Double(double d) + { + lua_pushnumber(L, static_cast<lua_Number>(d)); + context_.submit(L); + return true; + } + bool RawNumber(const char* str, rapidjson::SizeType length, bool copy) + { + lua_getglobal(L, "tonumber"); + lua_pushlstring(L, str, length); + lua_call(L, 1, 1); + context_.submit(L); + return true; + } + bool String(const char* str, rapidjson::SizeType length, bool copy) + { + lua_pushlstring(L, str, length); + context_.submit(L); + return true; + } + bool StartObject() + { + lua_createtable(L, 0, 0); // [..., object] + + // mark as object. + luaL_getmetatable(L, "json.object"); //[..., object, json.object] + lua_setmetatable(L, -2); // [..., object] + + stack_.push_back(context_); + context_ = Ctx::Object(); + return true; + } + bool Key(const char* str, rapidjson::SizeType length, bool copy) const + { + lua_pushlstring(L, str, length); + return true; + } + bool EndObject(rapidjson::SizeType memberCount) + { + context_ = stack_.back(); + stack_.pop_back(); + context_.submit(L); + return true; + } + bool StartArray() + { + lua_createtable(L, 0, 0); + + // mark as array. + luaL_getmetatable(L, "json.array"); //[..., array, json.array] + lua_setmetatable(L, -2); // [..., array] + + stack_.push_back(context_); + context_ = Ctx::Array(); + return true; + } + bool EndArray(rapidjson::SizeType elementCount) + { + assert(elementCount == context_.index_); + context_ = stack_.back(); + stack_.pop_back(); + context_.submit(L); + return true; + } + private: + + + struct Ctx + { + Ctx() : index_(0), fn_(&topFn) {} + Ctx(const Ctx& rhs) : index_(rhs.index_), fn_(rhs.fn_) {} + + const Ctx& operator=(const Ctx& rhs) + { + if (this != &rhs) + { + index_ = rhs.index_; + fn_ = rhs.fn_; + } + return *this; + } + + static Ctx Object() + { return Ctx(&objectFn); } + + static Ctx Array() + { return Ctx(&arrayFn); } + + void submit(lua_State* L) + { fn_(L, this); } + + int index_; + + void(*fn_)(lua_State* L, Ctx* ctx); + + private: + explicit Ctx(void(*f)(lua_State* L, Ctx* ctx)) : index_(0), fn_(f) {} + + static void objectFn(lua_State* L, Ctx* ctx) + { lua_rawset(L, -3); } + + static void arrayFn(lua_State* L, Ctx* ctx) + { lua_rawseti(L, -2, ++ctx->index_); } + + static void topFn(lua_State* L, Ctx* ctx) {} + }; + + lua_State* L; + std::vector < Ctx > stack_; + Ctx context_; + }; + + namespace details + { + rapidjson::Value toValue(lua_State* L, int idx, int depth, Allocator& allocator); + } + + inline rapidjson::Value toValue(lua_State* L, int idx, Allocator& allocator) + { + return details::toValue(L, idx, 0, allocator); + } + + inline void toDocument(lua_State* L, int idx, rapidjson::Document* doc) + { + details::toValue(L, idx, 0, doc->GetAllocator()).Swap(*doc); + } + + inline void push(lua_State* L, const rapidjson::Value& v) + { + ToLuaHandler handler(L); + v.Accept(handler); + } +} + +#endif |