feat: implement WASM phase 1 DValue ABI
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -793,6 +795,360 @@ void DValue::clear()
|
||||
_array_index = 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const char* UCEB_MAGIC = "UCEB";
|
||||
const u8 UCEB_VERSION = 1;
|
||||
const u8 UCEB_FLAG_LIST = 1;
|
||||
|
||||
thread_local String uce_dv_last_error_text;
|
||||
thread_local String uce_dv_value_result;
|
||||
thread_local DValue uce_dv_decode_result;
|
||||
|
||||
bool ucb_append_varint(String& out, u64 value)
|
||||
{
|
||||
while(value >= 0x80)
|
||||
{
|
||||
out.push_back((char)((value & 0x7f) | 0x80));
|
||||
value >>= 7;
|
||||
}
|
||||
out.push_back((char)value);
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool ucb_read_varint(const String& src, size_t& offset, u64& value_out)
|
||||
{
|
||||
value_out = 0;
|
||||
u32 shift = 0;
|
||||
while(offset < src.size() && shift <= 63)
|
||||
{
|
||||
u8 byte = (u8)src[offset++];
|
||||
value_out |= ((u64)(byte & 0x7f) << shift);
|
||||
if((byte & 0x80) == 0)
|
||||
return(true);
|
||||
shift += 7;
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
String ucb_node_scalar(const DValue& value)
|
||||
{
|
||||
const DValue& target = value.deref();
|
||||
switch(target.type)
|
||||
{
|
||||
case('S'):
|
||||
return(target._String);
|
||||
case('F'):
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << std::setprecision(std::numeric_limits<f64>::max_digits10) << target._float;
|
||||
return(out.str());
|
||||
}
|
||||
case('B'):
|
||||
return(target._bool ? "(true)" : "(false)");
|
||||
case('P'):
|
||||
return("");
|
||||
default:
|
||||
return("");
|
||||
}
|
||||
}
|
||||
|
||||
void ucb_encode_node(String& out, const DValue& value)
|
||||
{
|
||||
const DValue& target = value.deref();
|
||||
u8 flags = target.is_list() ? UCEB_FLAG_LIST : 0;
|
||||
out.push_back((char)flags);
|
||||
String scalar = ucb_node_scalar(target);
|
||||
ucb_append_varint(out, scalar.size());
|
||||
out.append(scalar.data(), scalar.size());
|
||||
|
||||
if(target.type != 'M')
|
||||
{
|
||||
ucb_append_varint(out, 0);
|
||||
return;
|
||||
}
|
||||
ucb_append_varint(out, target._map.size());
|
||||
target.each([&](const DValue& child, String key) {
|
||||
ucb_append_varint(out, key.size());
|
||||
out.append(key.data(), key.size());
|
||||
ucb_encode_node(out, child);
|
||||
});
|
||||
}
|
||||
|
||||
bool ucb_decode_node(const String& src, size_t& offset, DValue& out, String& error, u32 depth = 0)
|
||||
{
|
||||
if(depth > 1024)
|
||||
{
|
||||
error = "UCEB1 nesting limit exceeded";
|
||||
return(false);
|
||||
}
|
||||
if(offset >= src.size())
|
||||
{
|
||||
error = "unexpected end of UCEB1 node";
|
||||
return(false);
|
||||
}
|
||||
u8 flags = (u8)src[offset++];
|
||||
u64 scalar_len = 0;
|
||||
if(!ucb_read_varint(src, offset, scalar_len))
|
||||
{
|
||||
error = "invalid UCEB1 scalar length";
|
||||
return(false);
|
||||
}
|
||||
if(scalar_len > src.size() - offset)
|
||||
{
|
||||
error = "UCEB1 scalar length exceeds input";
|
||||
return(false);
|
||||
}
|
||||
String scalar(src.data() + offset, (size_t)scalar_len);
|
||||
offset += (size_t)scalar_len;
|
||||
|
||||
u64 child_count = 0;
|
||||
if(!ucb_read_varint(src, offset, child_count))
|
||||
{
|
||||
error = "invalid UCEB1 child count";
|
||||
return(false);
|
||||
}
|
||||
|
||||
out.clear();
|
||||
if(child_count == 0 && (flags & UCEB_FLAG_LIST) == 0)
|
||||
{
|
||||
out = scalar;
|
||||
return(true);
|
||||
}
|
||||
if((flags & UCEB_FLAG_LIST) != 0)
|
||||
out.set_array();
|
||||
|
||||
for(u64 i = 0; i < child_count; i++)
|
||||
{
|
||||
u64 key_len = 0;
|
||||
if(!ucb_read_varint(src, offset, key_len))
|
||||
{
|
||||
error = "invalid UCEB1 child key length";
|
||||
return(false);
|
||||
}
|
||||
if(key_len > src.size() - offset)
|
||||
{
|
||||
error = "UCEB1 child key length exceeds input";
|
||||
return(false);
|
||||
}
|
||||
String key(src.data() + offset, (size_t)key_len);
|
||||
offset += (size_t)key_len;
|
||||
DValue child;
|
||||
if(!ucb_decode_node(src, offset, child, error, depth + 1))
|
||||
return(false);
|
||||
out[key] = child;
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
String uce_dv_key(const char* key, size_t key_len)
|
||||
{
|
||||
if(key == 0)
|
||||
return("");
|
||||
return(String(key, key_len));
|
||||
}
|
||||
|
||||
DValue* uce_dv_target(uce_dvalue* value)
|
||||
{
|
||||
if(value == 0)
|
||||
return(0);
|
||||
return(reinterpret_cast<DValue*>(value)->reference_target() ? reinterpret_cast<DValue*>(value)->reference_target() : reinterpret_cast<DValue*>(value));
|
||||
}
|
||||
|
||||
const DValue* uce_dv_target_const(uce_dvalue* value)
|
||||
{
|
||||
if(value == 0)
|
||||
return(0);
|
||||
return(&reinterpret_cast<DValue*>(value)->deref());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
String ucb_encode(const DValue& value)
|
||||
{
|
||||
String out;
|
||||
out.append(UCEB_MAGIC, 4);
|
||||
out.push_back((char)UCEB_VERSION);
|
||||
ucb_encode_node(out, value);
|
||||
return(out);
|
||||
}
|
||||
|
||||
bool ucb_decode(const String& encoded, DValue& out, String* error_out)
|
||||
{
|
||||
String error;
|
||||
if(encoded.size() < 5 || encoded.compare(0, 4, UCEB_MAGIC) != 0)
|
||||
error = "missing UCEB magic header";
|
||||
else if((u8)encoded[4] != UCEB_VERSION)
|
||||
error = "unsupported UCEB version";
|
||||
else
|
||||
{
|
||||
size_t offset = 5;
|
||||
DValue decoded;
|
||||
if(ucb_decode_node(encoded, offset, decoded, error) && offset == encoded.size())
|
||||
{
|
||||
out = decoded;
|
||||
if(error_out)
|
||||
*error_out = "";
|
||||
return(true);
|
||||
}
|
||||
if(error == "")
|
||||
error = "trailing bytes after UCEB1 document";
|
||||
}
|
||||
if(error_out)
|
||||
*error_out = error;
|
||||
return(false);
|
||||
}
|
||||
|
||||
DValue ucb_decode(const String& encoded)
|
||||
{
|
||||
DValue out;
|
||||
String error;
|
||||
ucb_decode(encoded, out, &error);
|
||||
return(out);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
uce_dvalue* uce_dv_root(void)
|
||||
{
|
||||
if(context == 0)
|
||||
return(0);
|
||||
return(reinterpret_cast<uce_dvalue*>(&context->call));
|
||||
}
|
||||
|
||||
uce_dvalue* uce_dv_get(uce_dvalue* value, const char* key, size_t key_len)
|
||||
{
|
||||
DValue* target = uce_dv_target(value);
|
||||
if(target == 0)
|
||||
return(0);
|
||||
return(reinterpret_cast<uce_dvalue*>(target->get_or_create(uce_dv_key(key, key_len))));
|
||||
}
|
||||
|
||||
uce_dvalue* uce_dv_find(uce_dvalue* value, const char* key, size_t key_len)
|
||||
{
|
||||
DValue* target = uce_dv_target(value);
|
||||
if(target == 0)
|
||||
return(0);
|
||||
return(reinterpret_cast<uce_dvalue*>(target->key(uce_dv_key(key, key_len))));
|
||||
}
|
||||
|
||||
const char* uce_dv_value(uce_dvalue* value, size_t* len_out)
|
||||
{
|
||||
const DValue* target = uce_dv_target_const(value);
|
||||
if(target == 0)
|
||||
{
|
||||
if(len_out)
|
||||
*len_out = 0;
|
||||
return(0);
|
||||
}
|
||||
uce_dv_value_result = ucb_node_scalar(*target);
|
||||
if(len_out)
|
||||
*len_out = uce_dv_value_result.size();
|
||||
return(uce_dv_value_result.data());
|
||||
}
|
||||
|
||||
void uce_dv_set_value(uce_dvalue* value, const char* bytes, size_t len)
|
||||
{
|
||||
DValue* target = uce_dv_target(value);
|
||||
if(target == 0)
|
||||
return;
|
||||
if(bytes == 0 && len > 0)
|
||||
{
|
||||
target->set("");
|
||||
return;
|
||||
}
|
||||
target->set(String(bytes ? bytes : "", len));
|
||||
}
|
||||
|
||||
size_t uce_dv_count(uce_dvalue* value)
|
||||
{
|
||||
const DValue* target = uce_dv_target_const(value);
|
||||
if(target == 0 || target->type != 'M')
|
||||
return(0);
|
||||
return(target->_map.size());
|
||||
}
|
||||
|
||||
int uce_dv_is_list(uce_dvalue* value)
|
||||
{
|
||||
const DValue* target = uce_dv_target_const(value);
|
||||
return(target && target->is_list() ? 1 : 0);
|
||||
}
|
||||
|
||||
uce_dv_iter uce_dv_iter_begin(uce_dvalue* value)
|
||||
{
|
||||
uce_dv_iter iter;
|
||||
iter.position = 0;
|
||||
iter.reserved[0] = 0;
|
||||
iter.reserved[1] = 0;
|
||||
iter.reserved[2] = 0;
|
||||
return(iter);
|
||||
}
|
||||
|
||||
int uce_dv_iter_next(uce_dvalue* value, uce_dv_iter* iter, const char** key_out, size_t* key_len_out, uce_dvalue** child_out)
|
||||
{
|
||||
const DValue* target = uce_dv_target_const(value);
|
||||
if(target == 0 || target->type != 'M' || iter == 0)
|
||||
return(0);
|
||||
std::map<String, DValue>::const_iterator entry;
|
||||
if(target->is_list())
|
||||
{
|
||||
entry = target->_map.find(std::to_string((u64)iter->position));
|
||||
if(entry == target->_map.end())
|
||||
return(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(iter->position >= target->_map.size())
|
||||
return(0);
|
||||
entry = target->_map.begin();
|
||||
std::advance(entry, iter->position);
|
||||
}
|
||||
if(key_out)
|
||||
*key_out = entry->first.data();
|
||||
if(key_len_out)
|
||||
*key_len_out = entry->first.size();
|
||||
if(child_out)
|
||||
*child_out = reinterpret_cast<uce_dvalue*>(const_cast<DValue*>(&entry->second));
|
||||
iter->position += 1;
|
||||
return(1);
|
||||
}
|
||||
|
||||
size_t uce_dv_encode(uce_dvalue* value, char* buf, size_t cap)
|
||||
{
|
||||
const DValue* target = uce_dv_target_const(value);
|
||||
if(target == 0)
|
||||
return(0);
|
||||
String encoded = ucb_encode(*target);
|
||||
if(buf != 0 && cap > 0)
|
||||
{
|
||||
size_t copy_len = encoded.size() < cap ? encoded.size() : cap;
|
||||
memcpy(buf, encoded.data(), copy_len);
|
||||
}
|
||||
return(encoded.size());
|
||||
}
|
||||
|
||||
uce_dvalue* uce_dv_decode(const char* buf, size_t len)
|
||||
{
|
||||
String encoded(buf ? buf : "", buf ? len : 0);
|
||||
String error;
|
||||
DValue decoded;
|
||||
if(!ucb_decode(encoded, decoded, &error))
|
||||
{
|
||||
uce_dv_last_error_text = error;
|
||||
return(0);
|
||||
}
|
||||
uce_dv_last_error_text = "";
|
||||
uce_dv_decode_result = decoded;
|
||||
return(reinterpret_cast<uce_dvalue*>(&uce_dv_decode_result));
|
||||
}
|
||||
|
||||
const char* uce_dv_last_error(void)
|
||||
{
|
||||
return(uce_dv_last_error_text.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
String to_String(DValue t)
|
||||
{
|
||||
return(t.to_string());
|
||||
|
||||
@@ -67,3 +67,32 @@ struct DValue {
|
||||
|
||||
String to_String(DValue t);
|
||||
String var_dump(const DValue& map, String prefix = "", String postfix = "\n");
|
||||
|
||||
String ucb_encode(const DValue& value);
|
||||
DValue ucb_decode(const String& encoded);
|
||||
bool ucb_decode(const String& encoded, DValue& out, String* error_out = 0);
|
||||
|
||||
extern "C" {
|
||||
|
||||
typedef struct DValue uce_dvalue;
|
||||
|
||||
typedef struct uce_dv_iter
|
||||
{
|
||||
size_t position;
|
||||
size_t reserved[3];
|
||||
} uce_dv_iter;
|
||||
|
||||
uce_dvalue* uce_dv_root(void);
|
||||
uce_dvalue* uce_dv_get(uce_dvalue* value, const char* key, size_t key_len);
|
||||
uce_dvalue* uce_dv_find(uce_dvalue* value, const char* key, size_t key_len);
|
||||
const char* uce_dv_value(uce_dvalue* value, size_t* len_out);
|
||||
void uce_dv_set_value(uce_dvalue* value, const char* bytes, size_t len);
|
||||
size_t uce_dv_count(uce_dvalue* value);
|
||||
int uce_dv_is_list(uce_dvalue* value);
|
||||
uce_dv_iter uce_dv_iter_begin(uce_dvalue* value);
|
||||
int uce_dv_iter_next(uce_dvalue* value, uce_dv_iter* iter, const char** key_out, size_t* key_len_out, uce_dvalue** child_out);
|
||||
size_t uce_dv_encode(uce_dvalue* value, char* buf, size_t cap);
|
||||
uce_dvalue* uce_dv_decode(const char* buf, size_t len);
|
||||
const char* uce_dv_last_error(void);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user