refactor: rename DTree to DValue

This commit is contained in:
udo
2026-06-12 11:05:52 +00:00
parent 941f5aea08
commit 7066da3cde
157 changed files with 1203 additions and 1074 deletions
+859
View File
@@ -0,0 +1,859 @@
#include <cctype>
#include <cmath>
#include <limits>
namespace {
template <typename TreePtr>
TreePtr dv_resolve_reference(TreePtr tree)
{
u32 depth = 0;
while(tree && tree->type == 'R' && depth < 16)
{
TreePtr target = reinterpret_cast<TreePtr>(tree->_ptr);
if(target == 0 || target == tree)
break;
tree = target;
depth += 1;
}
return(tree);
}
bool dv_key_is_index(String key)
{
if(key == "")
return(false);
for(auto c : key)
{
if(!isdigit(c))
return(false);
}
// Only canonical index strings count ("1", not "01"), so each numeric
// value has exactly one representation.
if(key.length() > 1 && key[0] == '0')
return(false);
return(true);
}
String dv_trim(String raw)
{
if(raw == "")
return(raw);
size_t start = 0;
while(start < raw.length() && isspace(raw[start]))
start += 1;
size_t end = raw.length();
while(end > start && isspace(raw[end - 1]))
end -= 1;
return(raw.substr(start, end - start));
}
String dv_lower(String raw)
{
for(auto& c : raw)
c = (char)tolower(c);
return(raw);
}
bool dv_string_to_bool_value(String raw, bool& value_out)
{
raw = dv_lower(dv_trim(raw));
if(raw == "")
return(false);
if(raw == "1" || raw == "true" || raw == "(true)" || raw == "yes" || raw == "on")
{
value_out = true;
return(true);
}
if(raw == "0" || raw == "false" || raw == "(false)" || raw == "no" || raw == "off" || raw == "null")
{
value_out = false;
return(true);
}
return(false);
}
bool dv_string_to_f64_value(String raw, f64& value_out)
{
raw = dv_trim(raw);
if(raw == "")
return(false);
bool bool_value = false;
if(dv_string_to_bool_value(raw, bool_value))
{
value_out = (bool_value ? 1.0 : 0.0);
return(true);
}
char* end = 0;
value_out = strtod(raw.c_str(), &end);
if(end == raw.c_str())
return(false);
while(end && *end != 0)
{
if(!isspace(*end))
return(false);
end += 1;
}
return(std::isfinite(value_out));
}
const DValue* dv_scalar_map_value(const DValue& tree)
{
if(tree.type != 'M' || tree._map.size() != 1)
return(0);
auto it = tree._map.begin();
if(it == tree._map.end())
return(0);
return(&it->second.deref());
}
f64 dv_clamp_to_f64_range(long double value)
{
if(value > std::numeric_limits<f64>::max())
return(std::numeric_limits<f64>::max());
if(value < -std::numeric_limits<f64>::max())
return(-std::numeric_limits<f64>::max());
return((f64)value);
}
s64 dv_clamp_to_s64_range(long double value)
{
if(value > (long double)std::numeric_limits<s64>::max())
return(std::numeric_limits<s64>::max());
if(value < (long double)std::numeric_limits<s64>::min())
return(std::numeric_limits<s64>::min());
return((s64)value);
}
u64 dv_clamp_to_u64_range(long double value)
{
if(value <= 0)
return(0);
if(value > (long double)std::numeric_limits<u64>::max())
return(std::numeric_limits<u64>::max());
return((u64)value);
}
}
void DValue::each(std::function <void (const DValue& t, String key)> f) const
{
const DValue& target = deref();
switch(target.type)
{
case('M'):
// Lists iterate in numeric index order (string keys sort "10"
// before "2"), matching the json/xml/yaml encoders.
if(target.is_list())
{
for(u64 i = 0; i < target._map.size(); i++)
{
auto it = target._map.find(std::to_string(i));
if(it == target._map.end())
break;
f(it->second, it->first);
}
break;
}
for (auto it = target._map.begin(); it != target._map.end(); ++it)
{
f(it->second, it->first);
}
break;
default:
f(*this, "");
break;
}
}
bool DValue::is_array() const
{
return(deref().type == 'M');
}
bool DValue::is_list() const
{
const DValue& target = deref();
if(target.type != 'M')
return(false);
if(target._map.size() == 0)
return(target._list_mode);
// The map iterates in string order ("10" before "2"), so the check must
// be order-independent: n unique canonical index keys with maximum n-1
// are exactly 0..n-1.
s64 max_index = -1;
for(const auto& entry : target._map)
{
if(!dv_key_is_index(entry.first))
return(false);
s64 index = strtoll(entry.first.c_str(), 0, 10);
if(index > max_index)
max_index = index;
}
return(max_index == (s64)target._map.size() - 1);
}
String DValue::to_string(String default_value) const
{
const DValue& target = deref();
switch(target.type)
{
case('S'):
if(target._String == "")
return(default_value);
return(target._String);
case('F'):
return(std::to_string(target._float));
case('B'):
return(target._bool ? "(true)" : "(false)");
case('M'):
return(default_value);
case('P'):
return(std::to_string((u64)target._ptr));
case('R'):
return(default_value);
}
return(default_value);
}
s64 DValue::to_s64(s64 default_value) const
{
const DValue& target = deref();
switch(target.type)
{
case('S'):
{
f64 value = 0;
if(!dv_string_to_f64_value(target._String, value))
return(default_value);
return(dv_clamp_to_s64_range((long double)value));
}
case('F'):
return(dv_clamp_to_s64_range((long double)target._float));
case('B'):
return(target._bool ? 1 : 0);
case('M'):
{
const DValue* item = dv_scalar_map_value(target);
if(item)
return(item->to_s64(default_value));
return(default_value);
}
case('P'):
return(dv_clamp_to_s64_range((long double)(u64)target._ptr));
case('R'):
return(default_value);
}
return(default_value);
}
u64 DValue::to_u64(u64 default_value) const
{
const DValue& target = deref();
switch(target.type)
{
case('S'):
{
f64 value = 0;
if(!dv_string_to_f64_value(target._String, value))
return(default_value);
return(dv_clamp_to_u64_range((long double)value));
}
case('F'):
return(dv_clamp_to_u64_range((long double)target._float));
case('B'):
return(target._bool ? 1 : 0);
case('M'):
{
const DValue* item = dv_scalar_map_value(target);
if(item)
return(item->to_u64(default_value));
return(default_value);
}
case('P'):
return((u64)target._ptr);
case('R'):
return(default_value);
}
return(default_value);
}
f64 DValue::to_f64(f64 default_value) const
{
const DValue& target = deref();
switch(target.type)
{
case('S'):
{
f64 value = 0;
if(!dv_string_to_f64_value(target._String, value))
return(default_value);
return(value);
}
case('F'):
return(target._float);
case('B'):
return(target._bool ? 1.0 : 0.0);
case('M'):
{
const DValue* item = dv_scalar_map_value(target);
if(item)
return(item->to_f64(default_value));
return(default_value);
}
case('P'):
return(dv_clamp_to_f64_range((long double)(u64)target._ptr));
case('R'):
return(default_value);
}
return(default_value);
}
bool DValue::to_bool(bool default_value) const
{
const DValue& target = deref();
switch(target.type)
{
case('S'):
{
bool value = false;
if(dv_string_to_bool_value(target._String, value))
return(value);
f64 numeric_value = 0;
if(dv_string_to_f64_value(target._String, numeric_value))
return(numeric_value != 0);
// Non-empty unparseable strings stay truthy; only a missing/empty
// value falls back to the default.
if(dv_trim(target._String) != "")
return(true);
return(default_value);
}
case('F'):
return(target._float != 0);
case('B'):
return(target._bool);
case('M'):
{
const DValue* item = dv_scalar_map_value(target);
if(item)
return(item->to_bool(default_value));
return(target._map.size() > 0);
}
case('P'):
return(target._ptr != 0);
case('R'):
return(default_value);
}
return(default_value);
}
StringMap DValue::to_stringmap() const
{
const DValue& target = deref();
StringMap result;
switch(target.type)
{
case('M'):
for(const auto& entry : target._map)
result[entry.first] = entry.second.deref().to_string();
break;
case('S'):
if(dv_trim(target._String) != "")
result["value"] = target._String;
break;
case('F'):
case('B'):
case('P'):
result["value"] = target.to_string();
break;
case('R'):
break;
}
return(result);
}
String DValue::to_json(char quote_char) const
{
const DValue& target = deref();
switch(target.type)
{
case('S'):
return(json_escape(target._String, quote_char));
break;
case('F'):
return(std::to_string(target._float));
break;
case('B'):
return(target._bool ? "true" : "false");
break;
case('M'):
return("\"(array)\"");
break;
case('P'):
return("\"(pointer)\"");
break;
case('R'):
return("\"(reference)\"");
break;
}
return("\"(unknown)\"");
}
String DValue::get_type_name() const
{
const DValue& target = deref();
switch(target.type)
{
case('S'):
return("String");
break;
case('F'):
return("f64");
break;
case('B'):
return("bool");
break;
case('M'):
return("array");
break;
case('P'):
return("pointer");
break;
case('R'):
return("reference");
break;
}
return("unknown");
}
DValue DValue::get_by_path(String path, String delim) const
{
const DValue* current = &deref();
if(path == "")
return(*current);
size_t start = 0;
while(start <= path.length())
{
size_t end = path.find(delim, start);
String segment;
if(end == String::npos)
segment = path.substr(start);
else
segment = path.substr(start, end - start);
if(segment == "")
{
if(end == String::npos)
break;
start = end + delim.length();
continue;
}
current = &current->deref();
if(current->type != 'M')
return(DValue());
auto it = current->_map.find(segment);
if(it == current->_map.end())
return(DValue());
current = &it->second;
if(end == String::npos)
break;
start = end + delim.length();
}
return(current->deref());
}
bool DValue::is_reference() const
{
return(type == 'R');
}
DValue* DValue::reference_target()
{
if(type != 'R')
return(0);
DValue* target = dv_resolve_reference(this);
if(target == 0 || target == this || target->type == 'R')
return(0);
return(target);
}
const DValue* DValue::reference_target() const
{
if(type != 'R')
return(0);
const DValue* target = dv_resolve_reference(this);
if(target == 0 || target == this || target->type == 'R')
return(0);
return(target);
}
DValue& DValue::deref()
{
DValue* target = dv_resolve_reference(this);
if(target == 0)
return(*this);
return(*target);
}
const DValue& DValue::deref() const
{
const DValue* target = dv_resolve_reference(this);
if(target == 0)
return(*this);
return(*target);
}
void DValue::set_type(char t)
{
DValue* target = reference_target();
if(target)
{
target->set_type(t);
return;
}
if(type != t)
{
type = t;
switch(type)
{
case('M'):
_map.clear();
_array_index = 0;
_list_mode = false;
break;
}
}
}
void DValue::set(String s)
{
DValue* target = reference_target();
if(target)
{
target->set(s);
return;
}
set_type('S');
_String = s;
_list_mode = false;
}
void DValue::set(void* p)
{
DValue* target = reference_target();
if(target)
{
target->set(p);
return;
}
set_type('P');
_ptr = p;
_list_mode = false;
}
void DValue::set(f64 f)
{
DValue* target = reference_target();
if(target)
{
target->set(f);
return;
}
set_type('F');
_float = f;
_list_mode = false;
}
void DValue::set_bool(bool b)
{
DValue* target = reference_target();
if(target)
{
target->set_bool(b);
return;
}
set_type('B');
_bool = b;
_list_mode = false;
}
void DValue::set(DValue source)
{
DValue* target = reference_target();
if(target)
{
target->set(source);
return;
}
set_type(source.type);
switch(type)
{
case('S'):
_String = source._String;
_list_mode = false;
break;
case('F'):
_float = source._float;
_list_mode = false;
break;
case('B'):
_bool = source._bool;
_list_mode = false;
break;
case('M'):
_map = source._map;
_array_index = source._array_index;
_list_mode = source._list_mode;
break;
case('P'):
_ptr = source._ptr;
_list_mode = false;
break;
case('R'):
_ptr = source._ptr;
_list_mode = false;
break;
}
}
void DValue::set(StringMap source)
{
DValue* target = reference_target();
if(target)
{
target->set(source);
return;
}
set_type('M');
_map.clear();
_array_index = 0;
_list_mode = false;
for (auto it = source.begin(); it != source.end(); ++it)
{
_map[it->first] = it->second;
}
}
void DValue::set_array()
{
DValue* target = reference_target();
if(target)
{
target->set_array();
return;
}
type = 'M';
_map.clear();
_array_index = 0;
_list_mode = true;
}
void DValue::set_reference(DValue* target)
{
type = 'R';
_ptr = target;
}
bool DValue::has(String s) const
{
const DValue& target = deref();
if(target.type != 'M')
return(false);
return(target._map.find(s) != target._map.end());
}
DValue* DValue::key(String s)
{
DValue* target = reference_target();
if(target)
return(target->key(s));
if(type != 'M')
return(0);
auto it = _map.find(s);
if(it == _map.end())
return(0);
return(&it->second);
}
const DValue* DValue::key(String s) const
{
const DValue& target = deref();
if(target.type != 'M')
return(0);
auto it = target._map.find(s);
if(it == target._map.end())
return(0);
return(&it->second);
}
DValue* DValue::get_or_create(String s)
{
DValue* target = reference_target();
if(target)
return(target->get_or_create(s));
set_type('M');
if(_list_mode && !dv_key_is_index(s))
_list_mode = false;
return(&_map[s]);
}
DValue& DValue::operator [] (String s) {
DValue* target = reference_target();
if(target)
return((*target)[s]);
return(*get_or_create(s));
}
void DValue::operator = (String v) { set(v); }
void DValue::operator = (f64 v) { set(v); }
void DValue::operator = (void* v) { set(v); }
void DValue::operator = (DValue v) { set(v); }
void DValue::operator = (StringMap v) { set(v); }
void DValue::push(const DValue& child)
{
DValue* target = reference_target();
if(target)
{
target->push(child);
return;
}
set_type('M');
if(_map.size() == 0)
{
_list_mode = true;
_array_index = 0;
}
else
{
if(is_list())
{
_list_mode = true;
_array_index = _map.size();
}
else
{
_list_mode = false;
while(_map.find(std::to_string(_array_index)) != _map.end())
_array_index += 1;
}
}
_map[std::to_string(_array_index)] = child;
_array_index += 1;
}
DValue DValue::pop()
{
DValue* target = reference_target();
if(target)
return(target->pop());
set_type('M');
if(_map.empty())
{
_array_index = 0;
return(DValue());
}
auto last = _map.rbegin();
DValue result = last->second;
_map.erase(last->first);
if(_list_mode)
_array_index = _map.size();
return(result);
}
void DValue::remove(String s)
{
DValue* target = reference_target();
if(target)
{
target->remove(s);
return;
}
set_type('M');
_map.erase(s);
if(_map.size() == 0)
_array_index = 0;
}
void DValue::clear()
{
DValue* target = reference_target();
if(target)
{
target->clear();
return;
}
set_type('M');
_map.clear();
_array_index = 0;
}
String to_String(DValue t)
{
return(t.to_string());
}
String var_dump(const DValue& map, String prefix, String postfix)
{
String result = "";
if(!map.is_array())
return(map.to_string());
map.each([&] (const DValue& item, String key) {
result += prefix + key + ": " + item.to_string() + postfix;
if(item.is_array())
result += var_dump(item, prefix + "\t");
});
return(result);
}
String json_escape(String s, char quote_char)
{
//return(String("\"")+s+"\"");
String result;
u32 i = 0;
result.append(1, quote_char);
while(i < s.length())
{
char c = s[i];
if(c == quote_char)
{
result.append(1, '\\');
result.append(1, quote_char);
}
else switch(c)
{
case('\t'):
result.append("\\t");
break;
case('\n'):
result.append("\\n");
break;
case('"'):
result.append("\\\"");
break;
case('\r'):
result.append("\\r");
break;
case('\\'):
result.append("\\\\");
break;
case('\b'):
result.append("\\b");
break;
case('\f'):
result.append("\\f");
break;
default:
result.append(1, c);
break;
}
i += 1;
}
result.append(1, quote_char);
return(result);
}