commit b98c71e21963f8cc27ed5a7c5d9a5a203966f980
parent 5063415ac8e632d1856336be957bbed66dc7ef1c
Author: Drew DeVault <sir@cmpwn.com>
Date: Tue, 20 Apr 2021 10:34:07 -0400
haredoc: handle haredoc markup
Diffstat:
2 files changed, 265 insertions(+), 11 deletions(-)
diff --git a/cmd/haredoc/docstr.ha b/cmd/haredoc/docstr.ha
@@ -0,0 +1,215 @@
+use ascii;
+use bufio;
+use encoding::utf8;
+use fmt;
+use hare::ast;
+use hare::parse;
+use io;
+use strings;
+use strio;
+
+type paragraph = void;
+type text = str;
+type reference = ast::ident;
+type sample = str;
+type list = []str;
+type token = (paragraph | text | reference | sample | list);
+
+type docstate = enum {
+ PARAGRAPH,
+ TEXT,
+};
+
+type parser = struct {
+ src: *io::stream,
+ state: docstate,
+};
+
+fn parsedoc(in: *io::stream) parser = {
+ static let buf: [4096]u8 = [0...];
+ return parser {
+ src = bufio::buffered(in, buf[..], []),
+ state = docstate::PARAGRAPH,
+ };
+};
+
+fn scandoc(par: *parser) (token | void) = {
+ const rn = match (bufio::scanrune(par.src)) {
+ _: io::EOF => return,
+ rn: rune => rn,
+ * => abort(),
+ };
+
+ bufio::unreadrune(par.src, rn);
+ return switch (par.state) {
+ docstate::TEXT => switch (rn) {
+ '[' => scanref(par),
+ * => scantext(par),
+ },
+ docstate::PARAGRAPH => switch (rn) {
+ ' ', '\t' => scansample(par),
+ '[' => scanref(par),
+ '-' => scanlist(par),
+ * => scantext(par),
+ },
+ };
+};
+
+fn scantext(par: *parser) (token | void) = {
+ if (par.state == docstate::PARAGRAPH) {
+ par.state = docstate::TEXT;
+ return paragraph;
+ };
+ // TODO: Collapse whitespace
+ const buf = strio::dynamic();
+ for (true) {
+ const rn = match (bufio::scanrune(par.src)) {
+ _: io::EOF => break,
+ _: utf8::invalid => abort("Invalid UTF-8"),
+ err: io::error => fmt::fatal(io::strerror(err)),
+ rn: rune => rn,
+ };
+ switch (rn) {
+ '[' => {
+ bufio::unreadrune(par.src, rn);
+ break;
+ },
+ '\n' => {
+ strio::appendrune(buf, rn);
+ const rn = match (bufio::scanrune(par.src)) {
+ _: io::EOF => break,
+ * => abort(),
+ rn: rune => rn,
+ };
+ if (rn == '\n') {
+ par.state = docstate::PARAGRAPH;
+ break;
+ };
+ bufio::unreadrune(par.src, rn);
+ },
+ * => strio::appendrune(buf, rn),
+ };
+ };
+ let result = strio::finish(buf);
+ if (len(result) == 0) {
+ return;
+ };
+ return result: text;
+};
+
+fn scanref(par: *parser) (token | void) = {
+ match (bufio::scanrune(par.src)) {
+ _: io::EOF => return,
+ rn: rune => if (rn != '[') abort(),
+ * => abort(),
+ };
+ match (bufio::scanrune(par.src)) {
+ _: io::EOF => return,
+ rn: rune => if (rn != '[') {
+ bufio::unreadrune(par.src, rn);
+ return strings::dup("["): text;
+ },
+ * => abort(),
+ };
+
+ const buf = strio::dynamic();
+ defer io::close(buf);
+ // TODO: Handle invalid syntax here
+ for (true) match (bufio::scanrune(par.src)) {
+ _: io::EOF => break,
+ rn: rune => switch (rn) {
+ ']' => {
+ bufio::scanrune(par.src) as rune; // ]
+ break;
+ },
+ * => strio::appendrune(buf, rn),
+ },
+ * => abort(),
+ };
+ let id = parse::identstr(strio::string(buf)) as ast::ident;
+ return id: reference;
+};
+
+fn scansample(par: *parser) (token | void) = {
+ let nws = 0z;
+ for (true) match (bufio::scanrune(par.src)) {
+ _: io::EOF => return,
+ rn: rune => switch (rn) {
+ ' ' => nws += 1,
+ '\t' => nws += 8,
+ * => {
+ bufio::unreadrune(par.src, rn);
+ break;
+ },
+ },
+ * => abort(),
+ };
+ if (nws <= 1) {
+ return scantext(par);
+ };
+
+ let cont = true;
+ let buf = strio::dynamic();
+ for (cont) {
+ const rn = match (bufio::scanrune(par.src)) {
+ _: io::EOF => break,
+ rn: rune => rn,
+ * => abort(),
+ };
+ switch (rn) {
+ * => {
+ strio::appendrune(buf, rn);
+ continue;
+ },
+ '\n' => strio::appendrune(buf, rn),
+ };
+
+ // Consume whitespace
+ for (let i = 0z; i < nws) {
+ match (bufio::scanrune(par.src)) {
+ _: io::EOF => break,
+ * => abort(),
+ rn: rune => switch (rn) {
+ ' ' => i += 1,
+ '\t' => i += 8,
+ '\n' => {
+ strio::appendrune(buf, rn);
+ i = 0;
+ },
+ * => {
+ bufio::unreadrune(par.src, rn);
+ cont = false;
+ break;
+ },
+ },
+ };
+ };
+ };
+
+ let buf = strio::finish(buf);
+
+ // Trim trailing newlines
+ for (strings::has_suffix(buf, "\n")) {
+ buf = strings::sub(buf, 0, len(buf) - 1);
+ };
+
+ return buf: sample;
+};
+
+fn scanlist(par: *parser) (token | void) = {
+ let items: list = [];
+ for (true) {
+ match (bufio::scanrune(par.src)) {
+ _: io::EOF => break,
+ * => abort(),
+ rn: rune => if (rn != '-') break,
+ };
+ // XXX: multi-line list items
+ append(items, match (bufio::scanline(par.src)) {
+ _: io::EOF => break,
+ _: io::error => abort(),
+ u: []u8 => strings::fromutf8(u),
+ });
+ };
+ return items;
+};
diff --git a/cmd/haredoc/html.ha b/cmd/haredoc/html.ha
@@ -1,3 +1,4 @@
+use bufio;
use fmt;
use format::html;
use hare::ast;
@@ -30,10 +31,8 @@ fn emit_html(ctx: *context) (void | error) = {
match (ctx.readme) {
null => void,
f: *io::stream => {
- // TODO: Format as haredoc markup
fmt::println("<div class='readme'>")?;
- fmt::println("<p>")?;
- io::copy(os::stdout, f)?;
+ markup_html(f)?;
fmt::println("</div>")?;
},
};
@@ -130,20 +129,60 @@ fn details(decl: ast::decl) (void | error) = {
unparse_html(os::stdout, decl)?;
fmt::println("</pre>")?;
- // TODO: Apply Hare formatting
- fmt::println("<p class='comment'>")?;
- const iter = strings::tokenize(decl.docs, "\n");
- for (true) match (strings::next_token(&iter)) {
- s: str => if (len(s) != 0) {
- html::escape(os::stdout, s)?;
- },
- _: void => break,
+ if (len(decl.docs) != 0) {
+ const buf = strings::toutf8(decl.docs);
+ markup_html(bufio::fixed(buf, io::mode::READ))?;
};
fmt::println("</section>")?;
return;
};
+fn markup_html(in: *io::stream) (void | io::error) = {
+ let parser = parsedoc(in);
+ for (true) {
+ const tok = match (scandoc(&parser)) {
+ _: void => break,
+ tok: token => tok,
+ };
+ match (tok) {
+ _: paragraph => {
+ fmt::println()?;
+ fmt::print("<p>")?;
+ },
+ tx: text => {
+ html::escape(os::stdout, tx);
+ free(tx);
+ },
+ re: reference => {
+ fmt::print("<a href='#' class='ref'>")?;
+ const ident = unparse::identstr(re);
+ defer free(ident);
+ html::escape(os::stdout, ident);
+ fmt::print("</a>")?;
+ ast::ident_free(re);
+ },
+ sa: sample => {
+ fmt::print("<pre class='sample'>")?;
+ html::escape(os::stdout, sa);
+ fmt::print("</pre>")?;
+ free(sa);
+ },
+ li: list => {
+ fmt::println("<ul>");
+ for (let i = 0z; i < len(li); i += 1) {
+ fmt::println("<li>");
+ html::escape(os::stdout, li[i]);
+ fmt::println("</li>");
+ };
+ fmt::println("</ul>");
+ },
+ };
+ };
+ fmt::println()?;
+ return;
+};
+
// Forked from [[hare::unparse]]
fn unparse_html(out: *io::stream, d: ast::decl) (size | io::error) = {
let n = 0z;