2501 lines
50 KiB
C++
2501 lines
50 KiB
C++
#include "functionlib.h"
|
|
|
|
#ifndef __UCE_WASM_CORE__
|
|
#define PCRE2_CODE_UNIT_WIDTH 8
|
|
#include <pcre2.h>
|
|
#endif
|
|
#include <cctype>
|
|
#include <stdexcept>
|
|
#include <algorithm>
|
|
|
|
String var_dump(StringMap map, String prefix, String postfix)
|
|
{
|
|
String result = "";
|
|
|
|
for (auto it = map.begin(); it != map.end(); ++it)
|
|
{
|
|
result.append(prefix + to_upper(it->first) + ": " + it->second + postfix);
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
String var_dump(StringList slist, String prefix, String postfix)
|
|
{
|
|
String result = "";
|
|
|
|
for (auto& s : slist)
|
|
{
|
|
result.append(prefix + s + postfix);
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
u8 char_to_u8(char input)
|
|
{
|
|
if(input >= '0' && input <= '9')
|
|
return input - '0';
|
|
if(input >= 'A' && input <= 'F')
|
|
return input - 'A' + 10;
|
|
if(input >= 'a' && input <= 'f')
|
|
return input - 'a' + 10;
|
|
return(0);
|
|
}
|
|
|
|
u8 hex_to_u8(String src)
|
|
{
|
|
return(char_to_u8(src[0])*16 + char_to_u8(src[1]));
|
|
}
|
|
|
|
String to_lower(String s)
|
|
{
|
|
String result = s;
|
|
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return((char)std::tolower(c)); });
|
|
return(result);
|
|
}
|
|
|
|
String to_upper(String s)
|
|
{
|
|
String result = s;
|
|
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return((char)std::toupper(c)); });
|
|
return(result);
|
|
}
|
|
|
|
StringList list_unique(StringList items)
|
|
{
|
|
StringList result;
|
|
std::set<String> seen;
|
|
for(auto item : items)
|
|
{
|
|
if(seen.find(item) != seen.end())
|
|
continue;
|
|
seen.insert(item);
|
|
result.push_back(item);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
StringList list_sort(StringList items)
|
|
{
|
|
std::sort(items.begin(), items.end());
|
|
return(items);
|
|
}
|
|
|
|
bool list_some(StringList items, std::function<bool (String)> f)
|
|
{
|
|
for(auto item : items)
|
|
{
|
|
if(f(item))
|
|
return(true);
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
bool list_every(StringList items, std::function<bool (String)> f)
|
|
{
|
|
for(auto item : items)
|
|
{
|
|
if(!f(item))
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
String list_find(StringList items, std::function<bool (String)> f, String fallback)
|
|
{
|
|
for(auto item : items)
|
|
{
|
|
if(f(item))
|
|
return(item);
|
|
}
|
|
return(fallback);
|
|
}
|
|
|
|
StringList dv_keys(DValue tree)
|
|
{
|
|
StringList result;
|
|
tree.each([&](const DValue& item, String key) {
|
|
if(key != "")
|
|
result.push_back(key);
|
|
});
|
|
return(result);
|
|
}
|
|
|
|
DValue dv_values(DValue tree)
|
|
{
|
|
DValue result;
|
|
result.set_array();
|
|
tree.each([&](const DValue& item, String key) {
|
|
result.push(item);
|
|
});
|
|
return(result);
|
|
}
|
|
|
|
DValue dv_pick(DValue tree, StringList keys)
|
|
{
|
|
DValue result;
|
|
for(auto key : keys)
|
|
{
|
|
DValue* item = tree.key(key);
|
|
if(item)
|
|
result[key] = *item;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
DValue dv_omit(DValue tree, StringList keys)
|
|
{
|
|
DValue result;
|
|
std::set<String> omitted(keys.begin(), keys.end());
|
|
tree.each([&](const DValue& item, String key) {
|
|
if(key != "" && omitted.find(key) == omitted.end())
|
|
result[key] = item;
|
|
});
|
|
return(result);
|
|
}
|
|
|
|
DValue dv_map(DValue tree, std::function<DValue (const DValue&, String)> f)
|
|
{
|
|
DValue result;
|
|
bool input_is_list = tree.is_list();
|
|
if(input_is_list)
|
|
result.set_array();
|
|
tree.each([&](const DValue& item, String key) {
|
|
DValue mapped = f(item, key);
|
|
if(key != "" && !input_is_list)
|
|
result[key] = mapped;
|
|
else
|
|
result.push(mapped);
|
|
});
|
|
return(result);
|
|
}
|
|
|
|
DValue dv_filter(DValue tree, std::function<bool (const DValue&, String)> f)
|
|
{
|
|
DValue result;
|
|
bool input_is_list = tree.is_list();
|
|
if(input_is_list)
|
|
result.set_array();
|
|
tree.each([&](const DValue& item, String key) {
|
|
if(!f(item, key))
|
|
return;
|
|
if(key != "" && !input_is_list)
|
|
result[key] = item;
|
|
else
|
|
result.push(item);
|
|
});
|
|
return(result);
|
|
}
|
|
|
|
DValue dv_group_by(DValue tree, std::function<String (const DValue&, String)> f)
|
|
{
|
|
DValue result;
|
|
tree.each([&](const DValue& item, String key) {
|
|
String group = f(item, key);
|
|
DValue* group_items = result.get_or_create(group);
|
|
if(!group_items->is_array())
|
|
group_items->set_array();
|
|
group_items->push(item);
|
|
});
|
|
return(result);
|
|
}
|
|
|
|
String substr(String s, s64 start_pos)
|
|
{
|
|
s64 len = s.length();
|
|
s64 start = start_pos;
|
|
if(start < 0)
|
|
start = len + start;
|
|
if(start < 0)
|
|
start = 0;
|
|
if(start >= len)
|
|
return("");
|
|
return(s.substr(start));
|
|
}
|
|
|
|
String substr(String s, s64 start_pos, s64 length)
|
|
{
|
|
s64 len = s.length();
|
|
s64 start = start_pos;
|
|
if(start < 0)
|
|
start = len + start;
|
|
if(start < 0)
|
|
start = 0;
|
|
if(start >= len)
|
|
return("");
|
|
|
|
s64 end = len;
|
|
if(length >= 0)
|
|
end = start + length;
|
|
else
|
|
end = len + length;
|
|
|
|
if(end < start)
|
|
return("");
|
|
if(end > len)
|
|
end = len;
|
|
return(s.substr(start, end - start));
|
|
}
|
|
|
|
s64 strpos(String haystack, String needle, s64 offset)
|
|
{
|
|
s64 start = offset;
|
|
s64 len = haystack.length();
|
|
if(start < 0)
|
|
start = len + start;
|
|
if(start < 0)
|
|
start = 0;
|
|
if(start > len)
|
|
return(-1);
|
|
if(needle == "")
|
|
return(start);
|
|
auto pos = haystack.find(needle, start);
|
|
if(pos == std::string::npos)
|
|
return(-1);
|
|
return(pos);
|
|
}
|
|
|
|
bool str_starts_with(String haystack, String needle)
|
|
{
|
|
if(needle.length() > haystack.length())
|
|
return(false);
|
|
return(haystack.compare(0, needle.length(), needle) == 0);
|
|
}
|
|
|
|
bool str_ends_with(String haystack, String needle)
|
|
{
|
|
if(needle.length() > haystack.length())
|
|
return(false);
|
|
return(haystack.compare(haystack.length() - needle.length(), needle.length(), needle) == 0);
|
|
}
|
|
|
|
bool contains(String haystack, String needle)
|
|
{
|
|
if(needle == "")
|
|
return(true);
|
|
return(haystack.find(needle) != std::string::npos);
|
|
}
|
|
|
|
String replace(String s, String search, String replace_with)
|
|
{
|
|
s64 last_spos = 0;
|
|
auto spos = s.find(search);
|
|
if(spos == std::string::npos)
|
|
return(s);
|
|
String result = "";
|
|
auto slen = search.length();
|
|
while(spos != std::string::npos)
|
|
{
|
|
if(spos - last_spos > 0)
|
|
result.append(s.substr(last_spos, spos - last_spos));
|
|
result.append(replace_with);
|
|
last_spos = spos + slen;
|
|
spos = s.find(search, last_spos);
|
|
}
|
|
if(last_spos < s.length())
|
|
result.append(s.substr(last_spos));
|
|
return(result);
|
|
}
|
|
|
|
#ifndef __UCE_WASM_CORE__
|
|
namespace {
|
|
|
|
String regex_flags_label(String flags)
|
|
{
|
|
return(flags == "" ? "default" : flags);
|
|
}
|
|
|
|
void regex_throw(String function_name, String message)
|
|
{
|
|
throw std::runtime_error(function_name + "(): " + message);
|
|
}
|
|
|
|
String regex_pcre2_error(int error_code)
|
|
{
|
|
PCRE2_UCHAR buffer[256];
|
|
pcre2_get_error_message(error_code, buffer, sizeof(buffer));
|
|
return(String(reinterpret_cast<char*>(buffer)));
|
|
}
|
|
|
|
uint32_t regex_compile_options(String flags, String function_name)
|
|
{
|
|
uint32_t options = PCRE2_UTF | PCRE2_UCP;
|
|
for(char flag : flags)
|
|
{
|
|
switch(flag)
|
|
{
|
|
case('i'):
|
|
options |= PCRE2_CASELESS;
|
|
break;
|
|
case('m'):
|
|
options |= PCRE2_MULTILINE;
|
|
break;
|
|
case('s'):
|
|
options |= PCRE2_DOTALL;
|
|
break;
|
|
case('x'):
|
|
options |= PCRE2_EXTENDED;
|
|
break;
|
|
case('u'):
|
|
options |= PCRE2_UTF | PCRE2_UCP;
|
|
break;
|
|
case('a'):
|
|
options &= ~PCRE2_UTF;
|
|
options &= ~PCRE2_UCP;
|
|
break;
|
|
default:
|
|
regex_throw(function_name, "unknown regex flag '" + String(1, flag) + "'");
|
|
}
|
|
}
|
|
return(options);
|
|
}
|
|
|
|
struct RegexCode {
|
|
pcre2_code* code = 0;
|
|
|
|
RegexCode(String pattern, String flags, String function_name)
|
|
{
|
|
int error_code = 0;
|
|
PCRE2_SIZE error_offset = 0;
|
|
code = pcre2_compile(
|
|
reinterpret_cast<PCRE2_SPTR>(pattern.c_str()),
|
|
pattern.length(),
|
|
regex_compile_options(flags, function_name),
|
|
&error_code,
|
|
&error_offset,
|
|
0
|
|
);
|
|
if(!code)
|
|
regex_throw(function_name, "could not compile pattern at offset " + std::to_string((u64)error_offset) + ": " + regex_pcre2_error(error_code));
|
|
|
|
pcre2_jit_compile(code, PCRE2_JIT_COMPLETE);
|
|
}
|
|
|
|
~RegexCode()
|
|
{
|
|
if(code)
|
|
pcre2_code_free(code);
|
|
}
|
|
};
|
|
|
|
struct RegexMatchData {
|
|
pcre2_match_data* data = 0;
|
|
|
|
RegexMatchData(pcre2_code* code)
|
|
{
|
|
data = pcre2_match_data_create_from_pattern(code, 0);
|
|
if(!data)
|
|
regex_throw("regex", "could not allocate match data");
|
|
}
|
|
|
|
~RegexMatchData()
|
|
{
|
|
if(data)
|
|
pcre2_match_data_free(data);
|
|
}
|
|
};
|
|
|
|
String regex_subject_slice(String subject, PCRE2_SIZE start, PCRE2_SIZE end)
|
|
{
|
|
if(start == PCRE2_UNSET || end == PCRE2_UNSET || end < start || start > subject.length())
|
|
return("");
|
|
if(end > subject.length())
|
|
end = subject.length();
|
|
return(subject.substr(start, end - start));
|
|
}
|
|
|
|
size_t regex_next_utf8_offset(String subject, size_t offset)
|
|
{
|
|
if(offset >= subject.length())
|
|
return(subject.length() + 1);
|
|
|
|
unsigned char c = (unsigned char)subject[offset];
|
|
size_t step = 1;
|
|
if((c & 0x80) == 0)
|
|
step = 1;
|
|
else if((c & 0xE0) == 0xC0)
|
|
step = 2;
|
|
else if((c & 0xF0) == 0xE0)
|
|
step = 3;
|
|
else if((c & 0xF8) == 0xF0)
|
|
step = 4;
|
|
|
|
if(offset + step > subject.length())
|
|
step = 1;
|
|
return(offset + step);
|
|
}
|
|
|
|
void regex_add_named_captures(DValue& result, pcre2_code* code, String subject, PCRE2_SIZE* ovector, int rc)
|
|
{
|
|
uint32_t name_count = 0;
|
|
uint32_t entry_size = 0;
|
|
PCRE2_SPTR name_table = 0;
|
|
|
|
pcre2_pattern_info(code, PCRE2_INFO_NAMECOUNT, &name_count);
|
|
if(name_count == 0)
|
|
return;
|
|
|
|
pcre2_pattern_info(code, PCRE2_INFO_NAMEENTRYSIZE, &entry_size);
|
|
pcre2_pattern_info(code, PCRE2_INFO_NAMETABLE, &name_table);
|
|
|
|
for(uint32_t i = 0; i < name_count; i += 1)
|
|
{
|
|
PCRE2_SPTR entry = name_table + (i * entry_size);
|
|
uint32_t group_index = (entry[0] << 8) | entry[1];
|
|
String name(reinterpret_cast<const char*>(entry + 2));
|
|
if(group_index >= (uint32_t)rc)
|
|
continue;
|
|
|
|
PCRE2_SIZE start = ovector[group_index * 2];
|
|
PCRE2_SIZE end = ovector[group_index * 2 + 1];
|
|
if(start == PCRE2_UNSET || end == PCRE2_UNSET)
|
|
continue;
|
|
|
|
result["named"][name] = regex_subject_slice(subject, start, end);
|
|
result["named_offsets"][name]["index"] = (f64)group_index;
|
|
result["named_offsets"][name]["start"] = (f64)start;
|
|
result["named_offsets"][name]["end"] = (f64)end;
|
|
}
|
|
}
|
|
|
|
DValue regex_build_match_tree(String pattern, String flags, String subject, pcre2_code* code, pcre2_match_data* match_data, int rc)
|
|
{
|
|
DValue result;
|
|
result["matched"].set_bool(rc >= 0);
|
|
result["pattern"] = pattern;
|
|
result["flags"] = regex_flags_label(flags);
|
|
|
|
if(rc < 0)
|
|
return(result);
|
|
|
|
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data);
|
|
result["start"] = (f64)ovector[0];
|
|
result["end"] = (f64)ovector[1];
|
|
result["match"] = regex_subject_slice(subject, ovector[0], ovector[1]);
|
|
|
|
for(int i = 0; i < rc; i += 1)
|
|
{
|
|
DValue capture;
|
|
PCRE2_SIZE start = ovector[i * 2];
|
|
PCRE2_SIZE end = ovector[i * 2 + 1];
|
|
capture["index"] = (f64)i;
|
|
capture["matched"].set_bool(start != PCRE2_UNSET && end != PCRE2_UNSET);
|
|
if(start != PCRE2_UNSET && end != PCRE2_UNSET)
|
|
{
|
|
capture["start"] = (f64)start;
|
|
capture["end"] = (f64)end;
|
|
capture["text"] = regex_subject_slice(subject, start, end);
|
|
}
|
|
result["captures"].push(capture);
|
|
}
|
|
|
|
regex_add_named_captures(result, code, subject, ovector, rc);
|
|
return(result);
|
|
}
|
|
|
|
int regex_match_at(RegexCode& regex, RegexMatchData& match_data, String subject, size_t offset, uint32_t options, String function_name)
|
|
{
|
|
int rc = pcre2_match(
|
|
regex.code,
|
|
reinterpret_cast<PCRE2_SPTR>(subject.c_str()),
|
|
subject.length(),
|
|
offset,
|
|
options,
|
|
match_data.data,
|
|
0
|
|
);
|
|
|
|
if(rc == PCRE2_ERROR_NOMATCH)
|
|
return(rc);
|
|
if(rc < 0)
|
|
regex_throw(function_name, "match failed: " + regex_pcre2_error(rc));
|
|
return(rc);
|
|
}
|
|
|
|
}
|
|
|
|
bool regex_match(String pattern, String subject, String flags)
|
|
{
|
|
RegexCode regex(pattern, flags, "regex_match");
|
|
RegexMatchData match_data(regex.code);
|
|
int rc = regex_match_at(regex, match_data, subject, 0, PCRE2_ANCHORED | PCRE2_ENDANCHORED, "regex_match");
|
|
return(rc >= 0);
|
|
}
|
|
|
|
DValue regex_search(String pattern, String subject, String flags)
|
|
{
|
|
RegexCode regex(pattern, flags, "regex_search");
|
|
RegexMatchData match_data(regex.code);
|
|
int rc = regex_match_at(regex, match_data, subject, 0, 0, "regex_search");
|
|
return(regex_build_match_tree(pattern, flags, subject, regex.code, match_data.data, rc));
|
|
}
|
|
|
|
DValue regex_search_all(String pattern, String subject, String flags)
|
|
{
|
|
RegexCode regex(pattern, flags, "regex_search_all");
|
|
RegexMatchData match_data(regex.code);
|
|
DValue result;
|
|
result["matched"].set_bool(false);
|
|
result["pattern"] = pattern;
|
|
result["flags"] = regex_flags_label(flags);
|
|
|
|
size_t offset = 0;
|
|
while(offset <= subject.length())
|
|
{
|
|
int rc = regex_match_at(regex, match_data, subject, offset, 0, "regex_search_all");
|
|
if(rc == PCRE2_ERROR_NOMATCH)
|
|
break;
|
|
|
|
DValue match = regex_build_match_tree(pattern, flags, subject, regex.code, match_data.data, rc);
|
|
result["matches"].push(match);
|
|
result["matched"].set_bool(true);
|
|
|
|
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data.data);
|
|
size_t start = ovector[0];
|
|
size_t end = ovector[1];
|
|
if(end > offset)
|
|
offset = end;
|
|
else
|
|
offset = regex_next_utf8_offset(subject, offset);
|
|
}
|
|
|
|
result["count"] = (f64)result["matches"].deref()._map.size();
|
|
return(result);
|
|
}
|
|
|
|
String regex_replace(String pattern, String replacement, String subject, String flags)
|
|
{
|
|
RegexCode regex(pattern, flags, "regex_replace");
|
|
PCRE2_SIZE output_length = 0;
|
|
uint32_t options = PCRE2_SUBSTITUTE_GLOBAL | PCRE2_SUBSTITUTE_OVERFLOW_LENGTH;
|
|
|
|
int rc = pcre2_substitute(
|
|
regex.code,
|
|
reinterpret_cast<PCRE2_SPTR>(subject.c_str()),
|
|
subject.length(),
|
|
0,
|
|
options,
|
|
0,
|
|
0,
|
|
reinterpret_cast<PCRE2_SPTR>(replacement.c_str()),
|
|
replacement.length(),
|
|
0,
|
|
&output_length
|
|
);
|
|
|
|
if(rc != PCRE2_ERROR_NOMEMORY && rc < 0)
|
|
regex_throw("regex_replace", "substitution failed: " + regex_pcre2_error(rc));
|
|
|
|
String output;
|
|
output.resize(output_length);
|
|
rc = pcre2_substitute(
|
|
regex.code,
|
|
reinterpret_cast<PCRE2_SPTR>(subject.c_str()),
|
|
subject.length(),
|
|
0,
|
|
PCRE2_SUBSTITUTE_GLOBAL,
|
|
0,
|
|
0,
|
|
reinterpret_cast<PCRE2_SPTR>(replacement.c_str()),
|
|
replacement.length(),
|
|
reinterpret_cast<PCRE2_UCHAR*>(&output[0]),
|
|
&output_length
|
|
);
|
|
|
|
if(rc < 0)
|
|
regex_throw("regex_replace", "substitution failed: " + regex_pcre2_error(rc));
|
|
|
|
output.resize(output_length);
|
|
return(output);
|
|
}
|
|
|
|
StringList regex_split(String pattern, String subject, String flags)
|
|
{
|
|
RegexCode regex(pattern, flags, "regex_split");
|
|
RegexMatchData match_data(regex.code);
|
|
StringList result;
|
|
|
|
size_t offset = 0;
|
|
size_t last_end = 0;
|
|
while(offset <= subject.length())
|
|
{
|
|
int rc = regex_match_at(regex, match_data, subject, offset, 0, "regex_split");
|
|
if(rc == PCRE2_ERROR_NOMATCH)
|
|
break;
|
|
|
|
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data.data);
|
|
size_t start = ovector[0];
|
|
size_t end = ovector[1];
|
|
result.push_back(subject.substr(last_end, start - last_end));
|
|
last_end = end;
|
|
|
|
if(end > offset)
|
|
offset = end;
|
|
else
|
|
offset = regex_next_utf8_offset(subject, offset);
|
|
}
|
|
|
|
result.push_back(subject.substr(last_end));
|
|
return(result);
|
|
}
|
|
|
|
#else
|
|
|
|
bool regex_match(String pattern, String subject, String flags)
|
|
{
|
|
(void)pattern; (void)subject; (void)flags;
|
|
return(false);
|
|
}
|
|
|
|
DValue regex_search(String pattern, String subject, String flags)
|
|
{
|
|
DValue result;
|
|
result["matched"].set_bool(false);
|
|
result["pattern"] = pattern;
|
|
result["flags"] = flags == "" ? "default" : flags;
|
|
return(result);
|
|
}
|
|
|
|
DValue regex_search_all(String pattern, String subject, String flags)
|
|
{
|
|
DValue result;
|
|
result["matched"].set_bool(false);
|
|
result["pattern"] = pattern;
|
|
result["flags"] = flags == "" ? "default" : flags;
|
|
result["count"] = (f64)0;
|
|
return(result);
|
|
}
|
|
|
|
String regex_replace(String pattern, String replacement, String subject, String flags)
|
|
{
|
|
(void)pattern; (void)replacement; (void)flags;
|
|
return(subject);
|
|
}
|
|
|
|
StringList regex_split(String pattern, String subject, String flags)
|
|
{
|
|
(void)pattern; (void)flags;
|
|
return(StringList{ subject });
|
|
}
|
|
|
|
#endif
|
|
|
|
String trim(String raw)
|
|
{
|
|
s64 len = raw.length();
|
|
s64 start_pos = 0;
|
|
s64 end_pos = len - 1;
|
|
if(len == 0 || (len == 1 && isspace((unsigned char)raw[0])))
|
|
return("");
|
|
while(start_pos < len && isspace((unsigned char)raw[start_pos]))
|
|
start_pos++;
|
|
while(end_pos >= 0 && isspace((unsigned char)raw[end_pos]))
|
|
end_pos--;
|
|
if(end_pos < start_pos)
|
|
return("");
|
|
return(raw.substr(start_pos, 1 + end_pos - start_pos));
|
|
}
|
|
|
|
StringList split_space(String str)
|
|
{
|
|
StringList result;
|
|
String current_token = "";
|
|
for(auto c : str)
|
|
{
|
|
if(isspace((unsigned char)c))
|
|
{
|
|
if(current_token != "")
|
|
{
|
|
result.push_back(current_token);
|
|
current_token = "";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
current_token.append(1, c);
|
|
}
|
|
}
|
|
if(current_token != "")
|
|
result.push_back(current_token);
|
|
return(result);
|
|
}
|
|
|
|
StringList split(String str, String delim)
|
|
{
|
|
StringList result;
|
|
if(delim == "")
|
|
{
|
|
result.push_back(str);
|
|
return(result);
|
|
}
|
|
int start = 0;
|
|
int end = str.find(delim);
|
|
while (end != String::npos)
|
|
{
|
|
result.push_back(str.substr(start, end - start));
|
|
start = end + delim.size();
|
|
end = str.find(delim, start);
|
|
}
|
|
result.push_back(str.substr(start, end - start));
|
|
return(result);
|
|
}
|
|
|
|
StringMap split_kv(String s, char separator, bool trim_whitespace, bool uppercase_keys)
|
|
{
|
|
StringMap result;
|
|
String k;
|
|
String v;
|
|
for(auto s : split(s, "\n"))
|
|
{
|
|
u8 mode = 0;
|
|
k = "";
|
|
v = "";
|
|
if(s == "" || s[0] == '#')
|
|
continue;
|
|
for(auto c : s)
|
|
{
|
|
if(mode == 0)
|
|
{
|
|
if(c == separator)
|
|
mode = 1;
|
|
else
|
|
k.append(1, uppercase_keys ? toupper((unsigned char)c) : c);
|
|
}
|
|
else
|
|
{
|
|
v.append(1, c);
|
|
}
|
|
}
|
|
if(k != "")
|
|
{
|
|
if(trim_whitespace)
|
|
result[trim(k)] = trim(v);
|
|
else
|
|
result[k] = v;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
StringMap split_http_headers(String s)
|
|
{
|
|
StringMap result;
|
|
StringList lines = split(s, "\n");
|
|
if(lines.size() == 0)
|
|
return(result);
|
|
|
|
u64 header_start = 0;
|
|
while(header_start < lines.size() && trim(lines[header_start]) == "")
|
|
header_start++;
|
|
|
|
// A header line has its colon before any whitespace ("Host: x"); a request
|
|
// line has a space first even when the URI contains colons
|
|
// ("GET /x.uce?t=12:30 HTTP/1.1").
|
|
bool first_line_is_header = false;
|
|
if(header_start < lines.size())
|
|
{
|
|
String first_line = trim(lines[header_start]);
|
|
size_t colon = first_line.find(':');
|
|
size_t space = first_line.find_first_of(" \t");
|
|
first_line_is_header = colon != String::npos && (space == String::npos || colon < space);
|
|
}
|
|
|
|
if(header_start < lines.size() && !first_line_is_header)
|
|
{
|
|
String request_line = trim(lines[header_start]);
|
|
result["REQUEST_METHOD"] = nibble(request_line, " ");
|
|
result["REQUEST_URI"] = nibble(request_line, " ");
|
|
String query_string = result["REQUEST_URI"];
|
|
String base_uri = nibble(query_string, "?");
|
|
result["SERVER_PROTOCOL"] = trim(request_line);
|
|
result["SCRIPT_NAME"] = base_uri;
|
|
result["DOCUMENT_URI"] = base_uri;
|
|
result["QUERY_STRING"] = query_string;
|
|
header_start++;
|
|
}
|
|
|
|
for(u64 i = header_start; i < lines.size(); i++)
|
|
{
|
|
String line = lines[i];
|
|
size_t colon = line.find(':');
|
|
if(colon == String::npos)
|
|
continue;
|
|
String header_key = to_upper(trim(line.substr(0, colon)));
|
|
String value = trim(line.substr(colon + 1));
|
|
if(header_key == "")
|
|
continue;
|
|
std::replace(header_key.begin(), header_key.end(), '-', '_');
|
|
result["HTTP_" + header_key] = value;
|
|
if(header_key == "CONTENT_TYPE" || header_key == "CONTENT_LENGTH")
|
|
result[header_key] = value;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
String join(StringList l, String delim)
|
|
{
|
|
String result;
|
|
u32 i = 0;
|
|
for(auto& s : l)
|
|
{
|
|
if(i > 0)
|
|
result.append(delim);
|
|
result.append(s);
|
|
i += 1;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool array_merge_key_is_index(String key)
|
|
{
|
|
if(key == "")
|
|
return(false);
|
|
for(auto c : key)
|
|
{
|
|
if(!isdigit(c))
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
}
|
|
|
|
StringMap array_merge(StringMap a, StringMap b)
|
|
{
|
|
StringMap result = a;
|
|
for(const auto& entry : b)
|
|
result[entry.first] = entry.second;
|
|
return(result);
|
|
}
|
|
|
|
DValue array_merge(DValue a, DValue b)
|
|
{
|
|
const DValue& left = a.deref();
|
|
const DValue& right = b.deref();
|
|
if(left.type != 'M')
|
|
return(b);
|
|
|
|
DValue result;
|
|
result.set(a);
|
|
if(right.type != 'M')
|
|
return(result);
|
|
|
|
bool append_numeric_keys = left.is_list() || right.is_list();
|
|
for(const auto& entry : right._map)
|
|
{
|
|
if(append_numeric_keys && array_merge_key_is_index(entry.first))
|
|
{
|
|
DValue child = entry.second;
|
|
result.push(child);
|
|
}
|
|
else
|
|
{
|
|
result[entry.first] = entry.second;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
StringList split_utf8(String s, bool compound_characters)
|
|
{
|
|
StringList result;
|
|
auto len = s.size();
|
|
String codepoint = "";
|
|
for(s64 i = 0; i < len; i++)
|
|
{
|
|
u8 c = s[i];
|
|
if(is_bit_set(c, 7))
|
|
{
|
|
codepoint = "";
|
|
codepoint.append(1, c);
|
|
if(is_bit_set(c, 6))
|
|
{
|
|
codepoint.append(1, s[++i]);
|
|
if(is_bit_set(c, 5))
|
|
{
|
|
codepoint.append(1, s[++i]);
|
|
if(is_bit_set(c, 4))
|
|
{
|
|
codepoint.append(1, s[++i]);
|
|
}
|
|
}
|
|
}
|
|
result.push_back(codepoint);
|
|
}
|
|
else
|
|
{
|
|
result.push_back(String().append(1, c));
|
|
}
|
|
}
|
|
if(compound_characters)
|
|
{
|
|
StringList compound_result;
|
|
bool join_next = false;
|
|
bool last_was_regional = false;
|
|
for(auto& s : result)
|
|
{
|
|
if(join_next)
|
|
{
|
|
compound_result[compound_result.size()-1] += s;
|
|
join_next = false;
|
|
}
|
|
else if(s == "\xE2" "\x80" "\x8D") // ZWJ
|
|
{
|
|
compound_result[compound_result.size()-1] += s;
|
|
join_next = true;
|
|
last_was_regional = false;
|
|
}
|
|
else if(s[0] == '\xF0' && s[1] == '\x9F' && s[2] == '\x87' && s[3] >= '\xA6' && s[3] <= '\xBF') // Regional indicator letters
|
|
{
|
|
if(last_was_regional)
|
|
{
|
|
compound_result[compound_result.size()-1] += s;
|
|
last_was_regional = false;
|
|
}
|
|
else
|
|
{
|
|
compound_result.push_back(s);
|
|
last_was_regional = true;
|
|
}
|
|
}
|
|
else if(s[0] == '\xEF' && s[1] == '\xB8' && s[2] >= '\x80' && s[2] <= '\x8F') // Variation selector
|
|
{
|
|
compound_result[compound_result.size()-1] += s;
|
|
last_was_regional = false;
|
|
}
|
|
else
|
|
{
|
|
compound_result.push_back(s);
|
|
last_was_regional = false;
|
|
}
|
|
}
|
|
return(compound_result);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
String html_escape(String s)
|
|
{
|
|
String result;
|
|
|
|
for(u32 i = 0; i < s.length(); i++)
|
|
{
|
|
char c = s[i];
|
|
switch(c)
|
|
{
|
|
case('&'):
|
|
result.append("&");
|
|
break;
|
|
case('<'):
|
|
result.append("<");
|
|
break;
|
|
case('>'):
|
|
result.append(">");
|
|
break;
|
|
case('"'):
|
|
result.append(""");
|
|
break;
|
|
case('\''):
|
|
result.append("'");
|
|
break;
|
|
default:
|
|
result.append(1, c);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
String html_escape(u64 a)
|
|
{
|
|
return(std::to_string(a));
|
|
}
|
|
|
|
String html_escape(f64 a)
|
|
{
|
|
return(std::to_string(a));
|
|
}
|
|
|
|
String json_encode(String s, char quote_char)
|
|
{
|
|
return(json_escape(s, quote_char));
|
|
}
|
|
|
|
u64 int_val(String s, u32 base)
|
|
{
|
|
return(strtol(s.c_str(), 0, base));
|
|
}
|
|
|
|
f64 float_val(String s)
|
|
{
|
|
return(strtod(s.c_str(), 0));
|
|
}
|
|
|
|
String nibble(String& haystack, String delim)
|
|
{
|
|
auto idx = haystack.find(delim);
|
|
if(idx == String::npos)
|
|
{
|
|
String result = haystack;
|
|
haystack = "";
|
|
return(result);
|
|
}
|
|
else
|
|
{
|
|
String result = haystack.substr(0, idx);
|
|
haystack = haystack.substr(idx+delim.length());
|
|
return(result);
|
|
}
|
|
}
|
|
|
|
String json_encode(DValue t, char quote_char)
|
|
{
|
|
String result = "";
|
|
if(t.is_array())
|
|
{
|
|
if(t.is_list())
|
|
{
|
|
result += "[";
|
|
u32 count = 0;
|
|
for(u32 i = 0; i < t.deref()._map.size(); i += 1)
|
|
{
|
|
if(count > 0)
|
|
result += ", ";
|
|
count += 1;
|
|
auto it = t.deref()._map.find(std::to_string(i));
|
|
if(it == t.deref()._map.end())
|
|
{
|
|
result += "null";
|
|
continue;
|
|
}
|
|
result += json_encode(it->second, quote_char);
|
|
}
|
|
result += "]";
|
|
}
|
|
else
|
|
{
|
|
result += "{";
|
|
u32 count = 0;
|
|
t.each([&] (DValue item, String key) {
|
|
if(count > 0)
|
|
result += ", ";
|
|
count += 1;
|
|
result += json_escape(key, quote_char) + ": " + json_encode(item, quote_char);
|
|
});
|
|
result += "}";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = t.to_json(quote_char);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
namespace {
|
|
|
|
String xml_escape_text(String s)
|
|
{
|
|
String result;
|
|
for(char c : s)
|
|
{
|
|
switch(c)
|
|
{
|
|
case('&'):
|
|
result += "&";
|
|
break;
|
|
case('<'):
|
|
result += "<";
|
|
break;
|
|
case('>'):
|
|
result += ">";
|
|
break;
|
|
default:
|
|
result.append(1, c);
|
|
break;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
String xml_escape_attr(String s)
|
|
{
|
|
String result;
|
|
for(char c : s)
|
|
{
|
|
switch(c)
|
|
{
|
|
case('&'):
|
|
result += "&";
|
|
break;
|
|
case('<'):
|
|
result += "<";
|
|
break;
|
|
case('>'):
|
|
result += ">";
|
|
break;
|
|
case('"'):
|
|
result += """;
|
|
break;
|
|
case('\''):
|
|
result += "'";
|
|
break;
|
|
default:
|
|
result.append(1, c);
|
|
break;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
bool xml_name_start_char(char c)
|
|
{
|
|
return(isalpha((unsigned char)c) || c == '_' || c == ':');
|
|
}
|
|
|
|
bool xml_name_char(char c)
|
|
{
|
|
return(isalnum((unsigned char)c) || c == '_' || c == '-' || c == '.' || c == ':');
|
|
}
|
|
|
|
String xml_safe_name(String raw, String fallback = "item")
|
|
{
|
|
raw = trim(raw);
|
|
if(raw == "")
|
|
return(fallback);
|
|
|
|
String result;
|
|
for(char c : raw)
|
|
{
|
|
if(xml_name_char(c))
|
|
result.append(1, c);
|
|
else
|
|
result.append(1, '_');
|
|
}
|
|
|
|
if(result == "" || !xml_name_start_char(result[0]))
|
|
result = "_" + result;
|
|
return(result);
|
|
}
|
|
|
|
String xml_tree_scalar_text(DValue t)
|
|
{
|
|
const DValue& target = t.deref();
|
|
if(target.type == 'B')
|
|
return(target._bool ? "true" : "false");
|
|
return(t.to_string());
|
|
}
|
|
|
|
const DValue* xml_tree_key(const DValue& t, String key)
|
|
{
|
|
return(t.deref().key(key));
|
|
}
|
|
|
|
bool xml_tree_has_element_shape(const DValue& t)
|
|
{
|
|
const DValue& target = t.deref();
|
|
return(target.type == 'M' && target.key("name"));
|
|
}
|
|
|
|
String xml_encode_element(DValue tree, String name_hint)
|
|
{
|
|
const DValue& target = tree.deref();
|
|
bool shaped = xml_tree_has_element_shape(target);
|
|
String name = xml_safe_name(name_hint, "root");
|
|
|
|
if(shaped)
|
|
{
|
|
const DValue* name_node = xml_tree_key(target, "name");
|
|
DValue name_copy = *name_node;
|
|
name = xml_safe_name(name_copy.to_string(), name);
|
|
}
|
|
|
|
String result = "<" + name;
|
|
|
|
if(shaped)
|
|
{
|
|
const DValue* attrs = xml_tree_key(target, "attrs");
|
|
if(attrs && attrs->deref().type == 'M')
|
|
{
|
|
for(const auto& entry : attrs->deref()._map)
|
|
{
|
|
DValue attr_value = entry.second;
|
|
result += " " + xml_safe_name(entry.first, "attr") + "=\"" + xml_escape_attr(xml_tree_scalar_text(attr_value)) + "\"";
|
|
}
|
|
}
|
|
}
|
|
|
|
String content;
|
|
if(shaped)
|
|
{
|
|
const DValue* text = xml_tree_key(target, "text");
|
|
if(text)
|
|
{
|
|
DValue text_copy = *text;
|
|
content += xml_escape_text(xml_tree_scalar_text(text_copy));
|
|
}
|
|
|
|
const DValue* children = xml_tree_key(target, "children");
|
|
if(children && children->deref().type == 'M')
|
|
{
|
|
const DValue& child_map = children->deref();
|
|
if(child_map.is_list())
|
|
{
|
|
for(u32 i = 0; i < child_map._map.size(); i += 1)
|
|
{
|
|
auto it = child_map._map.find(std::to_string(i));
|
|
if(it != child_map._map.end())
|
|
content += xml_encode_element(it->second, "item");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(const auto& entry : child_map._map)
|
|
content += xml_encode_element(entry.second, entry.first);
|
|
}
|
|
}
|
|
}
|
|
else if(target.type == 'M')
|
|
{
|
|
if(target.is_list())
|
|
{
|
|
for(u32 i = 0; i < target._map.size(); i += 1)
|
|
{
|
|
auto it = target._map.find(std::to_string(i));
|
|
if(it != target._map.end())
|
|
content += xml_encode_element(it->second, "item");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(const auto& entry : target._map)
|
|
content += xml_encode_element(entry.second, entry.first);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
content += xml_escape_text(xml_tree_scalar_text(tree));
|
|
}
|
|
|
|
if(content == "")
|
|
return(result + "/>");
|
|
return(result + ">" + content + "</" + name + ">");
|
|
}
|
|
|
|
String xml_utf8_from_codepoint(u64 codepoint)
|
|
{
|
|
String result;
|
|
if(codepoint == 0 || codepoint > 0x10ffff || (codepoint >= 0xd800 && codepoint <= 0xdfff))
|
|
return(result);
|
|
if(codepoint <= 0x7f)
|
|
{
|
|
result.append(1, (char)codepoint);
|
|
}
|
|
else if(codepoint <= 0x7ff)
|
|
{
|
|
result.append(1, (char)(0xc0 | (codepoint >> 6)));
|
|
result.append(1, (char)(0x80 | (codepoint & 0x3f)));
|
|
}
|
|
else if(codepoint <= 0xffff)
|
|
{
|
|
result.append(1, (char)(0xe0 | (codepoint >> 12)));
|
|
result.append(1, (char)(0x80 | ((codepoint >> 6) & 0x3f)));
|
|
result.append(1, (char)(0x80 | (codepoint & 0x3f)));
|
|
}
|
|
else
|
|
{
|
|
result.append(1, (char)(0xf0 | (codepoint >> 18)));
|
|
result.append(1, (char)(0x80 | ((codepoint >> 12) & 0x3f)));
|
|
result.append(1, (char)(0x80 | ((codepoint >> 6) & 0x3f)));
|
|
result.append(1, (char)(0x80 | (codepoint & 0x3f)));
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
String xml_decode_entity(String entity)
|
|
{
|
|
if(entity == "amp")
|
|
return("&");
|
|
if(entity == "lt")
|
|
return("<");
|
|
if(entity == "gt")
|
|
return(">");
|
|
if(entity == "quot")
|
|
return("\"");
|
|
if(entity == "apos")
|
|
return("'");
|
|
if(entity.length() > 1 && entity[0] == '#')
|
|
{
|
|
u32 base = 10;
|
|
String digits = entity.substr(1);
|
|
if(digits.length() > 1 && (digits[0] == 'x' || digits[0] == 'X'))
|
|
{
|
|
base = 16;
|
|
digits = digits.substr(1);
|
|
}
|
|
u64 codepoint = int_val(digits, base);
|
|
String utf8 = xml_utf8_from_codepoint(codepoint);
|
|
if(utf8 != "")
|
|
return(utf8);
|
|
}
|
|
return("&" + entity + ";");
|
|
}
|
|
|
|
String xml_decode_text(String s)
|
|
{
|
|
String result;
|
|
for(u32 i = 0; i < s.length(); i += 1)
|
|
{
|
|
if(s[i] == '&')
|
|
{
|
|
auto end = s.find(";", i + 1);
|
|
if(end != String::npos)
|
|
{
|
|
result += xml_decode_entity(s.substr(i + 1, end - i - 1));
|
|
i = end;
|
|
continue;
|
|
}
|
|
}
|
|
result.append(1, s[i]);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
void xml_throw(String message)
|
|
{
|
|
#ifdef __UCE_WASM_CORE__
|
|
(void)message;
|
|
__builtin_trap();
|
|
#else
|
|
throw std::runtime_error("xml_decode(): " + message);
|
|
#endif
|
|
}
|
|
|
|
struct XmlParser
|
|
{
|
|
String src;
|
|
u32 i = 0;
|
|
|
|
XmlParser(String source)
|
|
{
|
|
src = source;
|
|
}
|
|
|
|
bool starts_with(String token)
|
|
{
|
|
return(src.compare(i, token.length(), token) == 0);
|
|
}
|
|
|
|
void skip_space()
|
|
{
|
|
while(i < src.length() && isspace((unsigned char)src[i]))
|
|
i += 1;
|
|
}
|
|
|
|
void skip_until(String token)
|
|
{
|
|
auto end = src.find(token, i);
|
|
if(end == String::npos)
|
|
{
|
|
i = src.length();
|
|
return;
|
|
}
|
|
i = end + token.length();
|
|
}
|
|
|
|
void skip_misc()
|
|
{
|
|
bool again = true;
|
|
while(again)
|
|
{
|
|
again = false;
|
|
skip_space();
|
|
if(starts_with("<?"))
|
|
{
|
|
skip_until("?>");
|
|
again = true;
|
|
}
|
|
else if(starts_with("<!--"))
|
|
{
|
|
skip_until("-->");
|
|
again = true;
|
|
}
|
|
else if(starts_with("<!DOCTYPE") || starts_with("<!doctype"))
|
|
{
|
|
skip_until(">");
|
|
again = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
String read_name()
|
|
{
|
|
if(i >= src.length() || !xml_name_start_char(src[i]))
|
|
xml_throw("expected XML name at byte " + std::to_string((u64)i));
|
|
u32 start = i;
|
|
i += 1;
|
|
while(i < src.length() && xml_name_char(src[i]))
|
|
i += 1;
|
|
return(src.substr(start, i - start));
|
|
}
|
|
|
|
String read_quoted_value()
|
|
{
|
|
skip_space();
|
|
if(i >= src.length() || (src[i] != '"' && src[i] != '\''))
|
|
xml_throw("expected quoted attribute value at byte " + std::to_string((u64)i));
|
|
char quote = src[i];
|
|
i += 1;
|
|
u32 start = i;
|
|
while(i < src.length() && src[i] != quote)
|
|
i += 1;
|
|
if(i >= src.length())
|
|
xml_throw("unterminated attribute value");
|
|
String value = src.substr(start, i - start);
|
|
i += 1;
|
|
return(xml_decode_text(value));
|
|
}
|
|
|
|
String read_text_until_markup()
|
|
{
|
|
u32 start = i;
|
|
while(i < src.length() && src[i] != '<')
|
|
i += 1;
|
|
return(xml_decode_text(src.substr(start, i - start)));
|
|
}
|
|
|
|
DValue parse_element()
|
|
{
|
|
if(i >= src.length() || src[i] != '<')
|
|
xml_throw("expected element at byte " + std::to_string((u64)i));
|
|
i += 1;
|
|
|
|
DValue node;
|
|
String name = read_name();
|
|
node["name"] = name;
|
|
|
|
while(i < src.length())
|
|
{
|
|
skip_space();
|
|
if(starts_with("/>"))
|
|
{
|
|
i += 2;
|
|
return(node);
|
|
}
|
|
if(starts_with(">"))
|
|
{
|
|
i += 1;
|
|
break;
|
|
}
|
|
|
|
String attr_name = read_name();
|
|
skip_space();
|
|
if(i >= src.length() || src[i] != '=')
|
|
xml_throw("expected '=' after attribute " + attr_name);
|
|
i += 1;
|
|
node["attrs"][attr_name] = read_quoted_value();
|
|
}
|
|
|
|
String text;
|
|
while(i < src.length())
|
|
{
|
|
if(starts_with("</"))
|
|
{
|
|
i += 2;
|
|
String close_name = read_name();
|
|
skip_space();
|
|
if(i >= src.length() || src[i] != '>')
|
|
xml_throw("expected '>' after closing tag " + close_name);
|
|
i += 1;
|
|
if(close_name != name)
|
|
xml_throw("mismatched closing tag: expected " + name + " but found " + close_name);
|
|
if(trim(text) != "")
|
|
node["text"] = text;
|
|
return(node);
|
|
}
|
|
|
|
if(starts_with("<!--"))
|
|
{
|
|
skip_until("-->");
|
|
continue;
|
|
}
|
|
|
|
if(starts_with("<![CDATA["))
|
|
{
|
|
i += 9;
|
|
auto end = src.find("]]>", i);
|
|
if(end == String::npos)
|
|
xml_throw("unterminated CDATA section");
|
|
text += src.substr(i, end - i);
|
|
i = end + 3;
|
|
continue;
|
|
}
|
|
|
|
if(starts_with("<?"))
|
|
{
|
|
skip_until("?>");
|
|
continue;
|
|
}
|
|
|
|
if(src[i] == '<')
|
|
{
|
|
DValue child = parse_element();
|
|
node["children"].push(child);
|
|
continue;
|
|
}
|
|
|
|
String chunk = read_text_until_markup();
|
|
if(trim(chunk) != "")
|
|
text += chunk;
|
|
}
|
|
|
|
xml_throw("missing closing tag for " + name);
|
|
return(node);
|
|
}
|
|
|
|
DValue parse_document()
|
|
{
|
|
skip_misc();
|
|
if(i >= src.length())
|
|
xml_throw("empty document");
|
|
DValue result = parse_element();
|
|
skip_misc();
|
|
if(i < src.length())
|
|
xml_throw("unexpected content after root element at byte " + std::to_string((u64)i));
|
|
return(result);
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
String xml_encode(DValue t, String root_name)
|
|
{
|
|
return(xml_encode_element(t, root_name));
|
|
}
|
|
|
|
DValue xml_decode(String s)
|
|
{
|
|
XmlParser parser(s);
|
|
return(parser.parse_document());
|
|
}
|
|
|
|
namespace {
|
|
|
|
String yaml_indent(u32 width)
|
|
{
|
|
return(String(width, ' '));
|
|
}
|
|
|
|
String yaml_rtrim(String raw)
|
|
{
|
|
while(raw.length() > 0)
|
|
{
|
|
char c = raw[raw.length() - 1];
|
|
if(c != ' ' && c != '\t' && c != '\r')
|
|
break;
|
|
raw.erase(raw.length() - 1);
|
|
}
|
|
return(raw);
|
|
}
|
|
|
|
u32 yaml_count_indent(String raw)
|
|
{
|
|
u32 result = 0;
|
|
while(result < raw.length() && raw[result] == ' ')
|
|
result += 1;
|
|
return(result);
|
|
}
|
|
|
|
bool yaml_is_plain_key(String key)
|
|
{
|
|
if(key == "")
|
|
return(false);
|
|
for(char c : key)
|
|
{
|
|
if(!(isalnum((unsigned char)c) || c == '_' || c == '-' || c == '.'))
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
bool yaml_is_list_item(String content)
|
|
{
|
|
return(content == "-" || str_starts_with(content, "- "));
|
|
}
|
|
|
|
bool yaml_is_number_like(String raw)
|
|
{
|
|
if(raw == "")
|
|
return(false);
|
|
char* end = 0;
|
|
strtod(raw.c_str(), &end);
|
|
if(end == raw.c_str())
|
|
return(false);
|
|
while(end && *end != 0)
|
|
{
|
|
if(!isspace((unsigned char)*end))
|
|
return(false);
|
|
end += 1;
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
bool yaml_plain_scalar_safe(String raw)
|
|
{
|
|
if(raw == "")
|
|
return(false);
|
|
String lower = to_lower(raw);
|
|
if(lower == "true" || lower == "false" || lower == "null" || lower == "~" || lower == "yes" || lower == "no" || lower == "on" || lower == "off")
|
|
return(false);
|
|
if(yaml_is_number_like(raw))
|
|
return(false);
|
|
if(raw[0] == '-' || raw[0] == '?' || raw[0] == ':' || raw[0] == '@' || raw[0] == '`' || raw[0] == '!' || raw[0] == '&' || raw[0] == '*' || raw[0] == '#')
|
|
return(false);
|
|
for(u32 i = 0; i < raw.length(); i += 1)
|
|
{
|
|
char c = raw[i];
|
|
if(c == '\n' || c == '\r' || c == '\t')
|
|
return(false);
|
|
if(c == '[' || c == ']' || c == '{' || c == '}' || c == ',' || c == '"' || c == '\'')
|
|
return(false);
|
|
if(c == '#')
|
|
{
|
|
if(i == 0 || isspace((unsigned char)raw[i - 1]))
|
|
return(false);
|
|
}
|
|
if(c == ':')
|
|
{
|
|
if(i + 1 >= raw.length() || isspace((unsigned char)raw[i + 1]))
|
|
return(false);
|
|
}
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
String yaml_double_quote(String raw)
|
|
{
|
|
String result = "\"";
|
|
for(char c : raw)
|
|
{
|
|
switch(c)
|
|
{
|
|
case('\\'):
|
|
result += "\\\\";
|
|
break;
|
|
case('"'):
|
|
result += "\\\"";
|
|
break;
|
|
case('\n'):
|
|
result += "\\n";
|
|
break;
|
|
case('\r'):
|
|
result += "\\r";
|
|
break;
|
|
case('\t'):
|
|
result += "\\t";
|
|
break;
|
|
default:
|
|
result.append(1, c);
|
|
break;
|
|
}
|
|
}
|
|
result += "\"";
|
|
return(result);
|
|
}
|
|
|
|
String yaml_quote_key(String key)
|
|
{
|
|
if(yaml_is_plain_key(key))
|
|
return(key);
|
|
return(yaml_double_quote(key));
|
|
}
|
|
|
|
String yaml_scalar_to_string(DValue t)
|
|
{
|
|
const DValue& target = t.deref();
|
|
switch(target.type)
|
|
{
|
|
case('B'):
|
|
return(target._bool ? "true" : "false");
|
|
case('F'):
|
|
{
|
|
String value = std::to_string(target._float);
|
|
if(value.find(".") != String::npos)
|
|
{
|
|
while(value.length() > 0 && value[value.length() - 1] == '0')
|
|
value.erase(value.length() - 1);
|
|
if(value.length() > 0 && value[value.length() - 1] == '.')
|
|
value.erase(value.length() - 1);
|
|
}
|
|
if(value == "-0")
|
|
value = "0";
|
|
return(value);
|
|
}
|
|
case('P'):
|
|
return(std::to_string((u64)target._ptr));
|
|
default:
|
|
return(t.to_string());
|
|
}
|
|
}
|
|
|
|
String yaml_encode_scalar(DValue t, u32 indent)
|
|
{
|
|
String raw = yaml_scalar_to_string(t);
|
|
const DValue& target = t.deref();
|
|
if(target.type == 'B' || target.type == 'F')
|
|
return(raw);
|
|
if(raw.find("\n") != String::npos)
|
|
{
|
|
String result = "|\n";
|
|
u32 start = 0;
|
|
while(start <= raw.length())
|
|
{
|
|
auto end = raw.find("\n", start);
|
|
String line = (end == String::npos) ? raw.substr(start) : raw.substr(start, end - start);
|
|
result += yaml_indent(indent + 2) + line + "\n";
|
|
if(end == String::npos)
|
|
break;
|
|
start = end + 1;
|
|
}
|
|
return(result.substr(0, result.length() - 1));
|
|
}
|
|
if(yaml_plain_scalar_safe(raw))
|
|
return(raw);
|
|
return(yaml_double_quote(raw));
|
|
}
|
|
|
|
String yaml_encode_node(DValue t, u32 indent)
|
|
{
|
|
const DValue& target = t.deref();
|
|
String result;
|
|
if(target.type == 'M')
|
|
{
|
|
if(target.is_list())
|
|
{
|
|
for(u32 i = 0; i < target._map.size(); i += 1)
|
|
{
|
|
auto it = target._map.find(std::to_string(i));
|
|
if(it == target._map.end())
|
|
continue;
|
|
const DValue& child = it->second.deref();
|
|
if(child.type == 'M')
|
|
{
|
|
result += yaml_indent(indent) + "-\n";
|
|
result += yaml_encode_node(it->second, indent + 2);
|
|
}
|
|
else
|
|
{
|
|
result += yaml_indent(indent) + "- " + yaml_encode_scalar(it->second, indent) + "\n";
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
for(const auto& entry : target._map)
|
|
{
|
|
const DValue& child = entry.second.deref();
|
|
result += yaml_indent(indent) + yaml_quote_key(entry.first) + ":";
|
|
if(child.type == 'M')
|
|
{
|
|
result += "\n" + yaml_encode_node(entry.second, indent + 2);
|
|
}
|
|
else
|
|
{
|
|
result += " " + yaml_encode_scalar(entry.second, indent) + "\n";
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
return(yaml_indent(indent) + yaml_encode_scalar(t, indent) + "\n");
|
|
}
|
|
|
|
String yaml_strip_comment(String raw)
|
|
{
|
|
bool in_single = false;
|
|
bool in_double = false;
|
|
bool escaped = false;
|
|
for(u32 i = 0; i < raw.length(); i += 1)
|
|
{
|
|
char c = raw[i];
|
|
if(in_double && escaped)
|
|
{
|
|
escaped = false;
|
|
continue;
|
|
}
|
|
if(in_double && c == '\\')
|
|
{
|
|
escaped = true;
|
|
continue;
|
|
}
|
|
if(!in_double && c == '\'')
|
|
in_single = !in_single;
|
|
else if(!in_single && c == '"')
|
|
in_double = !in_double;
|
|
else if(!in_single && !in_double && c == '#' && (i == 0 || isspace((unsigned char)raw[i - 1])))
|
|
return(yaml_rtrim(raw.substr(0, i)));
|
|
}
|
|
return(yaml_rtrim(raw));
|
|
}
|
|
|
|
String yaml_ltrim(String raw)
|
|
{
|
|
u32 i = 0;
|
|
while(i < raw.length() && (raw[i] == ' ' || raw[i] == '\t'))
|
|
i += 1;
|
|
return(raw.substr(i));
|
|
}
|
|
|
|
struct YamlLine
|
|
{
|
|
String raw;
|
|
String content;
|
|
u32 indent = 0;
|
|
bool empty = false;
|
|
};
|
|
|
|
String yaml_decode_quoted(String raw)
|
|
{
|
|
if(raw.length() < 2)
|
|
return(raw);
|
|
char quote = raw[0];
|
|
String body = raw.substr(1, raw.length() - 2);
|
|
if(quote == '\'')
|
|
return(replace(body, "''", "'"));
|
|
|
|
String result;
|
|
for(u32 i = 0; i < body.length(); i += 1)
|
|
{
|
|
char c = body[i];
|
|
if(c == '\\' && i + 1 < body.length())
|
|
{
|
|
i += 1;
|
|
switch(body[i])
|
|
{
|
|
case('n'):
|
|
result.append(1, '\n');
|
|
break;
|
|
case('r'):
|
|
result.append(1, '\r');
|
|
break;
|
|
case('t'):
|
|
result.append(1, '\t');
|
|
break;
|
|
case('"'):
|
|
result.append(1, '"');
|
|
break;
|
|
case('\\'):
|
|
result.append(1, '\\');
|
|
break;
|
|
default:
|
|
result.append(1, body[i]);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.append(1, c);
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
void yaml_throw(String message)
|
|
{
|
|
#ifdef __UCE_WASM_CORE__
|
|
(void)message;
|
|
__builtin_trap();
|
|
#else
|
|
throw std::runtime_error("yaml_decode(): " + message);
|
|
#endif
|
|
}
|
|
|
|
struct YamlParser
|
|
{
|
|
std::vector<YamlLine> lines;
|
|
u32 i = 0;
|
|
|
|
YamlParser(String source)
|
|
{
|
|
u32 start = 0;
|
|
while(start <= source.length())
|
|
{
|
|
auto end = source.find("\n", start);
|
|
String raw = (end == String::npos) ? source.substr(start) : source.substr(start, end - start);
|
|
if(end == String::npos && raw == "" && start == source.length())
|
|
break;
|
|
if(raw.length() > 0 && raw[raw.length() - 1] == '\r')
|
|
raw.erase(raw.length() - 1);
|
|
YamlLine line;
|
|
line.raw = raw;
|
|
line.indent = yaml_count_indent(raw);
|
|
line.content = yaml_ltrim(yaml_strip_comment(raw));
|
|
line.empty = (trim(line.content) == "");
|
|
lines.push_back(line);
|
|
if(end == String::npos)
|
|
break;
|
|
start = end + 1;
|
|
}
|
|
}
|
|
|
|
void skip_empty()
|
|
{
|
|
while(i < lines.size() && lines[i].empty)
|
|
i += 1;
|
|
}
|
|
|
|
bool split_key_value(String content, String& key, String& value)
|
|
{
|
|
bool in_single = false;
|
|
bool in_double = false;
|
|
bool escaped = false;
|
|
for(u32 pos = 0; pos < content.length(); pos += 1)
|
|
{
|
|
char c = content[pos];
|
|
if(in_double && escaped)
|
|
{
|
|
escaped = false;
|
|
continue;
|
|
}
|
|
if(in_double && c == '\\')
|
|
{
|
|
escaped = true;
|
|
continue;
|
|
}
|
|
if(!in_double && c == '\'')
|
|
in_single = !in_single;
|
|
else if(!in_single && c == '"')
|
|
in_double = !in_double;
|
|
else if(!in_single && !in_double && c == ':' && (pos + 1 >= content.length() || isspace((unsigned char)content[pos + 1])))
|
|
{
|
|
key = trim(content.substr(0, pos));
|
|
value = trim(content.substr(pos + 1));
|
|
if(key == "")
|
|
yaml_throw("empty mapping key at line " + std::to_string((u64)i + 1));
|
|
if((key[0] == '"' && key[key.length() - 1] == '"') || (key[0] == '\'' && key[key.length() - 1] == '\''))
|
|
key = yaml_decode_quoted(key);
|
|
return(true);
|
|
}
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
DValue parse_scalar(String value)
|
|
{
|
|
DValue result;
|
|
value = trim(value);
|
|
if(value == "")
|
|
{
|
|
result = "";
|
|
return(result);
|
|
}
|
|
if((value[0] == '"' && value[value.length() - 1] == '"') || (value[0] == '\'' && value[value.length() - 1] == '\''))
|
|
{
|
|
result = yaml_decode_quoted(value);
|
|
return(result);
|
|
}
|
|
String lower = to_lower(value);
|
|
if(lower == "true")
|
|
{
|
|
result.set_bool(true);
|
|
return(result);
|
|
}
|
|
if(lower == "false")
|
|
{
|
|
result.set_bool(false);
|
|
return(result);
|
|
}
|
|
if(lower == "null" || lower == "~")
|
|
{
|
|
result = "";
|
|
return(result);
|
|
}
|
|
result = value;
|
|
return(result);
|
|
}
|
|
|
|
String parse_block_scalar(u32 parent_indent, String style)
|
|
{
|
|
String literal;
|
|
bool found = false;
|
|
u32 content_indent = parent_indent + 2;
|
|
while(i < lines.size())
|
|
{
|
|
String raw = lines[i].raw;
|
|
if(trim(raw) == "")
|
|
{
|
|
if(found)
|
|
literal += "\n";
|
|
i += 1;
|
|
continue;
|
|
}
|
|
u32 indent = yaml_count_indent(raw);
|
|
if(indent <= parent_indent)
|
|
break;
|
|
if(!found)
|
|
{
|
|
content_indent = indent;
|
|
found = true;
|
|
}
|
|
if(indent < content_indent)
|
|
break;
|
|
String text = raw.substr(std::min((u32)raw.length(), content_indent));
|
|
literal += text + "\n";
|
|
i += 1;
|
|
}
|
|
if(literal.length() > 0)
|
|
literal.erase(literal.length() - 1);
|
|
if(style == ">")
|
|
{
|
|
String folded;
|
|
bool last_newline = false;
|
|
for(char c : literal)
|
|
{
|
|
if(c == '\n')
|
|
{
|
|
if(last_newline)
|
|
folded += "\n";
|
|
else
|
|
folded += " ";
|
|
last_newline = true;
|
|
}
|
|
else
|
|
{
|
|
folded.append(1, c);
|
|
last_newline = false;
|
|
}
|
|
}
|
|
return(folded);
|
|
}
|
|
return(literal);
|
|
}
|
|
|
|
DValue parse_value_after_line(String value, u32 parent_indent)
|
|
{
|
|
if(value == "|" || value == ">")
|
|
{
|
|
DValue result;
|
|
result = parse_block_scalar(parent_indent, value);
|
|
return(result);
|
|
}
|
|
if(value == "")
|
|
{
|
|
skip_empty();
|
|
if(i < lines.size() && lines[i].indent > parent_indent)
|
|
return(parse_block(lines[i].indent));
|
|
DValue result;
|
|
result = "";
|
|
return(result);
|
|
}
|
|
return(parse_scalar(value));
|
|
}
|
|
|
|
void merge_map(DValue& target, DValue source)
|
|
{
|
|
if(source.deref().type != 'M' || source.deref().is_list())
|
|
return;
|
|
for(const auto& entry : source.deref()._map)
|
|
target[entry.first] = entry.second;
|
|
}
|
|
|
|
DValue parse_list(u32 indent)
|
|
{
|
|
DValue result;
|
|
result.set_array();
|
|
while(i < lines.size())
|
|
{
|
|
skip_empty();
|
|
if(i >= lines.size() || lines[i].indent < indent)
|
|
break;
|
|
if(lines[i].indent != indent || !yaml_is_list_item(lines[i].content))
|
|
break;
|
|
|
|
String after = trim(lines[i].content.substr(1));
|
|
u32 line_indent = lines[i].indent;
|
|
i += 1;
|
|
|
|
DValue item;
|
|
if(after == "")
|
|
{
|
|
skip_empty();
|
|
if(i < lines.size() && lines[i].indent > line_indent)
|
|
item = parse_block(lines[i].indent);
|
|
else
|
|
item = "";
|
|
}
|
|
else
|
|
{
|
|
String key;
|
|
String value;
|
|
if(split_key_value(after, key, value))
|
|
{
|
|
item[key] = parse_value_after_line(value, line_indent);
|
|
skip_empty();
|
|
if(i < lines.size() && lines[i].indent > line_indent)
|
|
merge_map(item, parse_block(lines[i].indent));
|
|
}
|
|
else
|
|
{
|
|
item = parse_scalar(after);
|
|
}
|
|
}
|
|
result.push(item);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
DValue parse_map(u32 indent)
|
|
{
|
|
DValue result;
|
|
result.set_type('M');
|
|
while(i < lines.size())
|
|
{
|
|
skip_empty();
|
|
if(i >= lines.size() || lines[i].indent < indent)
|
|
break;
|
|
if(lines[i].indent != indent || yaml_is_list_item(lines[i].content))
|
|
break;
|
|
|
|
String key;
|
|
String value;
|
|
if(!split_key_value(lines[i].content, key, value))
|
|
{
|
|
if(result.deref()._map.size() == 0)
|
|
{
|
|
DValue scalar = parse_scalar(lines[i].content);
|
|
i += 1;
|
|
return(scalar);
|
|
}
|
|
yaml_throw("expected mapping entry at line " + std::to_string((u64)i + 1));
|
|
}
|
|
u32 line_indent = lines[i].indent;
|
|
i += 1;
|
|
result[key] = parse_value_after_line(value, line_indent);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
DValue parse_block(u32 indent)
|
|
{
|
|
skip_empty();
|
|
if(i >= lines.size())
|
|
{
|
|
DValue empty;
|
|
empty.set_type('M');
|
|
return(empty);
|
|
}
|
|
if(lines[i].indent < indent)
|
|
{
|
|
DValue empty;
|
|
empty.set_type('M');
|
|
return(empty);
|
|
}
|
|
if(yaml_is_list_item(lines[i].content))
|
|
return(parse_list(lines[i].indent));
|
|
return(parse_map(lines[i].indent));
|
|
}
|
|
|
|
DValue parse_document()
|
|
{
|
|
skip_empty();
|
|
if(i < lines.size() && lines[i].content == "---")
|
|
i += 1;
|
|
DValue result = parse_block(i < lines.size() ? lines[i].indent : 0);
|
|
skip_empty();
|
|
if(i < lines.size() && lines[i].content == "...")
|
|
i += 1;
|
|
skip_empty();
|
|
if(i < lines.size())
|
|
yaml_throw("unexpected content at line " + std::to_string((u64)i + 1));
|
|
return(result);
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
String yaml_encode(DValue t)
|
|
{
|
|
return(yaml_encode_node(t, 0));
|
|
}
|
|
|
|
DValue yaml_decode(String s)
|
|
{
|
|
YamlParser parser(s);
|
|
return(parser.parse_document());
|
|
}
|
|
|
|
// https://i.stack.imgur.com/SHLOB.gif
|
|
String json_decode_String(String s, u32& i, char termination_char)
|
|
{
|
|
String result;
|
|
//print("json_decode_String " + s.substr(i) + "\n");
|
|
while(i < s.length())
|
|
{
|
|
char c = s[i];
|
|
if(c == termination_char)
|
|
{
|
|
i += 1;
|
|
//print("json_decode_String = " + result + "\n");
|
|
return(result);
|
|
}
|
|
else if(c == '\\')
|
|
{
|
|
i += 1;
|
|
c = s[i];
|
|
switch(c)
|
|
{
|
|
case('t'):
|
|
result.append(1, '\t');
|
|
break;
|
|
case('n'):
|
|
result.append(1, '\n');
|
|
break;
|
|
case('r'):
|
|
result.append(1, '\r');
|
|
break;
|
|
case('\\'):
|
|
result.append(1, '\\');
|
|
break;
|
|
case('b'):
|
|
result.append(1, '\b');
|
|
break;
|
|
case('f'):
|
|
result.append(1, '\f');
|
|
break;
|
|
case('u'):
|
|
// todo decode
|
|
break;
|
|
default:
|
|
result.append(1, c);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.append(1, c);
|
|
}
|
|
i += 1;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
DValue json_decode_map(String s, u32& i);
|
|
DValue json_decode_array(String s, u32& i);
|
|
|
|
void json_consume_space(String s, u32& i)
|
|
{
|
|
while(i < s.length() && isspace(s[i]))
|
|
i += 1;
|
|
}
|
|
|
|
String json_decode_keyword(String s, u32& i)
|
|
{
|
|
String result;
|
|
json_consume_space(s, i);
|
|
while(i < s.length())
|
|
{
|
|
char c = s[i];
|
|
if(isalnum(c))
|
|
{
|
|
result.append(1, c);
|
|
}
|
|
else
|
|
{
|
|
return(result);
|
|
}
|
|
i += 1;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
String json_decode_number(String s, u32& i)
|
|
{
|
|
String result;
|
|
json_consume_space(s, i);
|
|
while(i < s.length())
|
|
{
|
|
char c = s[i];
|
|
if(isdigit(c) || c == '.')
|
|
{
|
|
result.append(1, c);
|
|
}
|
|
else
|
|
{
|
|
return(result);
|
|
}
|
|
i += 1;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
DValue json_decode_value(String s, u32& i)
|
|
{
|
|
DValue result;
|
|
String value = "";
|
|
json_consume_space(s, i);
|
|
char c = s[i];
|
|
//print("json_decode_value " + s.substr(i) + "\n");
|
|
if(c == '"' || c == '\'') // String value
|
|
{
|
|
result.type = 'S';
|
|
i += 1;
|
|
result._String = json_decode_String(s, i, s[i-1]);
|
|
return(result);
|
|
}
|
|
else if(isdigit(c))
|
|
{
|
|
result.type = 'S';
|
|
result._String = json_decode_number(s, i);
|
|
//result._float = stod(json_decode_number(s, i));
|
|
return(result);
|
|
}
|
|
else if(c == '{')
|
|
{
|
|
i += 1;
|
|
return(json_decode_map(s, i));
|
|
}
|
|
else if(c == '[')
|
|
{
|
|
i += 1;
|
|
return(json_decode_array(s, i));
|
|
}
|
|
else
|
|
{
|
|
value = json_decode_keyword(s, i);
|
|
if(value == "true")
|
|
result.set_bool(true);
|
|
else if(value == "false")
|
|
result.set_bool(false);
|
|
else if(value == "null")
|
|
result.set("");
|
|
return(result);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
DValue json_decode_map(String s, u32& i)
|
|
{
|
|
DValue result;
|
|
result.type = 'M';
|
|
String key = "";
|
|
json_consume_space(s, i);
|
|
//print("json_decode_map " + s.substr(i) + "\n");
|
|
while(i < s.length())
|
|
{
|
|
char c = s[i];
|
|
if(c == '}')
|
|
{
|
|
i += 1;
|
|
return(result);
|
|
}
|
|
else if(c == ',')
|
|
{
|
|
i += 1;
|
|
}
|
|
else if(c == '"' || c == '\'')
|
|
{
|
|
i += 1;
|
|
key = json_decode_String(s, i, s[i-1]);
|
|
json_consume_space(s, i);
|
|
if(s[i] != ':')
|
|
return(result); // malformed
|
|
i += 1;
|
|
DValue v = json_decode_value(s, i);
|
|
//result._map[key] = json_decode_value(s, i);
|
|
//print("KV " + key + " = " + to_String(v) + "\n");
|
|
//printf("map add %s (%c) \n", key.c_str(), s[i]);
|
|
result._map[key] = v;
|
|
}
|
|
else
|
|
{
|
|
// malformed
|
|
return(result);
|
|
}
|
|
json_consume_space(s, i);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
DValue json_decode_array(String s, u32& i)
|
|
{
|
|
DValue result;
|
|
result.set_array();
|
|
json_consume_space(s, i);
|
|
while(i < s.length())
|
|
{
|
|
char c = s[i];
|
|
if(c == ']')
|
|
{
|
|
i += 1;
|
|
return(result);
|
|
}
|
|
else if(c == ',')
|
|
{
|
|
i += 1;
|
|
}
|
|
else
|
|
{
|
|
DValue v = json_decode_value(s, i);
|
|
result.push(v);
|
|
}
|
|
json_consume_space(s, i);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
DValue json_decode(String s)
|
|
{
|
|
u32 i = 0;
|
|
return(json_decode_value(s, i));
|
|
}
|
|
|
|
void ob_start()
|
|
{
|
|
context->ob_start();
|
|
}
|
|
|
|
void ob_close()
|
|
{
|
|
delete context->ob;
|
|
context->ob_stack.pop_back();
|
|
if(context->ob_stack.size() == 0)
|
|
{
|
|
ob_start();
|
|
}
|
|
else
|
|
{
|
|
context->ob = context->ob_stack.back();
|
|
}
|
|
}
|
|
|
|
String ob_get()
|
|
{
|
|
return(context->ob->str());
|
|
}
|
|
|
|
String ob_get_close()
|
|
{
|
|
String result = context->ob->str();
|
|
ob_close();
|
|
return(result);
|
|
}
|
|
|
|
String safe_name(String raw)
|
|
{
|
|
return(ascii_safe_name(raw));
|
|
}
|
|
|
|
String ascii_safe_name(String raw)
|
|
{
|
|
String result = "";
|
|
for(auto c : raw)
|
|
{
|
|
if(isalnum(c) || c == '_')
|
|
result.append(1, c);
|
|
}
|
|
return(result);
|
|
}
|