Public Access
G1 step 2: Gemini parsers (host-tested), and the memory decision (Q86)
lib/gemini: URLs split per RFC 3986 appendix B and resolved per section 5.2 (all 32 reference examples of 5.4 pass, with gemini:// for http://), the request form (no fragment, lower-case host, never an empty path), query encoding for input prompts, Saved Page paths; the response header (status, category, MIME type and parameters, 1024-byte meta limit); gemtext's line types; and display text for the Latin-1 fonts. 12 tests. Q86, decided after step 1: free heap stays above 40 KB in steady state and 20 KB during a handshake; a fetch won't start below 55 KB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
#include "gemtext.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace roro::gemini {
|
||||
|
||||
namespace {
|
||||
std::string trim(const std::string& s) {
|
||||
size_t a = s.find_first_not_of(" \t"), b = s.find_last_not_of(" \t");
|
||||
return a == std::string::npos ? "" : s.substr(a, b - a + 1);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<GemLine> parseGemtext(const std::string& document) {
|
||||
std::vector<GemLine> lines;
|
||||
bool pre = false;
|
||||
size_t start = 0;
|
||||
while (start < document.size()) {
|
||||
size_t end = document.find('\n', start);
|
||||
if (end == std::string::npos) end = document.size();
|
||||
std::string line = document.substr(start, end - start);
|
||||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
||||
start = end + 1;
|
||||
|
||||
GemLine g;
|
||||
if (line.compare(0, 3, "```") == 0) {
|
||||
g.type = LineType::PreToggle;
|
||||
g.text = trim(line.substr(3));
|
||||
pre = !pre;
|
||||
} else if (pre) {
|
||||
g.type = LineType::Preformatted;
|
||||
g.text = line;
|
||||
} else if (line.compare(0, 2, "=>") == 0) {
|
||||
g.type = LineType::Link;
|
||||
std::string rest = trim(line.substr(2));
|
||||
size_t gap = rest.find_first_of(" \t");
|
||||
g.url = rest.substr(0, gap);
|
||||
g.text = gap == std::string::npos ? "" : trim(rest.substr(gap));
|
||||
if (g.text.empty()) g.text = g.url;
|
||||
} else if (line.compare(0, 3, "###") == 0) {
|
||||
g.type = LineType::Heading3;
|
||||
g.text = trim(line.substr(3));
|
||||
} else if (line.compare(0, 2, "##") == 0) {
|
||||
g.type = LineType::Heading2;
|
||||
g.text = trim(line.substr(2));
|
||||
} else if (line.compare(0, 1, "#") == 0) {
|
||||
g.type = LineType::Heading1;
|
||||
g.text = trim(line.substr(1));
|
||||
} else if (line.compare(0, 2, "* ") == 0) {
|
||||
g.type = LineType::ListItem;
|
||||
g.text = trim(line.substr(2));
|
||||
} else if (line.compare(0, 1, ">") == 0) {
|
||||
g.type = LineType::Quote;
|
||||
g.text = trim(line.substr(1));
|
||||
} else {
|
||||
g.text = line;
|
||||
}
|
||||
lines.push_back(std::move(g));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
std::string displayText(const std::string& s) {
|
||||
std::string out;
|
||||
size_t column = 0;
|
||||
for (size_t i = 0; i < s.size();) {
|
||||
unsigned char c = static_cast<unsigned char>(s[i]);
|
||||
if (c == '\t') {
|
||||
do out += ' ';
|
||||
while (++column % 4);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (c < 0x80) {
|
||||
out += static_cast<char>(c);
|
||||
i++;
|
||||
} else {
|
||||
int len = (c & 0xE0) == 0xC0 ? 2 : (c & 0xF0) == 0xE0 ? 3 : (c & 0xF8) == 0xF0 ? 4 : 0;
|
||||
bool valid = len > 0 && i + len <= s.size();
|
||||
for (int k = 1; valid && k < len; k++) valid = (static_cast<unsigned char>(s[i + k]) & 0xC0) == 0x80;
|
||||
if (!valid) {
|
||||
out += '?';
|
||||
i++;
|
||||
} else {
|
||||
uint32_t cp = len == 2 ? (c & 0x1F) : len == 3 ? (c & 0x0F) : (c & 0x07);
|
||||
for (int k = 1; k < len; k++) cp = (cp << 6) | (static_cast<unsigned char>(s[i + k]) & 0x3F);
|
||||
if (cp <= 0xFF) out.append(s, i, len); // Latin-1: the fonts have it
|
||||
else out += '?';
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
column++;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace roro::gemini
|
||||
Reference in New Issue
Block a user