#include "functionlib.h" #ifndef __UCE_WASM_CORE__ #define PCRE2_CODE_UNIT_WIDTH 8 #include #endif #include #include #include 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 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 f) { for(auto item : items) { if(f(item)) return(true); } return(false); } bool list_every(StringList items, std::function f) { for(auto item : items) { if(!f(item)) return(false); } return(true); } String list_find(StringList items, std::function 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 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 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 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 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(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(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(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(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(subject.c_str()), subject.length(), 0, options, 0, 0, reinterpret_cast(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(subject.c_str()), subject.length(), 0, PCRE2_SUBSTITUTE_GLOBAL, 0, 0, reinterpret_cast(replacement.c_str()), replacement.length(), reinterpret_cast(&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 + ""); } 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(""); again = true; } else if(starts_with(""); again = true; } else if(starts_with(""); 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("= 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(""); continue; } if(starts_with("", i); if(end == String::npos) xml_throw("unterminated CDATA section"); text += src.substr(i, end - i); i = end + 3; continue; } if(starts_with(""); 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 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); }