summaryrefslogtreecommitdiff
path: root/src/script
diff options
context:
space:
mode:
authorIronClawTrem <louie.nutman@gmail.com>2020-02-16 03:40:06 +0000
committerIronClawTrem <louie.nutman@gmail.com>2020-02-16 03:40:06 +0000
commit425decdf7e9284d15aa726e3ae96b9942fb0e3ea (patch)
tree6c0dd7edfefff1be7b9e75fe0b3a0a85fe1595f3 /src/script
parentccb0b2e4d6674a7a00c9bf491f08fc73b6898c54 (diff)
create tremded branch
Diffstat (limited to 'src/script')
-rw-r--r--src/script/CMakeLists.txt35
-rw-r--r--src/script/bind.h107
-rw-r--r--src/script/client.h54
-rw-r--r--src/script/cmd.h50
-rw-r--r--src/script/cvar.h146
-rw-r--r--src/script/http_client.h65
-rw-r--r--src/script/lnettlelib.c94
-rw-r--r--src/script/lnettlelib.h15
-rw-r--r--src/script/nettle.h41
-rw-r--r--src/script/rapidjson.h52
-rw-r--r--src/script/rapidjson/LICENSE19
-rw-r--r--src/script/rapidjson/document.cpp194
-rw-r--r--src/script/rapidjson/file.hpp21
-rw-r--r--src/script/rapidjson/luax.hpp82
-rw-r--r--src/script/rapidjson/rapidjson.cpp414
-rw-r--r--src/script/rapidjson/schema.cpp116
-rw-r--r--src/script/rapidjson/userdata.hpp112
-rw-r--r--src/script/rapidjson/values.cpp108
-rw-r--r--src/script/rapidjson/values.hpp245
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