hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit b98c71e21963f8cc27ed5a7c5d9a5a203966f980
parent 5063415ac8e632d1856336be957bbed66dc7ef1c
Author: Drew DeVault <sir@cmpwn.com>
Date:   Tue, 20 Apr 2021 10:34:07 -0400

haredoc: handle haredoc markup

Diffstat:
Acmd/haredoc/docstr.ha | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcmd/haredoc/html.ha | 61++++++++++++++++++++++++++++++++++++++++++++++++++-----------
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;