changes
This commit is contained in:
@@ -78,6 +78,7 @@ CORE_OBJ_FILES=\
|
||||
obj/globaldb.o\
|
||||
obj/sched.o\
|
||||
obj/http.o\
|
||||
obj/json.o\
|
||||
obj/table.o\
|
||||
obj/gui.o\
|
||||
obj/luasnap.o\
|
||||
|
||||
1260
luprex/core/cpp/bytell-hash-map.hpp
Normal file
1260
luprex/core/cpp/bytell-hash-map.hpp
Normal file
File diff suppressed because it is too large
Load Diff
1496
luprex/core/cpp/flat-hash-map.hpp
Normal file
1496
luprex/core/cpp/flat-hash-map.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
||||
#include "wrap-string.hpp"
|
||||
#include "util.hpp"
|
||||
#include "luastack.hpp"
|
||||
#include "json.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -319,6 +320,38 @@ static void send_error_response(bool http11, int code, eng::string extrainfo, St
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP 1.0: An entity body is included with a request message only when the request method calls for one.
|
||||
// The presence of an entity body in a request is signaled by the inclusion of a Content-Length header field
|
||||
// in the request message headers. HTTP/1.0 requests containing an entity body must include a valid
|
||||
// Content-Length header field.
|
||||
//
|
||||
// HTTP 1.1: The presence of a message-body in a request is signaled by the inclusion of a Content-Length or
|
||||
// Transfer-Encoding header field in the request's message-headers
|
||||
//
|
||||
static bool request_contains_content(int content_length, std::string_view transfer_encoding) {
|
||||
return (content_length >= 0) || (!transfer_encoding.empty());
|
||||
}
|
||||
|
||||
// HTTP 1.0: For response messages, whether or not an entity body is included with a message is dependent on
|
||||
// both the request method and the response code. All responses to the HEAD request method must not include a
|
||||
// body, even though the presence of entity header fields may lead one to believe they do. All 1xx (informational),
|
||||
// 204 (no content), and 304 (not modified) responses must not include a body. All other responses must include an
|
||||
// entity body or a Content-Length header field defined with a value of zero (0).
|
||||
//
|
||||
// HTTP 1.1: For response messages, whether or not a message-body is included with a message is dependent on both
|
||||
// the request method and the response status code (section 6.1.1). All responses to the HEAD request method MUST
|
||||
// NOT include a message-body, even though the presence of entity- header fields might lead one to believe they
|
||||
// do. All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST NOT include a message-body.
|
||||
// All other responses do include a message-body, although it MAY be of zero length.
|
||||
//
|
||||
static bool response_contains_content(std::string_view method, int status) {
|
||||
if (method == "HEAD") return false;
|
||||
if ((status >= 100) && (status <= 199)) return false;
|
||||
if (status == 204) return false;
|
||||
if (status == 304) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// In a properly-formed url, the hostname and path are url encoded.
|
||||
// This parser expects an encoded URL.
|
||||
struct ParsedURL {
|
||||
@@ -451,8 +484,8 @@ void HttpClientRequest::send_internal(StreamBuffer *sb, bool debug_string) const
|
||||
sb->write_bytes("\r\n");
|
||||
}
|
||||
|
||||
// If it's a post request, send the content length and the content type.
|
||||
if (method_ == "POST") {
|
||||
// Send the content length and the content type.
|
||||
if (content_assigned_) {
|
||||
send_content_length_header(true, content_.size(), sb);
|
||||
send_content_type_header(true, mime_type_, sb);
|
||||
}
|
||||
@@ -460,8 +493,8 @@ void HttpClientRequest::send_internal(StreamBuffer *sb, bool debug_string) const
|
||||
// Send the extra linebreak.
|
||||
sb->write_bytes("\r\n");
|
||||
|
||||
// If it's a post request, send the content.
|
||||
if (method_ == "POST") {
|
||||
// Send the content.
|
||||
if (content_assigned_) {
|
||||
sb->write_bytes(content_);
|
||||
}
|
||||
}
|
||||
@@ -681,6 +714,17 @@ void HttpClientRequest::set_content(LuaStack &LS, LuaSlot val) {
|
||||
set_content(LS.ckstring(val));
|
||||
}
|
||||
|
||||
void HttpClientRequest::set_jsonvalue(LuaStack &LS, LuaSlot val) {
|
||||
eng::string out;
|
||||
eng::string err = json::encode(LS, val, out, false, HttpParser::MAX_CONTENT_LENGTH);
|
||||
if (!err.empty()) {
|
||||
check_fail(util::ss("json encode failure: ", err));
|
||||
return;
|
||||
}
|
||||
set_content(out);
|
||||
set_mime_type("application/json");
|
||||
}
|
||||
|
||||
void HttpClientRequest::set_defaults() {
|
||||
if (method_.empty()) {
|
||||
method_ = "GET";
|
||||
@@ -715,6 +759,20 @@ void HttpClientRequest::set_config(LuaStack &LS0, LuaSlot tab) {
|
||||
set_mime_type(LS, val);
|
||||
} else if (kstr == "content") {
|
||||
set_content(LS, val);
|
||||
} else if (kstr == "html") {
|
||||
set_content(LS, val);
|
||||
set_mime_type("text/html");
|
||||
} else if (kstr == "text") {
|
||||
set_content(LS, val);
|
||||
set_mime_type("text/plain");
|
||||
} else if (kstr == "json") {
|
||||
set_content(LS, val);
|
||||
set_mime_type("application/json");
|
||||
} else if (kstr == "bytes") {
|
||||
set_content(LS, val);
|
||||
set_mime_type("application/octet-stream");
|
||||
} else if (kstr == "jsonvalue") {
|
||||
set_jsonvalue(LS, val);
|
||||
} else if (kstr == "") {
|
||||
check_fail(util::ss("configuration parameter names must be strings."));
|
||||
} else {
|
||||
@@ -755,13 +813,6 @@ eng::string HttpClientRequest::check() const {
|
||||
if (mime_type_.empty()) {
|
||||
return "mime type not set for POST request";
|
||||
}
|
||||
} else {
|
||||
if (content_assigned_) {
|
||||
return "content can only be specified for POST requests";
|
||||
}
|
||||
if (!mime_type_.empty()) {
|
||||
return "mime type can only be specified for POST requests";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -822,20 +873,23 @@ void HttpServerResponse::send_internal(StreamBuffer *sb, bool debug_string) cons
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if we're going to be sending content.
|
||||
bool contains_content = response_contains_content("GET", status_);
|
||||
|
||||
// The status message is the human-readable status code.
|
||||
eng::string statusmsg = status_code_to_string(status_);
|
||||
|
||||
// Annotate error messages.
|
||||
const eng::string *content = &content_;
|
||||
if (errstatus() && (mime_type_ == "text/plain") &&
|
||||
!content_.empty() && !contains_newline(content_)) {
|
||||
if (errstatus() && contains_content &&
|
||||
(mime_type_ == "text/plain") && !contains_newline(content_)) {
|
||||
if (sv::has_prefix(content_, statusmsg)) {
|
||||
statusmsg = content_;
|
||||
} else {
|
||||
} else if (!content_.empty()) {
|
||||
statusmsg += ": ";
|
||||
statusmsg += content_;
|
||||
content = &statusmsg;
|
||||
}
|
||||
content = &statusmsg;
|
||||
}
|
||||
|
||||
// Send the status line.
|
||||
@@ -848,7 +902,7 @@ void HttpServerResponse::send_internal(StreamBuffer *sb, bool debug_string) cons
|
||||
}
|
||||
|
||||
// Send the headers that make sense if we're sending content.
|
||||
if (content_assigned_) {
|
||||
if (contains_content) {
|
||||
if (!errstatus()) {
|
||||
send_cache_control_header(http11_, max_age_, sb);
|
||||
}
|
||||
@@ -860,7 +914,7 @@ void HttpServerResponse::send_internal(StreamBuffer *sb, bool debug_string) cons
|
||||
sb->write_bytes("\r\n");
|
||||
|
||||
// Send the content.
|
||||
if (content_assigned_) {
|
||||
if (contains_content) {
|
||||
sb->write_bytes(*content);
|
||||
}
|
||||
}
|
||||
@@ -960,6 +1014,17 @@ void HttpServerResponse::set_content(LuaStack &LS, LuaSlot val) {
|
||||
set_content(LS.ckstring(val));
|
||||
}
|
||||
|
||||
void HttpServerResponse::set_jsonvalue(LuaStack &LS, LuaSlot val) {
|
||||
eng::string out;
|
||||
eng::string err = json::encode(LS, val, out, false, HttpParser::MAX_CONTENT_LENGTH);
|
||||
if (!err.empty()) {
|
||||
check_fail(util::ss("json encode failure: ", err));
|
||||
return;
|
||||
}
|
||||
set_content(out);
|
||||
set_mime_type("application/json");
|
||||
}
|
||||
|
||||
void HttpServerResponse::set_config(LuaStack &LS0, LuaSlot tab) {
|
||||
LuaVar key, val;
|
||||
LuaStack LS(LS0.state(), key, val);
|
||||
@@ -987,6 +1052,8 @@ void HttpServerResponse::set_config(LuaStack &LS0, LuaSlot tab) {
|
||||
} else if (kstr == "bytes") {
|
||||
set_content(LS, val);
|
||||
set_mime_type("application/octet-stream");
|
||||
} else if (kstr == "jsonvalue") {
|
||||
set_jsonvalue(LS, val);
|
||||
} else if (kstr == "") {
|
||||
check_fail(util::ss("response configuration parameters must be strings."));
|
||||
} else {
|
||||
@@ -1010,13 +1077,13 @@ void HttpServerResponse::set_defaults() {
|
||||
status_ = 200;
|
||||
}
|
||||
|
||||
// If you're sending an error message along with
|
||||
// an error status code, then assume the error
|
||||
// message is text/plain.
|
||||
if ((status_ >= 400) && (status_ <= 599)) {
|
||||
if (content_assigned_ && (mime_type_.empty())) {
|
||||
mime_type_ = "text/plain";
|
||||
}
|
||||
// If the response status indicates that we're sending
|
||||
// content, and there's no content, generate blank content.
|
||||
if (response_contains_content("GET", status_) &&
|
||||
(!content_assigned_) && (mime_type_.empty())) {
|
||||
content_assigned_ = true;
|
||||
content_ = "";
|
||||
mime_type_ = "text/plain";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,15 +1097,16 @@ eng::string HttpServerResponse::check() const {
|
||||
return "status code not specified";
|
||||
}
|
||||
|
||||
// If you specify content, you have to specify mime
|
||||
// type, and vice versa. Also, we need both for a valid response.
|
||||
if ((status_ == 200) || (content_assigned_) || (!mime_type_.empty())) {
|
||||
if (!content_assigned_) {
|
||||
return "content not specified";
|
||||
}
|
||||
if (mime_type_.empty()) {
|
||||
return "mime type not specified";
|
||||
}
|
||||
// If you assigned a mime type and didn't specify
|
||||
// content, that's bad.
|
||||
if ((!content_assigned_) && (!mime_type_.empty())) {
|
||||
return "mime type specified without content";
|
||||
}
|
||||
|
||||
// If you assigned content, but didn't assign
|
||||
// a mime type, that's bad.
|
||||
if (content_assigned_ && (mime_type_.empty())) {
|
||||
return "content specified without mime type";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -1337,6 +1405,17 @@ bool HttpParser::parse_content_chunked(std::string_view &view, bool closed) {
|
||||
}
|
||||
|
||||
bool HttpParser::parse_content(std::string_view &view, bool closed) {
|
||||
// If there's no body, just return true.
|
||||
if (is_request_) {
|
||||
if (!request_contains_content(content_length_, transfer_encoding_)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!response_contains_content(method_, status_)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the content.
|
||||
if (transfer_encoding_ == "") {
|
||||
if (!parse_content_basic(view, closed)) {
|
||||
@@ -1386,8 +1465,8 @@ bool HttpParser::parse_content(std::string_view &view, bool closed) {
|
||||
}
|
||||
|
||||
void HttpParser::store(LuaStack &LS0, LuaSlot tab) const {
|
||||
LuaVar ptab;
|
||||
LuaStack LS(LS0.state(), ptab);
|
||||
LuaVar ptab, djson;
|
||||
LuaStack LS(LS0.state(), ptab, djson);
|
||||
|
||||
LS.newtable(tab);
|
||||
if (!is_request_) {
|
||||
@@ -1402,6 +1481,10 @@ void HttpParser::store(LuaStack &LS0, LuaSlot tab) const {
|
||||
if (!mime_type_.empty() || !content_.empty()) {
|
||||
LS.rawset(tab, "mimetype", mime_type_);
|
||||
LS.rawset(tab, "content", content_);
|
||||
if (mime_type_ == "application/json") {
|
||||
json::decode(LS, djson, content_);
|
||||
LS.rawset(tab, "jsonvalue", djson);
|
||||
}
|
||||
}
|
||||
// We currently don't store the method, because
|
||||
// our server only supports GET. Even if we
|
||||
@@ -1475,9 +1558,10 @@ eng::string HttpParser::debug_string() const {
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
void HttpParser::parse_response(std::string_view view, bool closed) {
|
||||
void HttpParser::parse_response(std::string_view view, bool closed, std::string_view method) {
|
||||
std::string_view original_view = view;
|
||||
is_request_ = false;
|
||||
method_ = method;
|
||||
|
||||
// Parse the status line.
|
||||
if (!parse_status_line(view, closed)) {
|
||||
@@ -1524,10 +1608,8 @@ void HttpParser::parse_request(std::string_view view, bool closed) {
|
||||
}
|
||||
|
||||
// Process the content, if any.
|
||||
if (method_ == "POST") {
|
||||
if (!parse_content(view, closed)) {
|
||||
return;
|
||||
}
|
||||
if (!parse_content(view, closed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the comm length.
|
||||
@@ -1594,8 +1676,22 @@ LuaDefine(http_clientrequest, "request",
|
||||
"| path (ie, '/index.html')"
|
||||
"| params (a table of url parameters)"
|
||||
"| verifycertificate (default: true)"
|
||||
"| content (the body to send to the server, if any)"
|
||||
"| mimetype (mime type of the body, if any)"
|
||||
"|"
|
||||
"|The table can also contain this field, which sets"
|
||||
"|host, port, path, and params in a single directive."
|
||||
"|"
|
||||
"| url (ie, 'https://host:port/path.html?a=b&c=d')"
|
||||
"|"
|
||||
"|The table can also contain these fields, which set"
|
||||
"|both the content and the mimetype in a single directive:"
|
||||
"|"
|
||||
"| html - content as a string (text/html)"
|
||||
"| text - content as a string (text/plain)"
|
||||
"| json - content as a string (application/json)"
|
||||
"| bytes - content as a string (application/octet-stream)"
|
||||
"|"
|
||||
"|You can specify url components separately (host, port, path,"
|
||||
"|and params), or you can specify the entire url as a unit. "
|
||||
"|If you specify components, they must not be url-encoded. "
|
||||
@@ -1674,7 +1770,7 @@ LuaDefine(http_clientresponse, "response",
|
||||
LuaRet tab;
|
||||
LuaStack LS(L, text, tab);
|
||||
HttpParser parser;
|
||||
parser.parse_response(LS.ckstring(text), true);
|
||||
parser.parse_response(LS.ckstring(text), true, "GET");
|
||||
parser.store(LS, tab);
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ public:
|
||||
void set_url(LuaStack &LS, LuaSlot val);
|
||||
void set_mime_type(LuaStack &LS, LuaSlot val);
|
||||
void set_content(LuaStack &LS, LuaSlot val);
|
||||
void set_jsonvalue(LuaStack &LS, LuaSlot val);
|
||||
|
||||
// Set default values for method and port.
|
||||
// This must be done after setting regular values.
|
||||
@@ -126,6 +127,10 @@ public:
|
||||
//
|
||||
eng::string check() const;
|
||||
|
||||
// Get the method.
|
||||
//
|
||||
eng::string method() const { return method_; }
|
||||
|
||||
// Put the request into the stream, assuming HTTP/1.1
|
||||
//
|
||||
void send(StreamBuffer *target) const { send_internal(target, false); }
|
||||
@@ -186,6 +191,7 @@ public:
|
||||
//
|
||||
HttpServerResponse();
|
||||
|
||||
void set_method(const eng::string &method);
|
||||
void set_status(int status);
|
||||
void set_max_age(int max_age);
|
||||
void set_mime_type(const eng::string &mime_type);
|
||||
@@ -195,6 +201,7 @@ public:
|
||||
void set_max_age(LuaStack &LS, LuaSlot val);
|
||||
void set_mime_type(LuaStack &LS, LuaSlot val);
|
||||
void set_content(LuaStack &LS, LuaSlot val);
|
||||
void set_jsonvalue(LuaStack &LS, LuaSlot val);
|
||||
|
||||
// Set default values.
|
||||
//
|
||||
@@ -275,7 +282,7 @@ protected:
|
||||
// The protocol: true for HTTP/1.1, false for HTTP/1.0
|
||||
bool http11_;
|
||||
|
||||
// The method: always "GET". (only when parsing requests)
|
||||
// The method. In a response, this is assigned before parsing.
|
||||
eng::string method_;
|
||||
|
||||
// The URL path, not url-encoded. (only when parsing requests)
|
||||
@@ -383,7 +390,7 @@ public:
|
||||
|
||||
// The parser will not try to parse content longer than this.
|
||||
//
|
||||
const int64_t MAX_CONTENT_LENGTH = 1000000;
|
||||
static constexpr int64_t MAX_CONTENT_LENGTH = 1000000;
|
||||
|
||||
// Parse a request or a response.
|
||||
//
|
||||
@@ -391,7 +398,7 @@ public:
|
||||
// construct a new parser.
|
||||
//
|
||||
void parse_request(std::string_view view, bool closed);
|
||||
void parse_response(std::string_view view, bool closed);
|
||||
void parse_response(std::string_view view, bool closed, std::string_view method);
|
||||
|
||||
// Store a status code and an error message, and clear the content.
|
||||
// This is generally used when the client detects an error,
|
||||
@@ -428,6 +435,7 @@ using HttpParserVec = eng::vector<HttpParser>;
|
||||
class HttpChannel {
|
||||
public:
|
||||
SharedChannel channel_;
|
||||
eng::string method_;
|
||||
int64_t parsed_bytes_;
|
||||
|
||||
bool marked_for_deletion() const { return channel_ == nullptr; }
|
||||
|
||||
@@ -87,12 +87,12 @@ void IdGlobalPool::deserialize(StreamBuffer *sb) {
|
||||
|
||||
eng::string IdGlobalPool::debug_string() const {
|
||||
eng::ostringstream oss;
|
||||
oss << "next_batch:" << util::hex64() << next_batch_ << " ";
|
||||
oss << "next_id:" << util::hex64() << next_id_ << " ";
|
||||
oss << "next_seqno: " << util::hex64() << next_seqno_ << " ";
|
||||
oss << "next_batch:" << util::hex64.val(next_batch_) << " ";
|
||||
oss << "next_id:" << util::hex64.val(next_id_) << " ";
|
||||
oss << "next_seqno: " << util::hex64.val(next_seqno_) << " ";
|
||||
oss << "salvaged:";
|
||||
for (const int64_t val : salvaged_) {
|
||||
oss << " " << util::hex64() << val;
|
||||
oss << " " << util::hex64.val(val);
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
@@ -253,9 +253,9 @@ eng::string IdPlayerPool::debug_string() const {
|
||||
oss << "cap:" << fifo_capacity_ << " ids:";
|
||||
for (int i = 0; i < int(ranges_.size()); i++) {
|
||||
if (i > 0) oss << ",";
|
||||
oss << util::hex64() << ranges_[i];
|
||||
oss << util::hex64.val(ranges_[i]);
|
||||
}
|
||||
oss << " seqno:" << util::hex64() << next_seqno_;
|
||||
oss << " seqno:" << util::hex64.val(next_seqno_);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
728
luprex/core/cpp/json.cpp
Normal file
728
luprex/core/cpp/json.cpp
Normal file
@@ -0,0 +1,728 @@
|
||||
#include "json.hpp"
|
||||
#include "luastack.hpp"
|
||||
#include "util.hpp"
|
||||
#include <string_view>
|
||||
#include <ostream>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
#define NOINDENT_LEVEL 1000
|
||||
|
||||
LuaTokenConstant(json_null, "null", "");
|
||||
LuaTokenConstant(json_object, "object", "");
|
||||
LuaTokenConstant(json_error, "error", "");
|
||||
|
||||
static void indent(eng::ostringstream &oss, int level) {
|
||||
if (level < NOINDENT_LEVEL) {
|
||||
oss << std::endl;
|
||||
for (int i = 0; i < level; i++) {
|
||||
oss << " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool length_exceeded(eng::ostringstream &oss, int maxlen) {
|
||||
return oss.tellp() > maxlen;
|
||||
}
|
||||
|
||||
template <class... ARGS>
|
||||
inline void store_error(eng::ostringstream &oss, const ARGS & ... args) {
|
||||
oss.str("");
|
||||
util::send_to_stream(oss, args...);
|
||||
}
|
||||
|
||||
static void store_length_error(eng::ostringstream &oss, int maxlen) {
|
||||
store_error(oss, "maximum json length exceeded: ", maxlen);
|
||||
}
|
||||
|
||||
static bool use_array_representation(lua_State *L) {
|
||||
int top = lua_gettop(L);
|
||||
int nfound = 0;
|
||||
while (true) {
|
||||
lua_rawgeti(L, top, nfound + 1);
|
||||
bool null = lua_isnil(L, -1);
|
||||
lua_settop(L, top);
|
||||
if (null) break;
|
||||
nfound += 1;
|
||||
}
|
||||
return (nfound == lua_nkeys(L, top));
|
||||
}
|
||||
|
||||
static bool encode_key(lua_State *L, eng::ostringstream &oss);
|
||||
static bool encode_value(lua_State *L, eng::ostringstream &oss, int level, int maxlen);
|
||||
|
||||
|
||||
// The goal here is to emit a double in such a way that
|
||||
// when we read it back in, we get the *exact* same number.
|
||||
//
|
||||
// In the worst case, you can accomplish this by using 17
|
||||
// digits of precision - that's enough to uniquely identify
|
||||
// all double values (see the following URL). However, 17
|
||||
// digits tends to produce unnecessary repeating decimals.
|
||||
// So we try 16 digits first, which tends to remove those
|
||||
// repeating decimals, but sometimes produces losses.
|
||||
// If that doesn't work, we fall back to 17 digits.
|
||||
//
|
||||
// https://randomascii.wordpress.com/2012/03/08/float-precisionfrom-zero-to-100-digits-2/
|
||||
//
|
||||
static void encode_double_lossless(double value, eng::ostringstream &oss) {
|
||||
char buffer[80];
|
||||
sprintf(buffer, "%.16g", value);
|
||||
if (strtod(buffer, nullptr) != value) {
|
||||
sprintf(buffer, "%.17g", value);
|
||||
assert(strtod(buffer, nullptr) == value);
|
||||
}
|
||||
oss << buffer;
|
||||
}
|
||||
|
||||
static bool encode_nil(lua_State *L, eng::ostringstream &oss) {
|
||||
oss << "null";
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_token(lua_State *L, eng::ostringstream &oss) {
|
||||
LuaToken token(lua_touserdata(L, -1));
|
||||
if (token == LuaToken("jsonnull")) {
|
||||
oss << "null";
|
||||
return true;
|
||||
} else {
|
||||
store_error(oss, "cannot encode token: [", token.str(), "]");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool encode_number(lua_State *L, eng::ostringstream &oss) {
|
||||
lua_Number value = lua_tonumber(L, -1);
|
||||
if (std::isnan(value) || std::isinf(value)) {
|
||||
store_error(oss, "cannot encode infinity or NAN");
|
||||
return false;
|
||||
}
|
||||
int64_t ivalue = int64_t(value);
|
||||
if (double(ivalue) == value) {
|
||||
oss << ivalue;
|
||||
} else {
|
||||
encode_double_lossless(value, oss);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_number_key(lua_State *L, eng::ostringstream &oss) {
|
||||
lua_Number value = lua_tonumber(L, -1);
|
||||
int64_t ivalue = int64_t(value);
|
||||
if (double(ivalue) != value) {
|
||||
store_error(oss, "cannot encode floating point numbers in table keys");
|
||||
return false;
|
||||
}
|
||||
if (ivalue >= 0) {
|
||||
oss << "\"\\uE000+" << ivalue << '"';
|
||||
} else {
|
||||
oss << "\"\\uE000-" << -ivalue << '"';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_boolean(lua_State *L, eng::ostringstream &oss) {
|
||||
int flag = lua_toboolean(L, -1);
|
||||
oss << (flag ? "true" : "false");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_string(lua_State *L, eng::ostringstream &oss) {
|
||||
size_t len;
|
||||
const char *s = lua_tolstring(L, -1, &len);
|
||||
std::string_view str(s, len);
|
||||
oss << '"';
|
||||
if (sv::valid_utf8(str) && !sv::has_prefix(str, "")) {
|
||||
// Output the string in the straightforward way,
|
||||
// using traditional json escaping.
|
||||
for (char c : str) {
|
||||
switch (c) {
|
||||
case '\\': oss << "\\\\"; break;
|
||||
case '"' : oss << "\\\""; break;
|
||||
case '\b': oss << "\\b"; break;
|
||||
case '\f': oss << "\\f"; break;
|
||||
case '\r': oss << "\\r"; break;
|
||||
case '\n': oss << "\\n"; break;
|
||||
case '\t': oss << "\\t"; break;
|
||||
default: {
|
||||
if (c < 32) {
|
||||
oss << "\\u" << util::hex16.val(c);
|
||||
} else {
|
||||
oss << c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Output as a base64-encoded string.
|
||||
oss << "\\uE000=";
|
||||
util::base64_encode(str, &oss);
|
||||
}
|
||||
oss << '"';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_array(lua_State *L, eng::ostringstream &oss, int level, int maxlen) {
|
||||
lua_checkstack(L, 20);
|
||||
int top = lua_gettop(L);
|
||||
oss << "[";
|
||||
level ++;
|
||||
int i = 1;
|
||||
while (true) {
|
||||
lua_rawgeti(L, top, i);
|
||||
if (lua_isnil(L, -1)) break;
|
||||
if (i > 1) oss << ",";
|
||||
indent(oss, level);
|
||||
bool ok = encode_value(L, oss, level, maxlen);
|
||||
lua_settop(L, top);
|
||||
if (!ok) return false;
|
||||
if (length_exceeded(oss, maxlen)) {
|
||||
store_length_error(oss, maxlen);
|
||||
return false;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
lua_settop(L, top);
|
||||
level --;
|
||||
indent(oss, level);
|
||||
oss << "]";
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_object(lua_State *L, eng::ostringstream &oss, int level, int maxlen) {
|
||||
lua_checkstack(L, 20);
|
||||
int top = lua_gettop(L);
|
||||
oss << "{";
|
||||
level ++;
|
||||
lua_pushnil(L);
|
||||
int i = 1;
|
||||
while (lua_next(L, top) != 0) {
|
||||
// Check for [json.object]=true, if so skip.
|
||||
if (lua_islightuserdata(L, -2) &&
|
||||
lua_isboolean(L, -1) &&
|
||||
(LuaToken(lua_touserdata(L, -2)) == LuaToken("object")) &&
|
||||
(lua_toboolean(L, -1) == 1)) {
|
||||
lua_pop(L, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
lua_pushvalue(L, -2);
|
||||
// Stack now has key, value, key
|
||||
assert(lua_gettop(L) == top + 3);
|
||||
if (i > 1) oss << ",";
|
||||
indent(oss, level);
|
||||
bool ok = encode_key(L, oss);
|
||||
if (!ok) {
|
||||
lua_settop(L, top);
|
||||
return false;
|
||||
}
|
||||
if (length_exceeded(oss, maxlen)) {
|
||||
store_length_error(oss, maxlen);
|
||||
lua_settop(L, top);
|
||||
return false;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
// Stack now has key, value
|
||||
assert(lua_gettop(L) == top + 2);
|
||||
oss << ((level < NOINDENT_LEVEL) ? " : " : ":");
|
||||
ok = encode_value(L, oss, level, maxlen);
|
||||
assert(lua_gettop(L) == top + 2);
|
||||
if (!ok) {
|
||||
lua_settop(L, top);
|
||||
return false;
|
||||
}
|
||||
if (length_exceeded(oss, maxlen)) {
|
||||
store_length_error(oss, maxlen);
|
||||
lua_settop(L, top);
|
||||
return false;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
// Stack now just has key.
|
||||
assert(lua_gettop(L) == top + 1);
|
||||
i += 1;
|
||||
}
|
||||
// Stack should be back to where we started.
|
||||
assert(lua_gettop(L) == top);
|
||||
level --;
|
||||
indent(oss, level);
|
||||
oss << "}";
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_key(lua_State *L, eng::ostringstream &oss) {
|
||||
int type = lua_type(L, -1);
|
||||
switch (type) {
|
||||
case LUA_TSTRING: return encode_string(L, oss);
|
||||
case LUA_TNUMBER: return encode_number_key(L, oss);
|
||||
case LUA_TBOOLEAN:
|
||||
case LUA_TTABLE: {
|
||||
store_error(oss, "cannot encode '", lua_typename(L, type), "' in table keys");
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
store_error(oss, "cannot encode '", lua_typename(L, type), "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool encode_value(lua_State *L, eng::ostringstream &oss, int level, int maxlen) {
|
||||
int type = lua_type(L, -1);
|
||||
switch (type) {
|
||||
case LUA_TNIL: return encode_nil(L, oss);
|
||||
case LUA_TNUMBER: return encode_number(L, oss);
|
||||
case LUA_TBOOLEAN: return encode_boolean(L, oss);
|
||||
case LUA_TSTRING: return encode_string(L, oss);
|
||||
case LUA_TLIGHTUSERDATA: return encode_token(L, oss);
|
||||
case LUA_TTABLE: {
|
||||
if (use_array_representation(L)) {
|
||||
return encode_array(L, oss, level, maxlen);
|
||||
} else {
|
||||
return encode_object(L, oss, level, maxlen);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
store_error(oss, "cannot encode '", lua_typename(L, type), "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool decode_value(lua_State *L, std::string_view &v);
|
||||
|
||||
static bool decode_id(lua_State *L, std::string_view &v) {
|
||||
std::string_view id = sv::read_ascii_identifier(v);
|
||||
if (id == "null") lua_pushlightuserdata(L, LuaToken("null").voidvalue());
|
||||
else if (id == "true") lua_pushboolean(L, 1);
|
||||
else if (id == "false") lua_pushboolean(L, 0);
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_number(lua_State *L, std::string_view &v) {
|
||||
std::string_view n = sv::read_number(v, true, true, true, true);
|
||||
if (n.empty()) return false;
|
||||
|
||||
// If it's an integer, make sure it fits in a lua double
|
||||
// losslessly. If it's a double, some loss in precision
|
||||
// is OK.
|
||||
if (sv::valid_number(n, true, true, false, false)) {
|
||||
int64_t i = sv::to_int64(n);
|
||||
if (!LuaStack::int64_storable(i)) return false;
|
||||
lua_pushnumber(L, double(i));
|
||||
return true;
|
||||
} else {
|
||||
double d = sv::to_double(n);
|
||||
if (std::isnan(d) || std::isinf(d)) return false;
|
||||
lua_pushnumber(L, d);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool decode_base64_string(lua_State *L, std::string_view &v) {
|
||||
// We've already read the starting quote and the E000
|
||||
// escape sequence at this point.
|
||||
|
||||
// Skip the equal sign.
|
||||
if (!sv::read_prefix(v, "=")) return false;
|
||||
|
||||
// Find the end of the quoted string.
|
||||
const char *p = v.data();
|
||||
const char *l = p + v.size();
|
||||
while (true) {
|
||||
if (p == l) return false;
|
||||
if (*p < 32) return false;
|
||||
if (*p == '"') break;
|
||||
p++;
|
||||
}
|
||||
std::string_view b64 = v.substr(0, p - v.data());
|
||||
v.remove_prefix(b64.size() + 1);
|
||||
eng::ostringstream oss;
|
||||
if (!util::base64_decode(b64, &oss)) return false;
|
||||
eng::string str = oss.str();
|
||||
lua_pushlstring(L, str.c_str(), str.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_int_string(lua_State *L, std::string_view &v) {
|
||||
// We've already read the starting quote and the E000
|
||||
// escape sequence at this point.
|
||||
|
||||
// Parse the number and the closing quote.
|
||||
std::string_view n = sv::read_number(v, true, true, false, false);
|
||||
if (n.empty()) return false;
|
||||
if (!sv::read_prefix(v, "\"")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the number fits in a lua double,
|
||||
// and push it on the stack.
|
||||
int64_t i = sv::to_int64(n);
|
||||
if (!LuaStack::int64_storable(i)) {
|
||||
return false;
|
||||
}
|
||||
lua_pushnumber(L, double(i));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_standard_string(lua_State *L, std::string_view &v) {
|
||||
// We've already read the starting quote at this point.
|
||||
eng::ostringstream oss;
|
||||
while (true) {
|
||||
// Get the next codepoint.
|
||||
int32_t c = sv::read_codepoint_utf8(v);
|
||||
|
||||
// If it's a control character or invalid codepoint, reject.
|
||||
if (c < 32) return false;
|
||||
|
||||
// If it is an unescaped quote, that's end of string.
|
||||
if (c == '"') break;
|
||||
|
||||
// If it's a backslash, then deal with the escape sequence.
|
||||
if (c == '\\') {
|
||||
char next = sv::read_ascii_char(v);
|
||||
switch (next) {
|
||||
case '"': oss << '"'; break;
|
||||
case '\\': oss << '\\'; break;
|
||||
case '/': oss << '/'; break;
|
||||
case 'r': oss << '\r'; break;
|
||||
case 'n': oss <<'\n'; break;
|
||||
case 'b': oss << '\b'; break;
|
||||
case 'f': oss << '\f'; break;
|
||||
case 't': oss << '\t'; break;
|
||||
case 'u': {
|
||||
std::string_view hexdigits = sv::read_nbytes(v, 4);
|
||||
if (hexdigits.size() != 4) return false;
|
||||
uint64_t codepoint = sv::to_hex64(hexdigits, 0x10000);
|
||||
if (codepoint >= 0x10000) return false;
|
||||
if (!util::write_codepoint_utf8(codepoint, &oss)) return false;
|
||||
break;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Any other codepoint should be echoed into stream.
|
||||
util::write_codepoint_utf8(c, &oss);
|
||||
}
|
||||
eng::string result = oss.str();
|
||||
lua_pushlstring(L, result.c_str(), result.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_string(lua_State *L, std::string_view &v) {
|
||||
if (!sv::read_prefix(v, "\"")) return false;
|
||||
|
||||
// Check for codepoint E000, the escape sequence.
|
||||
if (sv::read_prefix(v, "") ||
|
||||
sv::read_prefix(v, "\\uE000") ||
|
||||
sv::read_prefix(v, "\\ue000")) {
|
||||
char c = sv::zfront(v);
|
||||
if (c == '=') return decode_base64_string(L, v);
|
||||
else if ((c=='-') || (c=='+')) return decode_int_string(L, v);
|
||||
else return false;
|
||||
} else {
|
||||
return decode_standard_string(L, v);
|
||||
}
|
||||
}
|
||||
|
||||
static bool decode_array(lua_State *L, std::string_view &v) {
|
||||
if (!sv::read_prefix(v, "[")) return false;
|
||||
lua_newtable(L);
|
||||
int tabpos = lua_gettop(L);
|
||||
int next = 1;
|
||||
while (true) {
|
||||
v = sv::ltrim(v);
|
||||
if (sv::zfront(v) == ']') {
|
||||
v.remove_prefix(1);
|
||||
return true;
|
||||
}
|
||||
if (!decode_value(L, v)) {
|
||||
return false;
|
||||
}
|
||||
v = sv::ltrim(v);
|
||||
if (sv::zfront(v) == ',') {
|
||||
v.remove_prefix(1);
|
||||
}
|
||||
lua_rawseti(L, tabpos, next++);
|
||||
}
|
||||
}
|
||||
|
||||
static bool decode_object(lua_State *L, std::string_view &v) {
|
||||
if (!sv::read_prefix(v, "{")) return false;
|
||||
lua_newtable(L);
|
||||
int tabpos = lua_gettop(L);
|
||||
while (true) {
|
||||
v = sv::ltrim(v);
|
||||
if (sv::zfront(v) == '}') {
|
||||
v.remove_prefix(1);
|
||||
return true;
|
||||
}
|
||||
if (!decode_string(L, v)) {
|
||||
return false;
|
||||
}
|
||||
v = sv::ltrim(v);
|
||||
if (!sv::read_prefix(v, ":")) {
|
||||
return false;
|
||||
}
|
||||
if (!decode_value(L, v)) {
|
||||
return false;
|
||||
}
|
||||
v = sv::ltrim(v);
|
||||
if (sv::zfront(v) == ',') {
|
||||
v.remove_prefix(1);
|
||||
}
|
||||
lua_rawset(L, tabpos);
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a single value.
|
||||
//
|
||||
// On success, pushes the value on the stack and returns true.
|
||||
// On failure, pushes NIL on the stack and returns false.
|
||||
//
|
||||
static bool decode_value(lua_State *L, std::string_view &v) {
|
||||
lua_checkstack(L, 20);
|
||||
int top = lua_gettop(L);
|
||||
|
||||
// Skip blanks.
|
||||
v = sv::ltrim(v);
|
||||
|
||||
// Try to read something.
|
||||
char c = sv::zfront(v);
|
||||
bool result;
|
||||
if (c == '"') result = decode_string(L, v);
|
||||
else if (c == '[') result = decode_array(L, v);
|
||||
else if (c == '{') result = decode_object(L, v);
|
||||
else if (sv::ascii_isalpha(c)) result = decode_id(L, v);
|
||||
else result = decode_number(L, v);
|
||||
|
||||
// On failure, the decode routines may leave junk
|
||||
// on the stack, in which case it's our job to clean up.
|
||||
if (result == false) {
|
||||
lua_settop(L, top);
|
||||
lua_pushnil(L);
|
||||
}
|
||||
|
||||
// Now there should be exactly one new value on the stack.
|
||||
assert(lua_gettop(L) == top + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace json {
|
||||
|
||||
eng::string encode(LuaStack &LS, LuaSlot in, eng::string &out, bool indent, int maxlen) {
|
||||
eng::ostringstream oss;
|
||||
|
||||
// Call the recursive encoder. Clean up any crap on the lua stack afterward.
|
||||
int top = lua_gettop(LS.state());
|
||||
lua_pushvalue(LS.state(), in.index());
|
||||
bool ok = encode_value(LS.state(), oss, indent ? 0 : NOINDENT_LEVEL, maxlen);
|
||||
lua_settop(LS.state(), top);
|
||||
|
||||
// One last check for overruns.
|
||||
if (ok && length_exceeded(oss, maxlen)) {
|
||||
store_length_error(oss, maxlen);
|
||||
ok = false;
|
||||
}
|
||||
|
||||
// Produce the return value.
|
||||
if (ok) {
|
||||
out = oss.str();
|
||||
return "";
|
||||
} else {
|
||||
out = "";
|
||||
return oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
bool decode(LuaStack &LS, LuaSlot out, std::string_view v) {
|
||||
lua_State *L = LS.state();
|
||||
|
||||
// Try to read a single value from the view.
|
||||
int top = lua_gettop(L);
|
||||
bool ok = decode_value(L, v);
|
||||
lua_replace(L, out.index());
|
||||
lua_settop(L, top);
|
||||
if (!ok) {
|
||||
LS.set(out, LuaToken("error"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special case: if the top level value is 'null', change
|
||||
// it to 'nil.'
|
||||
if (LS.istoken(out)) {
|
||||
LS.set(out, LuaNil);
|
||||
}
|
||||
|
||||
// There should be nothing left of the input text.
|
||||
if (v.size() > 0) {
|
||||
LS.set(out, LuaToken("error"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
|
||||
LuaDefine(json_encode, "data, indent, maxlen",
|
||||
"|Encode a lua data structure returning a json string."
|
||||
"|"
|
||||
"|Data is the value being encoded. Indent is a flag,"
|
||||
"|if it's true, then the json is indented nicely,"
|
||||
"|otherwise, it is packed tightly. Maxlen is the maximum"
|
||||
"|length in bytes of the encoded json string."
|
||||
"|"
|
||||
"|Usually, Lua data translates straightforwardly to json."
|
||||
"|However, there are a number of special cases to be"
|
||||
"|aware of:"
|
||||
"|"
|
||||
"|- Closures and threads cannot be encoded. These will"
|
||||
"| cause the encoder to abort."
|
||||
"|"
|
||||
"|- The numbers infinity and NAN cannot be encoded."
|
||||
"| Both of these will cause the encoder to abort."
|
||||
"|"
|
||||
"|- You must specify a size-limit to the encoded"
|
||||
"| string. Exceeding the size limit causes the"
|
||||
"| encoder to abort."
|
||||
"|"
|
||||
"|- Recursive data structures will cause the encoder to"
|
||||
"| loop infinitely until the size-limit is exceeded,"
|
||||
"| causing the encoder to abort."
|
||||
"|"
|
||||
"|- There is no way to represent math.huge or math.nan in"
|
||||
"| json. Encoding math.nan will cause the encoder to abort,"
|
||||
"| as expected. However, encoding math.huge will emit null,"
|
||||
"| which is probably not what you would expect."
|
||||
"|"
|
||||
"|- Lua tables cannot contain 'nil', but json objects and"
|
||||
"| arrays can contain null. If you want the encoder to"
|
||||
"| emit a json object or array containing null, you must"
|
||||
"| use token json.null to represent null."
|
||||
"|"
|
||||
"|- Json objects, like lua tables, are key-value stores."
|
||||
"| However, json objects can only have string keys. Our"
|
||||
"| encoder uses a workaround to transparently"
|
||||
"| allow mixing string and integer keys in json tables."
|
||||
"| See 'encoding difficult data' below."
|
||||
"|"
|
||||
"|- Json strings are required to be valid utf-8. Our encoder"
|
||||
"| uses a workaround to transparently allow the use of"
|
||||
"| arbitrary 8-bit-clean strings. See 'encoding difficult"
|
||||
"| data' below."
|
||||
"|"
|
||||
"|- Lua tables containing contiguous integer keys from 1-n are"
|
||||
"| autodetected to be json arrays. Empty tables are also"
|
||||
"| emitted as json arrays. All other tables are emitted"
|
||||
"| as json objects."
|
||||
"|"
|
||||
"|- You can force a table to be emitted as a json object"
|
||||
"| by putting the key-value pair table[json.object]=true"
|
||||
"| into the table. This special key is not emitted, but"
|
||||
"| it triggers json object mode. This is the only way"
|
||||
"| to emit an empty json object (a truly empty table is"
|
||||
"| emitted as a json array.)"
|
||||
"|"
|
||||
"|Encoding Difficult Data:"
|
||||
"|"
|
||||
"|Normally, json doesn't allow integer table keys, and it"
|
||||
"|doesn't allow strings that aren't valid utf-8. Our"
|
||||
"|json encoder and decoder, on the other hand, can"
|
||||
"|encode and decode integer table keys and 8-bit-clean"
|
||||
"|strings transparently. This is accomplished without"
|
||||
"|violating the json specification, by encoding such"
|
||||
"|values as utf-8 strings:"
|
||||
"|"
|
||||
"| '123' (encoded integer 123)"
|
||||
"| '=aGVsbG8=' (binary string encoded as base64)"
|
||||
"|"
|
||||
"|Those encodings start with utf-8 codepoint E000."
|
||||
"|This codepoint probably shows up in your text editor"
|
||||
"|as a little rectangle. When the decoder sees codepoint"
|
||||
"|E000 at the beginning of a string, it automatically"
|
||||
"|decodes the string back into its original form."
|
||||
"|"
|
||||
"|The one price for this behavior is that the encoder"
|
||||
"|cannot literally emit strings that start"
|
||||
"|with codepoint E000. If the encoder detects such a"
|
||||
"|string, it will emit it as a base64-encoded string."
|
||||
"|This should be uncommon, since codepoint E000 is"
|
||||
"|reserved."
|
||||
"|"
|
||||
"|Note that integers are only encoded when they are"
|
||||
"|used as table keys. Otherwise, numbers are emitted"
|
||||
"|straightforwardly."
|
||||
"|") {
|
||||
LuaArg data, indent, maxlen;
|
||||
LuaRet encoded;
|
||||
LuaStack LS(L, data, indent, maxlen, encoded);
|
||||
eng::string out;
|
||||
eng::string error = json::encode(LS, data, out, LS.ckboolean(indent), LS.ckint(maxlen));
|
||||
if (!error.empty()) {
|
||||
luaL_error(L, "%s", error.c_str());
|
||||
LS.set(encoded, LuaNil);
|
||||
return LS.result();
|
||||
} else {
|
||||
LS.set(encoded, out);
|
||||
return LS.result();
|
||||
}
|
||||
}
|
||||
|
||||
LuaDefine(json_decode, "data",
|
||||
"|Decode a json expression into a lua data structure."
|
||||
"|"
|
||||
"|Data that was generated by our own encoder is almost"
|
||||
"|8-bit clean. That includes difficult cases, like"
|
||||
"|binary strings, floating point numbers, and tables"
|
||||
"|with mixed string and integer keys. The exception"
|
||||
"|are the kinds of data that can't be encoded at all:"
|
||||
"|See doc(json.encode) for details about what"
|
||||
"|can and cannot be encoded."
|
||||
"|"
|
||||
"|Some json may contain 'null' inside objects and"
|
||||
"|arrays. Lua tables can't store nil, so instead, we"
|
||||
"|store the token json.null. If that's not what you"
|
||||
"|want, you can use json.stripnulls to strip out"
|
||||
"|the json.null values from a data structure and"
|
||||
"|replace them with nil."
|
||||
"|"
|
||||
"|") {
|
||||
LuaArg encoded;
|
||||
LuaRet data;
|
||||
LuaStack LS(L, encoded, data);
|
||||
std::string_view v = LS.ckstringview(encoded);
|
||||
bool ok = json::decode(LS, data, v);
|
||||
if (!ok) {
|
||||
luaL_error(L, "invalid json string.");
|
||||
}
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
// LuaDefine(base64_encode, "data", "") {
|
||||
// LuaArg str;
|
||||
// LuaRet ret;
|
||||
// LuaStack LS(L, str, ret);
|
||||
// eng::string cstr = LS.ckstring(str);
|
||||
// eng::ostringstream oss;
|
||||
// util::base64_encode(cstr, &oss);
|
||||
// LS.set(ret, oss.str());
|
||||
// return LS.result();
|
||||
// }
|
||||
|
||||
// LuaDefine(base64_decode, "data", "") {
|
||||
// LuaArg str;
|
||||
// LuaRet ret;
|
||||
// LuaStack LS(L, str, ret);
|
||||
// eng::string cstr = LS.ckstring(str);
|
||||
// eng::ostringstream oss;
|
||||
// util::base64_decode(cstr, &oss);
|
||||
// LS.set(ret, oss.str());
|
||||
// return LS.result();
|
||||
// }
|
||||
|
||||
34
luprex/core/cpp/json.hpp
Normal file
34
luprex/core/cpp/json.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// Encode lua data structure into json, and decode them again.
|
||||
//
|
||||
// See the doc(http.jsonencode) to read about limitations of the encoder.
|
||||
//
|
||||
#ifndef JSON_HPP
|
||||
#define JSON_HPP
|
||||
|
||||
#include "luastack.hpp"
|
||||
#include "wrap-string.hpp"
|
||||
#include <string_view>
|
||||
|
||||
namespace json {
|
||||
// Encode json.
|
||||
//
|
||||
// See doc(http.jsonencode) for a lot more information.
|
||||
//
|
||||
// Returns an error message. If the error message is an
|
||||
// empty string, then the encoding was successful.
|
||||
//
|
||||
eng::string encode(LuaStack &LS, LuaSlot in, eng::string &out, bool indent, int maxlen);
|
||||
|
||||
// Decode json.
|
||||
//
|
||||
// See doc(http.jsondecode) for a lot more information.
|
||||
//
|
||||
// The only error condition is syntactically invalid json.
|
||||
// In that case, we return false and set 'out' to the
|
||||
// token 'error'.
|
||||
//
|
||||
bool decode(LuaStack &LS, LuaSlot out, std::string_view in);
|
||||
}
|
||||
|
||||
#endif // JSON_HPP
|
||||
|
||||
@@ -219,6 +219,7 @@ public:
|
||||
HttpChannel &channel = http_client_channels_[request.request_id()];
|
||||
if (channel.channel_ == nullptr) {
|
||||
channel.channel_ = new_outgoing_channel(request.target());
|
||||
channel.method_ = request.method();
|
||||
channel.parsed_bytes_ = 0;
|
||||
request.send(channel.channel_->out());
|
||||
}
|
||||
@@ -235,7 +236,7 @@ public:
|
||||
response.fail(503, util::ss("Service Unavailable: ", channel.error()));
|
||||
} else {
|
||||
htchan.parsed_bytes_ = channel.in()->fill();
|
||||
response.parse_response(channel.in()->view(), channel.closed());
|
||||
response.parse_response(channel.in()->view(), channel.closed(), htchan.method_);
|
||||
}
|
||||
if (response.complete()) {
|
||||
response.set_request_id(pair.first);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <climits>
|
||||
|
||||
LuaSpecial LuaRegistry(LUA_REGISTRYINDEX);
|
||||
LuaNilMarker LuaNil;
|
||||
@@ -17,6 +18,15 @@ LuaFunctionReg::LuaFunctionReg(const char *n, const char *a, const char *d, bool
|
||||
All = this;
|
||||
}
|
||||
|
||||
LuaConstantReg::LuaConstantReg(const char *n, const char *d, LuaToken tokenvalue, lua_Number numbervalue) {
|
||||
name_ = n;
|
||||
docs_ = d;
|
||||
tokenvalue_ = tokenvalue;
|
||||
numbervalue_ = numbervalue;
|
||||
next_ = All;
|
||||
All = this;
|
||||
}
|
||||
|
||||
const LuaFunctionReg *LuaFunctionReg::lookup(lua_CFunction fn) {
|
||||
for (const LuaFunctionReg *r = All; r != 0; r = r->next_) {
|
||||
if (r->func_ == fn) {
|
||||
@@ -27,6 +37,20 @@ const LuaFunctionReg *LuaFunctionReg::lookup(lua_CFunction fn) {
|
||||
}
|
||||
|
||||
LuaFunctionReg *LuaFunctionReg::All;
|
||||
LuaConstantReg *LuaConstantReg::All;
|
||||
|
||||
|
||||
eng::string LuaToken::str() const {
|
||||
uint64_t token = (uint64_t)value;
|
||||
char buffer[9];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
unsigned char c = token;
|
||||
buffer[7-i] = c;
|
||||
token >>= 8;
|
||||
}
|
||||
buffer[8] = 0;
|
||||
return eng::string(buffer);
|
||||
}
|
||||
|
||||
static int panicf(lua_State *L) {
|
||||
const char *p = lua_tostring(L, -1);
|
||||
@@ -107,11 +131,23 @@ eng::string LuaStack::ckstring(LuaSlot s) const {
|
||||
return eng::string(str, len);
|
||||
}
|
||||
|
||||
std::string_view LuaStack::ckstringview(LuaSlot s) const {
|
||||
luaL_checktype(L_, s, LUA_TSTRING);
|
||||
size_t len;
|
||||
const char *str = lua_tolstring(L_, s, &len);
|
||||
return std::string_view(str, len);
|
||||
}
|
||||
|
||||
lua_State *LuaStack::ckthread(LuaSlot s) const {
|
||||
luaL_checktype(L_, s, LUA_TTHREAD);
|
||||
return lua_tothread(L_, s);
|
||||
}
|
||||
|
||||
LuaToken LuaStack::cktoken(LuaSlot s) const {
|
||||
luaL_checktype(L_, s, LUA_TLIGHTUSERDATA);
|
||||
return LuaToken(lua_touserdata(L_, s));
|
||||
}
|
||||
|
||||
void LuaStack::count_slots_finalize(int narg, int nvar, int nret) {
|
||||
narg_ = narg;
|
||||
nret_ = nret;
|
||||
|
||||
@@ -226,6 +226,40 @@ int LuaTypeTagValue(lua_State *L) { return 0; }
|
||||
#define LUA_TT_GLOBALDB 22
|
||||
#define LUA_TT_CLASS 23
|
||||
|
||||
// We use lightuserdata to store 'tokens': short
|
||||
// strings of 8 characters or less. These tokens
|
||||
// are useful as unique markers. The 8 characters
|
||||
// are packed into a uint64.
|
||||
|
||||
struct LuaToken {
|
||||
private:
|
||||
static constexpr uint64_t literal_to_token(const char *str) {
|
||||
uint64_t result = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
unsigned char c = *str;
|
||||
result = (result << 8) + c;
|
||||
if (*str) str++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public:
|
||||
uint64_t value;
|
||||
|
||||
template<class T>
|
||||
LuaToken(T arg) = delete;
|
||||
|
||||
constexpr LuaToken(const char *str) : value(literal_to_token(str)) {}
|
||||
LuaToken(uint64_t v) : value(v) {}
|
||||
LuaToken(void *v) : value((uint64_t)v) {}
|
||||
LuaToken() : value(0) {}
|
||||
|
||||
bool empty() const { return value == 0; }
|
||||
bool operator ==(const LuaToken &other) const { return value == other.value; }
|
||||
void *voidvalue() const { return (void*)value; }
|
||||
|
||||
eng::string str() const;
|
||||
};
|
||||
|
||||
class LuaStack : public eng::nevernew {
|
||||
private:
|
||||
int narg_;
|
||||
@@ -302,6 +336,7 @@ private:
|
||||
void push_any_value(lua_Integer s) const { lua_pushinteger(L_, s); }
|
||||
void push_any_value(lua_CFunction s) const { lua_pushcfunction(L_, s); }
|
||||
void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); }
|
||||
void push_any_value(LuaToken token) const { lua_pushlightuserdata(L_, (void*)(token.value)); }
|
||||
|
||||
// Push multiple values on the stack, in order, by type.
|
||||
template<typename T0, typename... T>
|
||||
@@ -354,6 +389,7 @@ public:
|
||||
bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; }
|
||||
bool isnil(LuaSlot s) const { return lua_type(L_, s) == LUA_TNIL; }
|
||||
bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s) != 0; }
|
||||
bool istoken(LuaSlot s) const { return lua_islightuserdata(L_, s) != 0; }
|
||||
|
||||
void checktable(LuaSlot index) const { checktype(index, LUA_TTABLE); }
|
||||
void checkstring(LuaSlot index) const { checktype(index, LUA_TSTRING); }
|
||||
@@ -362,13 +398,16 @@ public:
|
||||
void checkfunction(LuaSlot index) const { checktype(index, LUA_TFUNCTION); }
|
||||
void checkboolean(LuaSlot index) const { checktype(index, LUA_TBOOLEAN); }
|
||||
void checknil(LuaSlot index) const { checktype(index, LUA_TNIL); }
|
||||
void checktoken(LuaSlot index) const { checktype(index, LUA_TLIGHTUSERDATA); }
|
||||
|
||||
bool ckboolean(LuaSlot s) const;
|
||||
lua_Integer ckinteger(LuaSlot s) const;
|
||||
int ckint(LuaSlot s) const;
|
||||
lua_Number cknumber(LuaSlot s) const;
|
||||
eng::string ckstring(LuaSlot s) const;
|
||||
std::string_view ckstringview(LuaSlot s) const;
|
||||
lua_State *ckthread(LuaSlot s) const;
|
||||
LuaToken cktoken(LuaSlot s) const;
|
||||
|
||||
void clearmetatable(LuaSlot tab) const;
|
||||
void setmetatable(LuaSlot tab, LuaSlot mt) const;
|
||||
@@ -472,8 +511,29 @@ public:
|
||||
// Lua flagbits manipulation: visited bit.
|
||||
bool getvisited(LuaSlot tab) const;
|
||||
void setvisited(LuaSlot tab, bool visited) const;
|
||||
|
||||
// Return true if the int64 value can be stored as a lua number.
|
||||
static bool int64_storable(int64_t v) { return (v <= MAXINT) && (v >= -MAXINT); }
|
||||
};
|
||||
|
||||
class LuaConstantReg : public eng::nevernew {
|
||||
private:
|
||||
const char *name_;
|
||||
const char *docs_;
|
||||
LuaToken tokenvalue_;
|
||||
lua_Number numbervalue_;
|
||||
LuaConstantReg *next_;
|
||||
|
||||
public:
|
||||
static LuaConstantReg *All;
|
||||
LuaConstantReg(const char *name, const char *docs, LuaToken tokenvalue, lua_Number numbervalue);
|
||||
|
||||
const char *get_name() const { return name_; }
|
||||
const char *get_docs() const { return docs_; }
|
||||
LuaToken get_tokenvalue() const { return tokenvalue_; }
|
||||
lua_Number get_numbervalue() const { return numbervalue_; }
|
||||
LuaConstantReg *next() const { return next_; }
|
||||
};
|
||||
|
||||
class LuaFunctionReg : public eng::nevernew {
|
||||
private:
|
||||
@@ -498,6 +558,11 @@ public:
|
||||
void set_func(lua_CFunction fn) { func_ = fn; }
|
||||
};
|
||||
|
||||
#define LuaTokenConstant(name, tvalue, docs) \
|
||||
LuaConstantReg reg_##name(#name, docs, LuaToken(tvalue), 0);
|
||||
|
||||
#define LuaNumberConstant(name, nvalue, docs) \
|
||||
LuaConstantReg reg_##name(#name, docs, LuaToken(), nvalue);
|
||||
|
||||
#define LuaDefine(name, args, docs) \
|
||||
int lfn_##name(lua_State *L); \
|
||||
@@ -518,4 +583,5 @@ public:
|
||||
#define LuaStringify(x) #x
|
||||
#define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); }
|
||||
#define LuaAssertStrEq(L, x, y) { eng::string _s1_(x); eng::string _s2_(y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); }
|
||||
|
||||
#endif // LUASTACK_HPP
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -76,21 +76,110 @@
|
||||
#include "wrap-vector.hpp"
|
||||
#include "wrap-map.hpp"
|
||||
#include "wrap-string.hpp"
|
||||
|
||||
#include "wrap-bytell-hash-map.hpp"
|
||||
#include "util.hpp"
|
||||
#include "luastack.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
class PlaneMap;
|
||||
class PlaneTree;
|
||||
|
||||
class PlaneScan : public eng::nevernew {
|
||||
public:
|
||||
friend class PlaneMap;
|
||||
friend class PlaneTree;
|
||||
enum Shape { BOX, CYLINDER, SPHERE };
|
||||
private:
|
||||
// The plane to scan.
|
||||
eng::string plane_;
|
||||
|
||||
// The bounding box of the scan.
|
||||
util::XYZ center_, radius_;
|
||||
|
||||
// If you scan a cylinder or SPHERE, it actually
|
||||
// scans the bounding box first, then clips out
|
||||
// the parts that aren't correct.
|
||||
Shape shape_;
|
||||
|
||||
// When true, the items are sorted by ID.
|
||||
// WARNING: setting this to false can create
|
||||
// nondeterminism. Scans by lua should always be sorted.
|
||||
bool sorted_;
|
||||
|
||||
// The near ID, if nonzero, is either PREPENDED to the
|
||||
// results, or OMITTED from the results, depending on include_near_.
|
||||
int64_t near_;
|
||||
bool include_near_;
|
||||
|
||||
// If this is true, items on the nowhere plane are not scanned.
|
||||
bool omit_nowhere_;
|
||||
|
||||
public:
|
||||
void clear() {
|
||||
plane_ = "";
|
||||
shape_ = BOX;
|
||||
sorted_ = true;
|
||||
near_ = 0;
|
||||
include_near_ = false;
|
||||
omit_nowhere_ = false;
|
||||
radius_ = center_ = util::XYZ();
|
||||
}
|
||||
PlaneScan() { clear(); }
|
||||
|
||||
// Convert a lua table into a scan configuration.
|
||||
//
|
||||
// If there is an error in the configuration, returns an
|
||||
// error message, otherwise returns empty string.
|
||||
//
|
||||
// Caution: if this detects the configuration flag 'near',
|
||||
// then it stores the near ID. However, it doesn't fetch
|
||||
// the center and plane. It is the caller's responsibility
|
||||
// to check if 'near' has been set, and if so, store a center
|
||||
// and plane. This is admittedly ugly, but it eliminates
|
||||
// a dependency on the world module.
|
||||
//
|
||||
eng::string configure(const LuaStack &LS, LuaSlot slot);
|
||||
|
||||
void set_bbox_given_center_radius(const util::XYZ ¢er, float r) {
|
||||
set_center(center);
|
||||
set_radius(r);
|
||||
}
|
||||
|
||||
void set_whole_plane() {
|
||||
shape_ = BOX;
|
||||
center_ = util::XYZ();
|
||||
radius_.x = std::numeric_limits<float>::infinity();
|
||||
radius_.y = radius_.z = radius_.x;
|
||||
}
|
||||
|
||||
void set_center(const util::XYZ ¢er) { center_ = center; }
|
||||
void set_radius(const util::XYZ &radius) { radius_ = radius; }
|
||||
void set_radius(float f) { radius_.x = radius_.y = radius_.z = f; }
|
||||
void set_plane(std::string_view p) { plane_ = p; }
|
||||
void set_shape(Shape s) { shape_ = s; }
|
||||
void set_sorted(bool s) { sorted_ = s; }
|
||||
void set_near(int64_t id, bool inc) { near_ = id; include_near_ = inc; }
|
||||
void set_omit_nowhere(bool b) { omit_nowhere_ = b; }
|
||||
|
||||
int64_t near() const { return near_; }
|
||||
|
||||
// Reveal the contents of the PlaneScan as a string.
|
||||
eng::string debug_string() const;
|
||||
};
|
||||
|
||||
class PlaneItem : public eng::nevernew {
|
||||
friend class PlaneMap;
|
||||
|
||||
friend class PlaneTree;
|
||||
friend class PlaneMap;
|
||||
private:
|
||||
PlaneMap *pmap_;
|
||||
PlaneTree *tree_;
|
||||
PlaneItem *prev_;
|
||||
PlaneItem *next_;
|
||||
int64_t id_;
|
||||
eng::string plane_;
|
||||
float x_, y_, z_;
|
||||
int64_t id_;
|
||||
|
||||
public:
|
||||
PlaneItem();
|
||||
@@ -105,43 +194,51 @@ public:
|
||||
const float y() const { return y_; }
|
||||
const float z() const { return z_; }
|
||||
|
||||
void untrack();
|
||||
void track(PlaneMap *pmap);
|
||||
void untrack() { track(nullptr); }
|
||||
|
||||
void set_pos(const eng::string &plane, float x, float y, float z);
|
||||
void set_xyz(float x, float y, float z) { set_pos(plane_, x, y, z); }
|
||||
void set_xyz(float x, float y, float z);
|
||||
};
|
||||
|
||||
class PlaneMap : public eng::nevernew {
|
||||
friend class PlaneItem;
|
||||
private:
|
||||
using EltVec = eng::vector<PlaneItem *>;
|
||||
using Plane = eng::map<int64_t, EltVec>;
|
||||
eng::map<eng::string, Plane> planes_;
|
||||
void remove(const eng::string &plane, int64_t cell, PlaneItem *client);
|
||||
void insert(const eng::string &plane, int64_t cell, PlaneItem *client);
|
||||
|
||||
friend class PlaneTree;
|
||||
public:
|
||||
using IdVector = util::IdVector;
|
||||
|
||||
private:
|
||||
float default_radius_;
|
||||
eng::map<eng::string, std::unique_ptr<PlaneTree>> planes_;
|
||||
|
||||
public:
|
||||
// No special code is needed for construction or destruction.
|
||||
PlaneMap();
|
||||
~PlaneMap();
|
||||
|
||||
// Caution: scan_radius is not deterministically ordered unless sort=true.
|
||||
//
|
||||
// exclude_nowhere - if true, and plane="nowhere", nothing is scanned.
|
||||
// special - an ID that is considered special. If zero, there is no special ID.
|
||||
// omit - if true, remove the special ID from the list.
|
||||
// if false, prepend the special ID to the head of the list.
|
||||
//
|
||||
IdVector scan_radius(const eng::string &plane, float x, float y, float radius, bool exclude_nowhere, int64_t special, bool omit) const;
|
||||
IdVector scan_radius_unsorted(const eng::string &plane, float x, float y, float radius, bool exclude_nowhere, int64_t special, bool omit) const;
|
||||
// The 'insert' and 'remove' operators are inside class
|
||||
// PlaneItem. See PlaneItem::track and PlaneItem::untrack.
|
||||
|
||||
// Scan the PlaneMap for items, return their IDs.
|
||||
IdVector scan(const PlaneScan &s) const;
|
||||
|
||||
// Set the default radius for all planes.
|
||||
// Maybe we'll make it adaptive some day.
|
||||
void set_default_radius(float r) { default_radius_ = r; }
|
||||
|
||||
// Return a debug string for the specified plane.
|
||||
// This is for unit testing.
|
||||
eng::string tree_debug_string(const eng::string &plane);
|
||||
eng::string outliers_debug_string(const eng::string &plane);
|
||||
eng::string search_bboxes_debug_string(const PlaneScan &scan);
|
||||
eng::string scan_steps_debug_string(const PlaneScan &scan);
|
||||
|
||||
// Untrack all planeitems. This is for unit testing.
|
||||
void untrack_all();
|
||||
|
||||
private:
|
||||
// unit testing stuff.
|
||||
friend int lfn_unittests_planemap(lua_State *L);
|
||||
EltVec get_cell(const eng::string &plane, int64_t cell) const;
|
||||
int total_cells() const;
|
||||
void clear() { planes_.clear(); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "table.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
void atomic_print(LuaStack &LS, LuaSlot val, bool quote, std::ostream *os) {
|
||||
@@ -23,11 +24,15 @@ void atomic_print(LuaStack &LS, LuaSlot val, bool quote, std::ostream *os) {
|
||||
return;
|
||||
case LUA_TNUMBER: {
|
||||
double value = LS.cknumber(val);
|
||||
int64_t ivalue = int64_t(value);
|
||||
if (double(ivalue) == value) {
|
||||
(*os) << ivalue;
|
||||
if (std::isnan(value)) {
|
||||
(*os) << "nan";
|
||||
} else {
|
||||
(*os) << value;
|
||||
int64_t ivalue = int64_t(value);
|
||||
if (double(ivalue) == value) {
|
||||
(*os) << ivalue;
|
||||
} else {
|
||||
(*os) << value;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -38,6 +43,11 @@ void atomic_print(LuaStack &LS, LuaSlot val, bool quote, std::ostream *os) {
|
||||
(*os) << "<function>";
|
||||
return;
|
||||
}
|
||||
case LUA_TLIGHTUSERDATA: {
|
||||
LuaToken token = LS.cktoken(val);
|
||||
(*os) << "[" << token.str() << "]";
|
||||
return;
|
||||
}
|
||||
default:
|
||||
(*os) << "<" << lua_typename(LS.state(), tt) << ">";
|
||||
return;
|
||||
|
||||
@@ -56,8 +56,7 @@ LuaDefine(classname, "classtable", "get the class name from a class table") {
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
static void get_reg_name(const LuaFunctionReg *reg, std::string_view &classname, std::string_view &funcname) {
|
||||
std::string_view name(reg->get_name());
|
||||
static void get_reg_name(std::string_view name, std::string_view &classname, std::string_view &funcname) {
|
||||
size_t upos = name.find('_');
|
||||
if (upos == std::string_view::npos) {
|
||||
funcname = name;
|
||||
@@ -280,7 +279,7 @@ static void source_load_cfunctions(lua_State *L) {
|
||||
if ((func != nullptr) && (!r->get_sandbox())) {
|
||||
std::string_view classname;
|
||||
std::string_view funcname;
|
||||
get_reg_name(r, classname, funcname);
|
||||
get_reg_name(r->get_name(), classname, funcname);
|
||||
if (classname.empty()) {
|
||||
LS.getglobaltable(classobj);
|
||||
LS.rawset(classobj, funcname, func);
|
||||
@@ -293,6 +292,31 @@ static void source_load_cfunctions(lua_State *L) {
|
||||
LS.result();
|
||||
}
|
||||
|
||||
// Load all the 'LuaConstant' constants into the lua state.
|
||||
//
|
||||
static void source_load_cconstants(lua_State *L) {
|
||||
LuaVar classobj, value;
|
||||
LuaStack LS(L, classobj, value);
|
||||
for (auto r = LuaConstantReg::All; r != nullptr; r=r->next()) {
|
||||
if (r->get_tokenvalue().empty()) {
|
||||
LS.set(value, r->get_numbervalue());
|
||||
} else {
|
||||
LS.set(value, r->get_tokenvalue());
|
||||
}
|
||||
std::string_view classname;
|
||||
std::string_view funcname;
|
||||
get_reg_name(r->get_name(), classname, funcname);
|
||||
if (classname.empty()) {
|
||||
LS.getglobaltable(classobj);
|
||||
LS.rawset(classobj, funcname, value);
|
||||
} else {
|
||||
LS.makeclass(classobj, classname);
|
||||
LS.rawset(classobj, funcname, value);
|
||||
}
|
||||
}
|
||||
LS.result();
|
||||
}
|
||||
|
||||
// Run all the closures from the source database.
|
||||
//
|
||||
static eng::string source_load_lfunctions(lua_State *L) {
|
||||
@@ -341,18 +365,12 @@ static eng::string source_load_lfunctions(lua_State *L) {
|
||||
|
||||
eng::string SourceDB::rebuild() {
|
||||
lua_State *L = lua_state_;
|
||||
LuaVar mathclass;
|
||||
LuaStack LS(L, mathclass);
|
||||
LuaVar mathclass, httpclass, jsonnull;
|
||||
LuaStack LS(L, mathclass, httpclass, jsonnull);
|
||||
source_clear_globals(L);
|
||||
source_load_cfunctions(L);
|
||||
source_load_cconstants(L);
|
||||
eng::string errs = source_load_lfunctions(L);
|
||||
|
||||
// A few builtin constants. These are hardwired.
|
||||
LS.makeclass(mathclass, "math");
|
||||
LS.rawset(mathclass, "pi", M_PI);
|
||||
LS.rawset(mathclass, "huge", HUGE_VAL);
|
||||
LS.rawset(mathclass, "maxint", LuaStack::MAXINT);
|
||||
|
||||
LS.result();
|
||||
return errs;
|
||||
}
|
||||
@@ -466,7 +484,7 @@ void SourceDB::register_lua_builtins() {
|
||||
for (auto reg = LuaFunctionReg::All; reg != nullptr; reg=reg->next()) {
|
||||
std::string_view funcname;
|
||||
std::string_view classname;
|
||||
get_reg_name(reg, classname, funcname);
|
||||
get_reg_name(reg->get_name(), classname, funcname);
|
||||
if (classname.empty()) {
|
||||
LS.getglobaltable(classtab);
|
||||
} else {
|
||||
@@ -524,7 +542,7 @@ eng::string SourceDB::function_docs(const LuaStack &LS0, LuaSlot fn) {
|
||||
}
|
||||
std::string_view classname;
|
||||
std::string_view funcname;
|
||||
get_reg_name(reg, classname, funcname);
|
||||
get_reg_name(reg->get_name(), classname, funcname);
|
||||
eng::ostringstream oss;
|
||||
util::StringVec docs = util::split_docstring(reg->get_docs());
|
||||
oss << "function ";
|
||||
@@ -747,6 +765,11 @@ LuaDefineBuiltin(math_sqrt, "x", "return the square root of x");
|
||||
LuaDefineBuiltin(math_tan, "x", "return the tangent of x in radians");
|
||||
LuaDefineBuiltin(math_tanh, "x", "return the hyperbolic tangent of x in radians");
|
||||
LuaSandboxBuiltin(math_log10, "", "");
|
||||
LuaNumberConstant(math_pi, M_PI, "");
|
||||
LuaNumberConstant(math_huge, HUGE_VAL, "");
|
||||
LuaNumberConstant(math_nan, NAN, "");
|
||||
LuaNumberConstant(math_maxint, LuaStack::MAXINT, "");
|
||||
|
||||
// math.random and math.randomseed are in world-accessor.cpp, because
|
||||
// generating random numbers must manipulate global state which is
|
||||
// stored in the world model.
|
||||
|
||||
@@ -65,8 +65,10 @@ bool valid_double(string_view value) {
|
||||
|
||||
int64_t to_int64(string_view value, int64_t errval) {
|
||||
int64_t result;
|
||||
const char *last = value.data() + value.size();
|
||||
auto r = std::from_chars(value.data(), last, result, 10);
|
||||
const char *p = value.data();
|
||||
const char *last = p + value.size();
|
||||
if ((p < last) && (*p == '+')) p++;
|
||||
auto r = std::from_chars(p, last, result, 10);
|
||||
if (r.ec != std::errc()) return errval;
|
||||
if (r.ptr != last) return errval;
|
||||
return result;
|
||||
@@ -74,6 +76,7 @@ int64_t to_int64(string_view value, int64_t errval) {
|
||||
|
||||
uint64_t to_hex64(string_view value, uint64_t errval) {
|
||||
uint64_t result;
|
||||
if (sv::zfront(value) == '-') return errval;
|
||||
const char *last = value.data() + value.size();
|
||||
auto r = std::from_chars(value.data(), last, result, 16);
|
||||
if (r.ec != std::errc()) return errval;
|
||||
@@ -204,6 +207,15 @@ string_view read_to_line(string_view &source) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool read_prefix(string_view &source, string_view prefix) {
|
||||
if (0 == source.compare(0, prefix.size(), prefix)) {
|
||||
source.remove_prefix(prefix.size());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string_view read_to_space(string_view &source) {
|
||||
size_t pos1 = 0;
|
||||
while ((pos1 < source.size()) && (!ascii_isspace(source[pos1]))) {
|
||||
@@ -243,57 +255,119 @@ string_view read_ascii_identifier(string_view &source) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string_view read_number(string_view &source, bool plus, bool minus, bool dec, bool exp) {
|
||||
const char *p = source.data();
|
||||
const char *l = p + source.size();
|
||||
if (p == l) return source.substr(0, 0);
|
||||
char sign = *p;
|
||||
if (sign == '+') {
|
||||
if (!plus) return source.substr(0, 0);
|
||||
p++;
|
||||
}
|
||||
if (sign == '-') {
|
||||
if (!minus) return source.substr(0, 0);
|
||||
p++;
|
||||
}
|
||||
if (p == l) return source.substr(0, 0);
|
||||
bool have_digits = false;
|
||||
while ((p < l) && (ascii_isdigit(*p))) {
|
||||
have_digits = true;
|
||||
p++;
|
||||
}
|
||||
if ((p < l) && dec && (*p == '.')) {
|
||||
p++;
|
||||
while ((p < l) && (ascii_isdigit(*p))) {
|
||||
have_digits = true;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
if (!have_digits) return source.substr(0, 0);
|
||||
if ((p < l) && exp && ((*p == 'e')||(*p == 'E'))) {
|
||||
p++;
|
||||
if ((p < l) && ((*p == '+') || (*p == '-'))) {
|
||||
p++;
|
||||
}
|
||||
bool have_exp = false;
|
||||
while ((p < l) && (ascii_isdigit(*p))) {
|
||||
have_exp = true;
|
||||
p++;
|
||||
}
|
||||
if (!have_exp) return source.substr(0, 0);
|
||||
}
|
||||
string_view result = source.substr(0, p - source.data());
|
||||
source.remove_prefix(result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t read_ascii_char(string_view &source) {
|
||||
if (source.empty()) return -1;
|
||||
int32_t result = source.front();
|
||||
source.remove_prefix(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t read_codepoint_utf8(string_view &source) {
|
||||
size_t size = source.size();
|
||||
if (size == 0) return -1;
|
||||
const unsigned char *bytes = (const unsigned char *)source.data();
|
||||
int codepoint;
|
||||
size_t seqlen;
|
||||
if ((bytes[0] & 0x80) == 0x00) {
|
||||
// U+0000 to U+007F
|
||||
codepoint = (bytes[0] & 0x7F);
|
||||
seqlen = 1;
|
||||
} else if ((bytes[0] & 0xE0) == 0xC0) {
|
||||
// U+0080 to U+07FF
|
||||
codepoint = (bytes[0] & 0x1F);
|
||||
seqlen = 2;
|
||||
} else if ((bytes[0] & 0xF0) == 0xE0) {
|
||||
// U+0800 to U+FFFF
|
||||
codepoint = (bytes[0] & 0x0F);
|
||||
seqlen = 3;
|
||||
} else if ((bytes[0] & 0xF8) == 0xF0) {
|
||||
// U+10000 to U+10FFFF
|
||||
codepoint = (bytes[0] & 0x07);
|
||||
seqlen = 4;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (seqlen > size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < seqlen; ++i) {
|
||||
if ((bytes[i] & 0xC0) != 0x80) return -1;
|
||||
codepoint = (codepoint << 6) | (bytes[i] & 0x3F);
|
||||
}
|
||||
|
||||
if ((codepoint > 0x10FFFF) ||
|
||||
((codepoint >= 0xD800) && (codepoint <= 0xDFFF)) ||
|
||||
((codepoint <= 0x007F) && (seqlen != 1)) ||
|
||||
((codepoint >= 0x0080) && (codepoint <= 0x07FF) && (seqlen != 2)) ||
|
||||
((codepoint >= 0x0800) && (codepoint <= 0xFFFF) && (seqlen != 3)) ||
|
||||
((codepoint >= 0x10000) && (codepoint <= 0x1FFFFF) && (seqlen != 4))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
source.remove_prefix(seqlen);
|
||||
return codepoint;
|
||||
}
|
||||
|
||||
bool valid_utf8(string_view s)
|
||||
{
|
||||
const unsigned char *bytes = (const unsigned char *)s.data();
|
||||
const unsigned char *tail = bytes + s.size();
|
||||
unsigned int codepoint;
|
||||
int seqlen;
|
||||
|
||||
while (bytes < tail) {
|
||||
if ((bytes[0] & 0x80) == 0x00) {
|
||||
// U+0000 to U+007F
|
||||
codepoint = (bytes[0] & 0x7F);
|
||||
seqlen = 1;
|
||||
} else if ((bytes[0] & 0xE0) == 0xC0) {
|
||||
// U+0080 to U+07FF
|
||||
codepoint = (bytes[0] & 0x1F);
|
||||
seqlen = 2;
|
||||
} else if ((bytes[0] & 0xF0) == 0xE0) {
|
||||
// U+0800 to U+FFFF
|
||||
codepoint = (bytes[0] & 0x0F);
|
||||
seqlen = 3;
|
||||
} else if ((bytes[0] & 0xF8) == 0xF0) {
|
||||
// U+10000 to U+10FFFF
|
||||
codepoint = (bytes[0] & 0x07);
|
||||
seqlen = 4;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes + seqlen > tail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 1; i < seqlen; ++i) {
|
||||
if ((bytes[i] & 0xC0) != 0x80) return false;
|
||||
codepoint = (codepoint << 6) | (bytes[i] & 0x3F);
|
||||
}
|
||||
|
||||
if ((codepoint > 0x10FFFF) ||
|
||||
((codepoint >= 0xD800) && (codepoint <= 0xDFFF)) ||
|
||||
((codepoint <= 0x007F) && (seqlen != 1)) ||
|
||||
((codepoint >= 0x0080) && (codepoint <= 0x07FF) && (seqlen != 2)) ||
|
||||
((codepoint >= 0x0800) && (codepoint <= 0xFFFF) && (seqlen != 3)) ||
|
||||
((codepoint >= 0x10000) && (codepoint <= 0x1FFFFF) && (seqlen != 4))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes += seqlen;
|
||||
while (!s.empty()) {
|
||||
int32_t codepoint = read_codepoint_utf8(s);
|
||||
if (codepoint < 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid_number(string_view s, bool plus, bool minus, bool dec, bool exp) {
|
||||
read_number(s, plus, minus, dec, exp);
|
||||
return s.empty();
|
||||
}
|
||||
|
||||
} // namespace sv
|
||||
|
||||
|
||||
@@ -334,6 +408,8 @@ void quote_string(const eng::string &s, std::ostream *os) {
|
||||
(*os) << (usesinglequote ? "\"" : "\\\"");
|
||||
} else if (c == '\'') {
|
||||
(*os) << (usesinglequote ? "\\'" : "'");
|
||||
} else if (c == '\\') {
|
||||
(*os) << "\\\\";
|
||||
} else {
|
||||
(*os) << c;
|
||||
}
|
||||
@@ -344,7 +420,7 @@ void quote_string(const eng::string &s, std::ostream *os) {
|
||||
case '\t': (*os) << "\\t"; break;
|
||||
case '\r': (*os) << "\\r"; break;
|
||||
default:
|
||||
(*os) << "\\" << std::setfill('0') << std::setw(3) << value;
|
||||
(*os) << "\\" << dec.width(3).fill('0').val(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -352,6 +428,52 @@ void quote_string(const eng::string &s, std::ostream *os) {
|
||||
(*os) << (usesinglequote ? '\'' : '"');
|
||||
}
|
||||
|
||||
void base64_encode(std::string_view str, std::ostream *oss) {
|
||||
const char *encode_tab =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
const char *s = str.data();
|
||||
size_t size = str.size();
|
||||
for (size_t i = 0; i < size; i += 3) {
|
||||
uint32_t block = ((unsigned char)(s[i])) << 16;
|
||||
if (i + 1 < size) block |= ((unsigned char)(s[i + 1])) << 8;
|
||||
if (i + 2 < size) block |= ((unsigned char)(s[i + 2]));
|
||||
(*oss) << encode_tab[(block>>18)&0x3F];
|
||||
(*oss) << encode_tab[(block>>12)&0x3F];
|
||||
(*oss) << ((i + 1 < size) ? encode_tab[(block>>6)&0x3F] : '=');
|
||||
(*oss) << ((i + 2 < size) ? encode_tab[(block>>0)&0x3F] : '=');
|
||||
}
|
||||
}
|
||||
|
||||
bool base64_decode(std::string_view str, std::ostream *oss) {
|
||||
uint32_t chunk = 0;
|
||||
int fill = 0;
|
||||
int skip = 0;
|
||||
bool clean = true;
|
||||
for (int i = 0; i < int(str.size()); i++) {
|
||||
char c = str[i];
|
||||
uint32_t value;
|
||||
|
||||
if ((c >= 'A') && (c <= 'Z')) value = c - 'A';
|
||||
else if ((c >= 'a') && (c <= 'z')) value = c - 'a' + 26;
|
||||
else if ((c >= '0') && (c <= '9')) value = c - '0' + 52;
|
||||
else if (c == '+') value = 62;
|
||||
else if (c == '/') value = 63;
|
||||
else if (c == '=') { value = 0; skip ++; }
|
||||
else { clean=false; continue; }
|
||||
|
||||
chunk = (chunk << 6) | value;
|
||||
fill ++;
|
||||
if (fill == 4) {
|
||||
oss->put((chunk>>16) & 0xFF);
|
||||
if (skip < 2) oss->put((chunk>>8) & 0xFF);
|
||||
if (skip < 1) oss->put(chunk & 0xFF);
|
||||
chunk = 0; fill = 0; skip = 0;
|
||||
}
|
||||
}
|
||||
if (fill != 0) clean = false;
|
||||
return clean;
|
||||
}
|
||||
|
||||
IdVector id_vector_create(int64_t id1, int64_t id2, int64_t id3, int64_t id4) {
|
||||
IdVector result;
|
||||
if (id1 >= 0) result.push_back(id1);
|
||||
@@ -361,14 +483,18 @@ IdVector id_vector_create(int64_t id1, int64_t id2, int64_t id3, int64_t id4) {
|
||||
return result;
|
||||
}
|
||||
|
||||
eng::string id_vector_debug_string(const IdVector &idv) {
|
||||
eng::ostringstream oss;
|
||||
void print_id_vector(const IdVector &idv, std::ostream *os) {
|
||||
bool first = true;
|
||||
for (int64_t id : idv) {
|
||||
if (!first) oss << ",";
|
||||
oss << id;
|
||||
if (!first) (*os) << ",";
|
||||
(*os) << id;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
eng::string id_vector_debug_string(const IdVector &idv) {
|
||||
eng::ostringstream oss;
|
||||
print_id_vector(idv, &oss);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
@@ -406,8 +532,7 @@ HashValue hash_id_vector(const IdVector &idv) {
|
||||
|
||||
eng::string hash_to_hex(const HashValue &hv) {
|
||||
eng::ostringstream oss;
|
||||
oss << std::hex << std::setw(16) << std::setfill('0') << hv.first;
|
||||
oss << std::hex << std::setw(16) << std::setfill('0') << hv.second;
|
||||
oss << hex64.val(hv.first) << hex64.val(hv.second);
|
||||
return oss.str();
|
||||
}
|
||||
static inline uint64_t Rot64(uint64_t x, int k)
|
||||
@@ -530,6 +655,52 @@ eng::string toupper(eng::string input) {
|
||||
return input;
|
||||
}
|
||||
|
||||
static void buffer_codepoint_utf8(int32_t scp, char *buffer) {
|
||||
uint32_t cp = (uint32_t)scp;
|
||||
unsigned char *c = (unsigned char *)buffer;
|
||||
if (cp <= 0x7F) {
|
||||
c[0] = cp;
|
||||
c[1] = 0;
|
||||
}
|
||||
else if (cp <= 0x7FF) {
|
||||
c[0] = (cp>>6)+192;
|
||||
c[1] = (cp&63)+128;
|
||||
c[2] = 0;
|
||||
}
|
||||
else if (cp <= 0xFFFF) {
|
||||
if (0xd800 <= cp && cp <= 0xdfff) {
|
||||
c[0] = 0;
|
||||
} else {
|
||||
c[0] = (cp>>12)+224;
|
||||
c[1] = ((cp>>6)&63)+128;
|
||||
c[2] = (cp&63)+128;
|
||||
c[3] = 0;
|
||||
}
|
||||
}
|
||||
else if (cp <= 0x10FFFF) {
|
||||
c[0] = (cp>>18)+240;
|
||||
c[1] = ((cp>>12)&63)+128;
|
||||
c[2] = ((cp>>6)&63)+128;
|
||||
c[3] = (cp&63)+128;
|
||||
c[4] = 0;
|
||||
} else {
|
||||
c[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
eng::string get_codepoint_utf8(uint32_t cp) {
|
||||
char buffer[5];
|
||||
buffer_codepoint_utf8(cp, buffer);
|
||||
return eng::string(buffer);
|
||||
}
|
||||
|
||||
bool write_codepoint_utf8(int32_t cp, std::ostream *s) {
|
||||
char buffer[5];
|
||||
buffer_codepoint_utf8(cp, buffer);
|
||||
(*s) << buffer;
|
||||
return buffer[0] != 0;
|
||||
}
|
||||
|
||||
double distance_squared(double x1, double y1, double x2, double y2) {
|
||||
double dx = x1 - x2;
|
||||
double dy = y1 - y2;
|
||||
@@ -549,35 +720,20 @@ eng::string XYZ::debug_string() const {
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
} // namespace util
|
||||
|
||||
std::ostream &operator<<(std::ostream &oss, const util::hex64 &v) {
|
||||
oss << "0x" << std::setw(16) << std::setfill('0') << std::hex;
|
||||
return oss;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &oss, const util::hex32 &v) {
|
||||
oss << "0x" << std::setw(8) << std::setfill('0') << std::hex;
|
||||
return oss;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &oss, const util::hex16 &v) {
|
||||
oss << "0x" << std::setw(4) << std::setfill('0') << std::hex;
|
||||
return oss;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &oss, const util::hex8 &v) {
|
||||
oss << "0x" << std::setw(2) << std::setfill('0') << std::hex;
|
||||
return oss;
|
||||
static std::string_view read_number_x(const char *p, bool plus, bool minus, bool dec, bool exp) {
|
||||
std::string_view source = p;
|
||||
return sv::read_number(source, plus, minus, dec, exp);
|
||||
}
|
||||
|
||||
LuaDefine(unittests_util, "", "some unit tests") {
|
||||
// test str_to_int64, str_to_double
|
||||
LuaAssert(L, sv::to_int64("123") == 123);
|
||||
LuaAssert(L, sv::to_int64("123.4") == INT64_MIN);
|
||||
LuaAssert(L, sv::to_int64("12ab") == INT64_MIN);
|
||||
LuaAssert(L, sv::to_int64("") == INT64_MIN);
|
||||
LuaAssert(L, sv::to_int64("123.4") == INT64_MAX);
|
||||
LuaAssert(L, sv::to_int64("12ab") == INT64_MAX);
|
||||
LuaAssert(L, sv::to_int64("") == INT64_MAX);
|
||||
LuaAssert(L, sv::to_double("123.5") == 123.5);
|
||||
LuaAssert(L, std::isnan(sv::to_double("12ab")));
|
||||
LuaAssert(L, std::isnan(sv::to_double("")));
|
||||
@@ -689,6 +845,20 @@ LuaDefine(unittests_util, "", "some unit tests") {
|
||||
LuaAssert(L, util::hash_to_double(0x1000000000000000) == 1.0/16.0);
|
||||
LuaAssert(L, util::hash_to_double(0x7000000000000000) == 7.0/16.0);
|
||||
LuaAssert(L, util::hash_to_double(0xF000000000000000) == 15.0/16.0);
|
||||
|
||||
// Test read_number allowing everything.
|
||||
LuaAssert(L, read_number_x("123x", true, true, true, true) == "123");
|
||||
LuaAssert(L, read_number_x("123.3x", true, true, true, true) == "123.3");
|
||||
LuaAssert(L, read_number_x("123.x", true, true, true, true) == "123.");
|
||||
LuaAssert(L, read_number_x("123..x", true, true, true, true) == "123.");
|
||||
LuaAssert(L, read_number_x("-123x", true, true, true, true) == "-123");
|
||||
LuaAssert(L, read_number_x("+123x", true, true, true, true) == "+123");
|
||||
LuaAssert(L, read_number_x("+-123x", true, true, true, true) == "");
|
||||
LuaAssert(L, read_number_x("-123.02e05x", true, true, true, true) == "-123.02e05");
|
||||
LuaAssert(L, read_number_x("-123e-5x", true, true, true, true) == "-123e-5");
|
||||
LuaAssert(L, read_number_x("-123e+5x", true, true, true, true) == "-123e+5");
|
||||
LuaAssert(L, read_number_x("-123e+x", true, true, true, true) == "");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,13 @@
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
<<<<<<< HEAD
|
||||
#include <limits>
|
||||
=======
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <iomanip>
|
||||
>>>>>>> 4e754377ee4922940c37a20710314befeff0d84e
|
||||
|
||||
#include "luastack.hpp"
|
||||
#include "spookyv2.hpp"
|
||||
@@ -60,9 +66,17 @@ bool valid_double(string_view v);
|
||||
bool valid_int64(string_view v);
|
||||
bool valid_hex64(string_view v);
|
||||
|
||||
// Parse numbers as int32, int64, or double. Returns errval on failure.
|
||||
// Convert strings to numbers. Returns errval on failure.
|
||||
//
|
||||
// The integer parser accepts a sequence of digits,
|
||||
// with or without a + or - sign. The hex parser
|
||||
// does not allow a + or - sign. For both the int64
|
||||
// and hex64 parser, it is a failure if the number
|
||||
// does not fit in 64 bits. The double parser does
|
||||
// not accept the strings 'nan' or 'inf'.
|
||||
//
|
||||
double to_double(string_view v, double errval = std::numeric_limits<double>::quiet_NaN());
|
||||
int64_t to_int64(string_view v, int64_t errval = std::numeric_limits<int64_t>::min());
|
||||
int64_t to_int64(string_view v, int64_t errval = std::numeric_limits<int64_t>::max());
|
||||
uint64_t to_hex64(string_view v, uint64_t errval = std::numeric_limits<uint64_t>::max());
|
||||
|
||||
// Trim whitspace from a string_view.
|
||||
@@ -116,6 +130,13 @@ string_view read_to_sep(string_view &source, char sep);
|
||||
//
|
||||
string_view read_to_line(string_view &source);
|
||||
|
||||
// Read a prefix string from a string_view.
|
||||
//
|
||||
// Returns false if the string view doesn't start with
|
||||
// the specified prefix.
|
||||
//
|
||||
bool read_prefix(string_view &source, string_view prefix);
|
||||
|
||||
// Read from a string_view until whitespace is reached.
|
||||
//
|
||||
// If there's any whitespace in the source, returns the text
|
||||
@@ -137,9 +158,46 @@ string_view read_nbytes(string_view &source, int nbytes);
|
||||
//
|
||||
string_view read_ascii_identifier(string_view &source);
|
||||
|
||||
// Read a number from a string view
|
||||
//
|
||||
// This is basically a regex pattern matching routine
|
||||
// hardwired with the regex for numbers. You must
|
||||
// specify which of the following parts of the regex
|
||||
// are allowed or not:
|
||||
//
|
||||
// * plus sign
|
||||
// * minus sign
|
||||
// * decimal point
|
||||
// * scientific notation exponents
|
||||
//
|
||||
// Returns the number as a string_view. There is
|
||||
// no guarantee that the number is small enough to
|
||||
// fit into any particular number of bits. This
|
||||
// always uses base 10.
|
||||
//
|
||||
std::string_view read_number(string_view &source, bool plus, bool minus, bool dec, bool exp);
|
||||
|
||||
// Read an ascii character from a string.
|
||||
//
|
||||
// Returns -1 if the string is empty.
|
||||
//
|
||||
int32_t read_ascii_char(string_view &source);
|
||||
|
||||
// Read a UTF8 codepoint from a string_view.
|
||||
//
|
||||
// If the next thing in the string_view isn't a valid
|
||||
// codepoint, returns -1 and doesn't update the view.
|
||||
//
|
||||
int32_t read_codepoint_utf8(string_view &source);
|
||||
|
||||
// Return true if the string is valid utf-8.
|
||||
bool valid_utf8(string_view s);
|
||||
|
||||
// Return true if the number conforms to the spec.
|
||||
// See read_number for more information.
|
||||
//
|
||||
bool valid_number(string_view v, bool plus, bool minus, bool dec, bool exp);
|
||||
|
||||
} // namespace sv
|
||||
|
||||
namespace util {
|
||||
@@ -176,9 +234,22 @@ double profiling_clock();
|
||||
// Output a string to a stream using Lua string escaping and quoting.
|
||||
void quote_string(const eng::string &str, std::ostream *os);
|
||||
|
||||
// base64 encode.
|
||||
void base64_encode(std::string_view v, std::ostream *oss);
|
||||
|
||||
// base64 decode.
|
||||
//
|
||||
// Returns true if the base64 was 'clean' base64, as
|
||||
// opposed to base64 with extraneous characters.
|
||||
//
|
||||
bool base64_decode(std::string_view v, std::ostream *oss);
|
||||
|
||||
// ID vector quick create.
|
||||
IdVector id_vector_create(int64_t id1=-1, int64_t id2=-1, int64_t id3=-1, int64_t id4=-1);
|
||||
|
||||
// Print an ID vector to a stream.
|
||||
void print_id_vector(const IdVector &idv, std::ostream *os);
|
||||
|
||||
// ID vector debug string.
|
||||
eng::string id_vector_debug_string(const IdVector &idv);
|
||||
|
||||
@@ -198,6 +269,16 @@ eng::string hash_to_hex(const HashValue &hash);
|
||||
// This is a good hash, but not cryptographically good.
|
||||
uint64_t hash_ints(uint64_t n1, uint64_t n2, uint64_t n3, uint64_t n4);
|
||||
|
||||
// Hash a single 64-bit integer.
|
||||
// This is a good hash, but not cryptographically good.
|
||||
// Published by David Stafford in his article 'Better Bit Mixing'.
|
||||
inline uint64_t hash_int(uint64_t x) {
|
||||
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
|
||||
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
|
||||
x = x ^ (x >> 31);
|
||||
return x;
|
||||
}
|
||||
|
||||
// Convert a 64-bit hash value into a floating point number between 0 and 1.
|
||||
double hash_to_double(uint64_t hash);
|
||||
|
||||
@@ -220,6 +301,14 @@ eng::string repeat_string(const eng::string &a, int n);
|
||||
eng::string tolower(eng::string input);
|
||||
eng::string toupper(eng::string input);
|
||||
|
||||
// Convert a codepoint number into a utf8 string.
|
||||
// If the codepoint is invalid, returns empty string.
|
||||
eng::string get_codepoint_utf8(int32_t cp);
|
||||
|
||||
// Write a codepoint in utf8 to a stream.
|
||||
// If the codepoint is invalid, writes nothing and returns false.
|
||||
bool write_codepoint_utf8(int32_t cp, std::ostream *out);
|
||||
|
||||
// Calculate distance between two points
|
||||
double distance_squared(double x1, double y1, double x2, double y2);
|
||||
|
||||
@@ -247,15 +336,12 @@ struct XYZ {
|
||||
XYZ(float ix, float iy, float iz) { x=ix; y=iy; z=iz; }
|
||||
bool operator ==(const XYZ &o) const { return x==o.x && y == o.y && z==o.z; }
|
||||
bool operator !=(const XYZ &o) const { return x!=o.x || y != o.y || z!=o.z; }
|
||||
XYZ operator -(const XYZ &o) const { return XYZ(x-o.x, y-o.y, z-o.z); }
|
||||
XYZ operator +(const XYZ &o) const { return XYZ(x+o.x, y+o.y, z+o.z); }
|
||||
XYZ operator *(float scale) const { return XYZ(x*scale, y*scale, z*scale); }
|
||||
eng::string debug_string() const;
|
||||
};
|
||||
|
||||
// These are formatting directives that can be sent to a std::ostream.
|
||||
class hex64 {};
|
||||
class hex32 {};
|
||||
class hex16 {};
|
||||
class hex8 {};
|
||||
|
||||
class NullStreamBuffer : public std::streambuf
|
||||
{
|
||||
public:
|
||||
@@ -265,24 +351,69 @@ public:
|
||||
// send_to_stream: send all arguments to the specified stream.
|
||||
inline void send_to_stream(std::ostream &os) {}
|
||||
template <class ARG, class... REST>
|
||||
inline void send_to_stream(std::ostream &os, ARG arg, REST & ... rest) {
|
||||
inline void send_to_stream(std::ostream &os, const ARG &arg, const REST & ... rest) {
|
||||
os << arg;
|
||||
send_to_stream(os, rest...);
|
||||
}
|
||||
|
||||
// ss: convert all arguments to a string by sending them to a stringstream.
|
||||
template <class... ARGS>
|
||||
inline eng::string ss(ARGS & ... args) {
|
||||
inline eng::string ss(const ARGS & ... args) {
|
||||
eng::ostringstream oss;
|
||||
send_to_stream(oss, args...);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// A better API than std::setfill, std::hex, std::setw, std::setprecision
|
||||
//
|
||||
// Usage examples:
|
||||
// std::cout << util::hex.width(5).fill('0').val(123)
|
||||
// std::cout << util::dec.fill('$').precision(val(123)
|
||||
//
|
||||
// The reason that other API is bad is that it can leave std::cout
|
||||
// in an unpredictable state. This API always leaves the stream clean.
|
||||
//
|
||||
template <class VALUE>
|
||||
class FormattedNumber {
|
||||
public:
|
||||
VALUE value_;
|
||||
bool hex_;
|
||||
int width_;
|
||||
char fill_;
|
||||
int precision_;
|
||||
|
||||
constexpr FormattedNumber(VALUE v, bool h, int w, char f, int p)
|
||||
: value_(v), hex_(h), width_(w), fill_(f), precision_(p) {}
|
||||
|
||||
constexpr FormattedNumber width(int w) const { return FormattedNumber(value_, hex_, w, fill_, precision_); }
|
||||
constexpr FormattedNumber fill(char f) const { return FormattedNumber(value_, hex_, width_, f, precision_); }
|
||||
constexpr FormattedNumber precision(int p) const { return FormattedNumber(value_, hex_, width_, fill_, p); }
|
||||
|
||||
template <class NVALUE>
|
||||
constexpr FormattedNumber val(NVALUE v) const { return FormattedNumber(v, hex_, width_, fill_, precision_); }
|
||||
};
|
||||
|
||||
constexpr auto hex = FormattedNumber<int>(0, true, 0, '0', 6);
|
||||
constexpr auto hex8 = FormattedNumber<int>(0, true, 2, '0', 6);
|
||||
constexpr auto hex16 = FormattedNumber<int>(0, true, 4, '0', 6);
|
||||
constexpr auto hex32 = FormattedNumber<int>(0, true, 8, '0', 6);
|
||||
constexpr auto hex64 = FormattedNumber<int>(0, true, 16, '0', 6);
|
||||
constexpr auto dec = FormattedNumber<int>(0, false, 0, ' ', 6);
|
||||
|
||||
} // namespace util
|
||||
|
||||
std::ostream &operator<<(std::ostream &oss, const util::hex64 &v);
|
||||
std::ostream &operator<<(std::ostream &oss, const util::hex32 &v);
|
||||
std::ostream &operator<<(std::ostream &oss, const util::hex16 &v);
|
||||
std::ostream &operator<<(std::ostream &oss, const util::hex8 &v);
|
||||
template<class VALUE>
|
||||
inline std::ostream &operator<<(std::ostream &oss, util::FormattedNumber<VALUE> n) {
|
||||
if (n.hex_) oss << std::hex;
|
||||
else oss << std::dec;
|
||||
oss << std::setprecision(n.precision_) << std::setfill(n.fill_) << std::setw(n.width_) << n.value_;
|
||||
oss << std::dec << std::setfill(' ') << std::setprecision(6);
|
||||
return oss;
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &oss, const util::XYZ &xyz) {
|
||||
oss << xyz.x << "," << xyz.y << "," << xyz.z;
|
||||
return oss;
|
||||
}
|
||||
|
||||
#endif // UTIL_HPP
|
||||
|
||||
@@ -265,11 +265,17 @@ LuaDefine(tangible_near, "tan,radius,omit_nowhere,omit_self",
|
||||
LuaStack LS(L, ltan, lradius, lomit_nowhere, lomit_self, list);
|
||||
World *w = World::fetch_global_pointer(L);
|
||||
Tangible *tan = w->tangible_get(LS, ltan);
|
||||
double radius = LS.cknumber(lradius);
|
||||
bool omit_nowhere = LS.ckboolean(lomit_nowhere);
|
||||
bool omit_self = LS.ckboolean(lomit_self);
|
||||
const AnimStep &aqback = tan->anim_queue_.back();
|
||||
util::IdVector idv = w->plane_map_.scan_radius(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, omit_nowhere, tan->id(), omit_self);
|
||||
|
||||
PlaneScan scan;
|
||||
scan.set_plane(aqback.plane());
|
||||
scan.set_bbox_given_center_radius(aqback.xyz(), LS.cknumber(lradius));
|
||||
scan.set_shape(PlaneScan::SPHERE);
|
||||
scan.set_sorted(true);
|
||||
scan.set_near(tan->id(), !LS.ckboolean(lomit_self));
|
||||
scan.set_omit_nowhere(LS.ckboolean(lomit_nowhere));
|
||||
|
||||
util::IdVector idv = w->plane_map_.scan(scan);
|
||||
tangible_getall(LS, list, idv);
|
||||
return LS.result();
|
||||
}
|
||||
@@ -282,16 +288,98 @@ LuaDefine(tangible_scan, "plane,x,y,radius,omit_nowhere",
|
||||
LuaRet list;
|
||||
LuaStack LS(L, lplane, lx, ly, lradius, lomit_nowhere, list);
|
||||
World *w = World::fetch_global_pointer(L);
|
||||
eng::string plane = LS.ckstring(lplane);
|
||||
double x = LS.cknumber(lx);
|
||||
double y = LS.cknumber(ly);
|
||||
double radius = LS.cknumber(lradius);
|
||||
bool omit_nowhere = LS.ckboolean(lomit_nowhere);
|
||||
util::IdVector idv = w->plane_map_.scan_radius(plane, x, y, radius, omit_nowhere, 0, false);
|
||||
|
||||
PlaneScan scan;
|
||||
scan.set_plane(LS.ckstring(lplane));
|
||||
scan.set_bbox_given_center_radius(util::XYZ(LS.cknumber(lx), LS.cknumber(ly), 0), LS.cknumber(lradius));
|
||||
scan.set_shape(PlaneScan::SPHERE);
|
||||
scan.set_sorted(true);
|
||||
scan.set_omit_nowhere(LS.ckboolean(lomit_nowhere));
|
||||
|
||||
util::IdVector idv = w->plane_map_.scan(scan);
|
||||
tangible_getall(LS, list, idv);
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
LuaDefine(tangible_find, "config",
|
||||
"|Find tangibles by their location."
|
||||
"|"
|
||||
"|There are multiple ways to specify the plane, the bounding"
|
||||
"|box, and the shape of the search. The most basic is to"
|
||||
"|include these parameters in the table:"
|
||||
"|"
|
||||
"| plane : the plane to search (a string)"
|
||||
"| centerx : x-coordinate of the center of the search"
|
||||
"| centery : y-coordinate of the center of the search"
|
||||
"| centerz : z-coordinate of the center of the search"
|
||||
"| radius : the radius of the search"
|
||||
"| shape : 'box', 'sphere', or 'cylinder'"
|
||||
"|"
|
||||
"|Shape has a default: 'sphere'. The other parameters do"
|
||||
"|not have default values."
|
||||
"|"
|
||||
"|Instead of specifying the radius as a single float,"
|
||||
"|you may optionally specify separate radii for each dimension."
|
||||
"|"
|
||||
"| radiusx : the radius in the x-dimension."
|
||||
"| radiusy : the radius in the y-dimension."
|
||||
"| radiusz : the radius in the z-dimension."
|
||||
"|"
|
||||
"|If you specify different radii in each dimension, then the"
|
||||
"|'sphere' shape will actually be a spheroid, and the 'cylinder'"
|
||||
"|shape will actually be a cylindroid."
|
||||
"|"
|
||||
"|Instead of specifying the center and plane, you can specify"
|
||||
"|a tangible to search near:"
|
||||
"|"
|
||||
"| near : a tangible in whose vicinity the search occurs"
|
||||
"|"
|
||||
"|If you specify 'near', then by default, the near tangible is"
|
||||
"|filtered out of the search. If you want to include it in the"
|
||||
"|results, set the 'include' flag to true. In this case, the"
|
||||
"|near tangible will always be the first search result:"
|
||||
"|"
|
||||
"| include : include the 'near' object in the results"
|
||||
"|"
|
||||
"|If you want to search an entire plane, you can use the"
|
||||
"|wholeplane option, which replaces all the other options:"
|
||||
"|"
|
||||
"| wholeplane: the name of the plane to scan in its entirety"
|
||||
"|"
|
||||
"|It is valid to use 0.0 as the radius. If you do so, then only"
|
||||
"|objects at the exact center you specify will be found."
|
||||
"|"
|
||||
"|If you are making a 2D game, it works fine to set all object Z"
|
||||
"|coordinates to zero, and then do searches with centerz=0.0"
|
||||
"|and radiusz=0.0."
|
||||
"|") {
|
||||
LuaArg config;
|
||||
LuaRet result;
|
||||
LuaStack LS(L, config, result);
|
||||
PlaneScan scan;
|
||||
eng::string error = scan.configure(LS, config);
|
||||
if (!error.empty()) {
|
||||
luaL_error(L, "%s", error.c_str());
|
||||
}
|
||||
// When the configure routine sees the 'near' flag, it stores the tangible
|
||||
// ID, but not the center and plane, because doing so would require it to
|
||||
// know about world models. We have to handle center and plane for 'near'
|
||||
// separately.
|
||||
World *w = World::fetch_global_pointer(L);
|
||||
int64_t near = scan.near();
|
||||
if (near != 0) {
|
||||
Tangible *t = w->tangible_get(near);
|
||||
assert(t != nullptr); // Should never happen.
|
||||
const AnimStep &aqback = t->anim_queue_.back();
|
||||
scan.set_plane(aqback.plane());
|
||||
scan.set_center(aqback.xyz());
|
||||
}
|
||||
// Do the scan.
|
||||
util::IdVector idv = w->plane_map_.scan(scan);
|
||||
tangible_getall(LS, result, idv);
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
LuaDefine(tangible_start, "tangible,function,arg1,arg2...",
|
||||
"|Start a thread."
|
||||
"|"
|
||||
@@ -462,6 +550,9 @@ LuaDefine(math_random, "(args...)",
|
||||
"| (high) - an int between 1 and high inclusive"
|
||||
"| (low, high) - an int between low and high inclusive"
|
||||
"|"
|
||||
"|You may also pass in a randomstate table"
|
||||
"|as an optional first argument."
|
||||
"|"
|
||||
"|math.random tries to cooperate with predictive"
|
||||
"|reexecution to be as predictable as possible."
|
||||
"|To achieve predictability, we used an ad-hoc"
|
||||
@@ -654,9 +745,7 @@ LuaDefine(doc, "function",
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
LuaDefine(http_get, "request",
|
||||
"|Make an HTTP GET request. Returns an HTTP response."
|
||||
"|See doc(http.clientrequest) and doc(http.clientresponse).") {
|
||||
int lfn_http_request(lua_State *L, const char *method) {
|
||||
World *w = World::fetch_global_pointer(L);
|
||||
w->guard_blockable(L, "http.get");
|
||||
|
||||
@@ -667,7 +756,7 @@ LuaDefine(http_get, "request",
|
||||
|
||||
// Parse the request and make sure it's valid.
|
||||
// If not, immediately pass a '400 bad request' back to lua.
|
||||
req.set_method("GET");
|
||||
req.set_method(method);
|
||||
req.set_config(LS, request);
|
||||
req.set_defaults();
|
||||
eng::string error = req.check();
|
||||
@@ -686,4 +775,22 @@ LuaDefine(http_get, "request",
|
||||
|
||||
// Block.
|
||||
return lua_yield(L, 0);
|
||||
}
|
||||
}
|
||||
|
||||
LuaDefine(http_get, "request",
|
||||
"|Make an HTTP GET request. Returns an HTTP response."
|
||||
"|See doc(http.clientrequest) and doc(http.clientresponse).") {
|
||||
return lfn_http_request(L, "GET");
|
||||
}
|
||||
|
||||
LuaDefine(http_head, "request",
|
||||
"|Make an HTTP HEAD request. Returns an HTTP response."
|
||||
"|See doc(http.clientrequest) and doc(http.clientresponse).") {
|
||||
return lfn_http_request(L, "HEAD");
|
||||
}
|
||||
|
||||
LuaDefine(http_post, "request",
|
||||
"|Make an HTTP POST request. Returns an HTTP response."
|
||||
"|See doc(http.clientrequest) and doc(http.clientresponse).") {
|
||||
return lfn_http_request(L, "POST");
|
||||
}
|
||||
|
||||
@@ -220,26 +220,23 @@ void World::tangible_delete(int64_t id) {
|
||||
LS.result();
|
||||
}
|
||||
|
||||
util::IdVector World::get_near_unsorted(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const {
|
||||
util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted) const {
|
||||
const Tangible *player = tangible_get(player_id);
|
||||
if (player == nullptr) {
|
||||
return IdVector();
|
||||
}
|
||||
|
||||
// Find out where's the center of the world.
|
||||
// Find out where the player is.
|
||||
const AnimStep &aqback = player->anim_queue_.back();
|
||||
return plane_map_.scan_radius_unsorted(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, exclude_nowhere, player_id, omit_player);
|
||||
}
|
||||
|
||||
util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const {
|
||||
const Tangible *player = tangible_get(player_id);
|
||||
if (player == nullptr) {
|
||||
return IdVector();
|
||||
}
|
||||
|
||||
// Find out where's the center of the world.
|
||||
const AnimStep &aqback = player->anim_queue_.back();
|
||||
return plane_map_.scan_radius(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, exclude_nowhere, player_id, omit_player);
|
||||
PlaneScan scan;
|
||||
scan.set_plane(aqback.plane());
|
||||
scan.set_bbox_given_center_radius(aqback.xyz(), radius);
|
||||
scan.set_shape(PlaneScan::SPHERE);
|
||||
scan.set_sorted(sorted);
|
||||
scan.set_omit_nowhere(exclude_nowhere);
|
||||
scan.set_near(player_id, !omit_player);
|
||||
return plane_map_.scan(scan);
|
||||
}
|
||||
|
||||
World::Redirects World::fetch_redirects() {
|
||||
@@ -301,6 +298,7 @@ eng::string World::probe_lua(int64_t actor_id, const eng::string &lua) {
|
||||
for (int i = top + 1; i <= lua_gettop(L); i++) {
|
||||
LuaSpecial root(i);
|
||||
pprint(LS, root, true, ostream);
|
||||
// TODO: this endl is unnecessary if we just printed a newline.
|
||||
(*ostream) << std::endl;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -56,6 +56,10 @@ static bool equivalent_values(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap,
|
||||
if (SLS.type(sval) != LUA_TSTRING) return false;
|
||||
return MLS.ckstring(mval) == SLS.ckstring(sval);
|
||||
}
|
||||
case LUA_TLIGHTUSERDATA: {
|
||||
if (SLS.type(sval) != LUA_TLIGHTUSERDATA) return false;
|
||||
return MLS.cktoken(mval) == SLS.cktoken(sval);
|
||||
}
|
||||
case LUA_TFUNCTION: {
|
||||
// Cannot really compare. Just return true if the types match.
|
||||
return SLS.type(sval) == MLS.type(mval);
|
||||
@@ -105,6 +109,11 @@ static void transmit_value(LuaStack &MLS, LuaSlot mval, LuaSlot mtnmap, StreamBu
|
||||
sb->write_string(MLS.ckstring(mval));
|
||||
return;
|
||||
}
|
||||
case LUA_TLIGHTUSERDATA: {
|
||||
sb->write_uint8(LUA_TLIGHTUSERDATA);
|
||||
sb->write_uint64(MLS.cktoken(mval).value);
|
||||
return;
|
||||
}
|
||||
case LUA_TT_GENERAL: {
|
||||
int midx = get_table_number(MLS, mval, mtnmap);
|
||||
if (midx == 0) {
|
||||
@@ -151,6 +160,10 @@ static void transmit_value_debug_string(StreamBuffer *sb, eng::ostringstream &os
|
||||
oss << sb->read_string();
|
||||
return;
|
||||
}
|
||||
case LUA_TLIGHTUSERDATA: {
|
||||
LuaToken token(sb->read_uint64());
|
||||
oss << "[" << token.str() << "]";
|
||||
}
|
||||
case LUA_TT_GENERAL: {
|
||||
oss << "table " << sb->read_int32();
|
||||
return;
|
||||
@@ -270,6 +283,12 @@ static void set_transmitted_value(LuaStack &LS, LuaSlot tangibles, LuaSlot ntmap
|
||||
LS.set(target, value);
|
||||
return;
|
||||
}
|
||||
case LUA_TLIGHTUSERDATA: {
|
||||
LuaToken value(sb->read_uint64());
|
||||
DebugLine(dbc) << dbinfo << "[" << value.str() << "]";
|
||||
LS.set(target, value);
|
||||
return;
|
||||
}
|
||||
case LUA_TT_GENERAL: {
|
||||
int index = sb->read_int32();
|
||||
DebugLine(dbc) << dbinfo << "table " << index;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
util::IdVector World::get_visible_union(int64_t actor_id, World *master) {
|
||||
return util::sort_union_id_vectors(
|
||||
master->get_near_unsorted(actor_id, RadiusVisibility, true, false),
|
||||
get_near_unsorted(actor_id, RadiusVisibility, true, false));
|
||||
master->get_near(actor_id, RadiusVisibility, true, false, false),
|
||||
get_near(actor_id, RadiusVisibility, true, false, false));
|
||||
}
|
||||
|
||||
int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) {
|
||||
@@ -169,7 +169,7 @@ void World::patch_luatabs(StreamBuffer *sb, DebugCollector *dbc) {
|
||||
int64_t actor_id = sb->read_int64();
|
||||
util::HashValue closehash = sb->read_hashvalue();
|
||||
int ncreate = sb->read_int32();
|
||||
util::IdVector closetans = get_near(actor_id, RadiusClose, true, false);
|
||||
util::IdVector closetans = get_near(actor_id, RadiusClose, true, false, true);
|
||||
assert(closehash == util::hash_id_vector(closetans));
|
||||
number_lua_tables(closetans);
|
||||
create_new_tables(ncreate);
|
||||
@@ -183,8 +183,8 @@ void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
||||
StreamBuffer tsb;
|
||||
|
||||
// Calculate the set of close tangibles.
|
||||
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false);
|
||||
assert(get_near(actor_id, RadiusClose, true, false) == closetans);
|
||||
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false, true);
|
||||
assert(get_near(actor_id, RadiusClose, true, false, true) == closetans);
|
||||
util::HashValue closehash = util::hash_id_vector(closetans);
|
||||
|
||||
// Number and pair tables in the synchronous and master model.
|
||||
@@ -250,7 +250,7 @@ void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
||||
|
||||
// Calculate the set of close tangibles.
|
||||
// TODO: we've already calculated this in an earlier function. This is wasteful.
|
||||
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false);
|
||||
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false, true);
|
||||
|
||||
tsb.write_int32(0);
|
||||
int write_count_after = tsb.total_writes();
|
||||
|
||||
@@ -39,7 +39,7 @@ eng::string World::tangible_ids_debug_string() const {
|
||||
|
||||
eng::string World::tangibles_near_debug_string(int64_t actor, int64_t distance) {
|
||||
eng::ostringstream result;
|
||||
for (int64_t id : get_near(actor, distance, true, false)) {
|
||||
for (int64_t id : get_near(actor, distance, true, false, true)) {
|
||||
const Tangible *tan = tangible_get(id);
|
||||
const AnimStep &aqback = tan->anim_queue_.back();
|
||||
result << id << ": " << aqback.graphic() << " " << aqback.plane() << " " << aqback.xyz().debug_string() << std::endl;
|
||||
|
||||
@@ -122,10 +122,10 @@ public:
|
||||
//
|
||||
// Get a list of the tangibles that are near the player. If 'exclude_nowhere' is
|
||||
// true, exclude any tangibles on the nowhere plane (but still include the player himself).
|
||||
// The unsorted version returns the tangibles in an unpredictable order.
|
||||
// The unsorted version returns the tangibles in an unpredictable order. If sorted
|
||||
// is false, return them in an unpredictable order.
|
||||
//
|
||||
IdVector get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const;
|
||||
IdVector get_near_unsorted(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const;
|
||||
IdVector get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted) const;
|
||||
|
||||
// Make a tangible.
|
||||
//
|
||||
@@ -552,12 +552,13 @@ private:
|
||||
friend int lfn_tangible_nopredict(lua_State *L);
|
||||
friend int lfn_tangible_near(lua_State *L);
|
||||
friend int lfn_tangible_scan(lua_State *L);
|
||||
friend int lfn_tangible_find(lua_State *L);
|
||||
friend int lfn_tangible_start(lua_State *L);
|
||||
friend int lfn_math_random(lua_State *L);
|
||||
friend int lfn_math_randomstate(lua_State *L);
|
||||
friend int lfn_wait(lua_State *L);
|
||||
friend int lfn_nopredict(lua_State *L);
|
||||
friend int lfn_http_get(lua_State *L);
|
||||
friend int lfn_http_request(lua_State *L, const char *method);
|
||||
};
|
||||
|
||||
using UniqueWorld = std::unique_ptr<World>;
|
||||
|
||||
14
luprex/core/wrap/wrap-bytell-hash-map.hpp
Normal file
14
luprex/core/wrap/wrap-bytell-hash-map.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef WRAP_BYTELL_HASH_MAP_HPP
|
||||
#define WRAP_BYTELL_HASH_MAP_HPP
|
||||
|
||||
#include "eng-malloc.hpp"
|
||||
#include "bytell-hash-map.hpp"
|
||||
|
||||
namespace eng {
|
||||
template<class K, class V, class H=std::hash<K>, class E=std::equal_to<K>>
|
||||
class bytell_hash_map : public ska::bytell_hash_map<K, V, H, E, eng::allocator<std::pair<const K, V>>>, public eng::opnew {
|
||||
using ska::bytell_hash_map<K, V, H, E, eng::allocator<std::pair<const K, V>>>::bytell_hash_map;
|
||||
};
|
||||
} // namespace eng
|
||||
|
||||
#endif // WRAP_BYTELL_HASH_MAP_HPP
|
||||
Reference in New Issue
Block a user