hare

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

commit fe6332b75cc8cf3d013da357f340552a7c0cc13c
parent d97c115d356eb2b9d3669f1a4cafe5974ee9a180
Author: Drew DeVault <sir@cmpwn.com>
Date:   Mon, 19 Apr 2021 17:29:46 -0400

haredoc: implement module resolution

Diffstat:
MMakefile | 1+
Acmd/haredoc/env.ha | 21+++++++++++++++++++++
Mcmd/haredoc/hare.ha | 5+++--
Mcmd/haredoc/html.ha | 31++++++++++++++++++++++++++-----
Mcmd/haredoc/main.ha | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mhare/module/context.ha | 3+++
Mhare/parse/ident.ha | 13++++++++++++-
7 files changed, 129 insertions(+), 30 deletions(-)

diff --git a/Makefile b/Makefile @@ -35,6 +35,7 @@ harec_srcs=\ haredoc_srcs=\ ./cmd/haredoc/main.ha \ ./cmd/haredoc/errors.ha \ + ./cmd/haredoc/env.ha \ ./cmd/haredoc/hare.ha \ ./cmd/haredoc/html.ha \ ./cmd/haredoc/sort.ha diff --git a/cmd/haredoc/env.ha b/cmd/haredoc/env.ha @@ -0,0 +1,21 @@ +use hare::module; +use os::exec; +use os; +use strings; + +fn default_tags() []module::tag = { + // TODO: Once os::exec can handle pipes, we should read the default tags + // from $(hare version). + return alloc([module::tag { + name = strings::dup("linux"), + mode = module::tag_mode::INCLUSIVE, + }, module::tag { + name = strings::dup(os::machine()), + mode = module::tag_mode::INCLUSIVE, + }]); +}; + +fn default_harepath() str = { + // TODO: Same deal + return "/usr/share/src/hare/stdlib:/usr/share/src/hare/third-party"; +}; diff --git a/cmd/haredoc/hare.ha b/cmd/haredoc/hare.ha @@ -8,8 +8,9 @@ use strings; use strio; // Formats output as Hare source code (prototypes) -fn emit_hare(summary: summary) (void | error) = { - // TODO: Should we emit the dependencies, too? +fn emit_hare(ctx: *context) (void | error) = { + const summary = ctx.summary; + // XXX: Should we emit the dependencies, too? for (let i = 0z; i < len(summary.types); i += 1) { details_hare(summary.types[i])?; }; diff --git a/cmd/haredoc/html.ha b/cmd/haredoc/html.ha @@ -2,6 +2,7 @@ use fmt; use format::html; use hare::ast; use hare::lex; +use hare::module; use hare::unparse; use io; use os; @@ -9,12 +10,32 @@ use strings; use strio; // Formats output as HTML -fn emit_html(decls: summary, template: bool) (void | error) = { - if (template) head()?; +fn emit_html(ctx: *context) (void | error) = { + const decls = ctx.summary; + if (ctx.template) head()?; + + const ident = unparse::identstr(ctx.ident); + fmt::printf("<h2>{} <span class='heading-extra'>", ident); + for (let i = 0z; i < len(ctx.tags); i += 1) { + const mode = switch (ctx.tags[i].mode) { + module::tag_mode::INCLUSIVE => '+', + module::tag_mode::EXCLUSIVE => '-', + }; + fmt::printf("{}{} ", mode, ctx.tags[i].name); + }; + fmt::println("</span></h2>")?; + + 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)?; + fmt::println("</div>")?; + }, + }; - // TODO: Module name - fmt::println("<h2>hash::fnv <span class='heading-extra'>+linux +x86_64</span></h2>")?; - fmt::println("<p class='readme'>TODO: Insert module README here")?; if (len(decls.types) != 0) { fmt::println("<h4>Types</h4>")?; fmt::println("<ol>")?; diff --git a/cmd/haredoc/main.ha b/cmd/haredoc/main.ha @@ -3,9 +3,11 @@ use fs; use getopt; use hare::ast; use hare::lex; +use hare::module; use hare::parse; use io; use os; +use path; use strings; type format = enum { @@ -15,6 +17,16 @@ type format = enum { GEMTEXT, }; +type context = struct { + ident: ast::ident, + tags: []module::tag, + version: module::version, + summary: summary, + format: format, + template: bool, + readme: nullable *io::stream, +}; + export fn main() void = { // TODO: Use format::TTY by default if stdout is a tty let fmt = format::HARE; @@ -41,28 +53,61 @@ export fn main() void = { }; }; - let unit: []ast::subunit = []; - defer { - for (let i = 0z; i < len(unit); i += 1) { - ast::subunit_free(unit[i]); - }; - free(unit); + let decls: []ast::decl = []; + defer free(decls); // TODO: Free interior state + + let tags = default_tags(); + defer module::tags_free(tags); + + let ctx = module::context_init(tags, [], default_harepath()); + defer module::context_finish(&ctx); + + assert(len(cmd.args) == 1); // TODO: Something more sophisticated here + + const id = match (parse::identstr(cmd.args[0])) { + err: parse::error => fmt::fatal(parse::strerror(err)), + id: ast::ident => id, }; - for (let i = 0z; i < len(cmd.args); i += 1) { - match (scan(cmd.args[i])) { - u: ast::subunit => append(unit, u), - err: error => fmt::fatal("Error: {}", strerror(err)), - }; + const version = match (module::lookup(&ctx, id)) { + ver: module::version => ver, + err: module::error => fmt::fatal( + "Error scanning input module: {}", + module::strerror(err)), }; - for (let i = 0z; i < len(unit); i += 1) { - let summary = sort_decls(unit[i].decls); - match (emit(summary, fmt, template)) { - _: void => void, + for (let i = 0z; i < len(version.inputs); i += 1) { + const in = version.inputs[i]; + match (scan(in.path)) { + u: ast::subunit => append(decls, u.decls...), err: error => fmt::fatal("Error: {}", strerror(err)), }; }; + + const rpath = path::join(version.basedir, "README"); + defer free(rpath); + const readme: nullable *io::stream = match (os::open(rpath)) { + err: fs::error => null, + f: *io::stream => f, + }; + defer match (readme) { + null => void, + f: *io::stream => io::close(f), + }; + + let dctx = context { + ident = id, + tags = tags, + version = version, + summary = sort_decls(decls), + format = fmt, + template = template, + readme = readme, + }; + match (emit(&dctx)) { + _: void => void, + err: error => fmt::fatal("Error: {}", strerror(err)), + }; }; fn scan(path: str) (ast::subunit | error) = { @@ -76,13 +121,9 @@ fn scan(path: str) (ast::subunit | error) = { return parse::subunit(&lexer)?; }; -fn emit( - summary: summary, - fmt: format, - template: bool, -) (void | error) = switch (fmt) { - format::HARE => emit_hare(summary), +fn emit(ctx: *context) (void | error) = switch (ctx.format) { + format::HARE => emit_hare(ctx), format::TTY => abort(), // TODO - format::HTML => emit_html(summary, template)?, + format::HTML => emit_html(ctx)?, format::GEMTEXT => abort(), // TODO }; diff --git a/hare/module/context.ha b/hare/module/context.ha @@ -71,6 +71,9 @@ export fn context_finish(ctx: *context) void = { // Converts an identifier to a partial path (e.g. foo::bar becomes foo/bar). The // return value must be freed by the caller. export fn identpath(name: ast::ident) str = { + if (len(name) == 0) { + return strings::dup("."); + }; let p = path::join(name[0]); for (let i = 1z; i < len(name); i += 1) { let q = path::join(p, name[i]); diff --git a/hare/parse/ident.ha b/hare/parse/ident.ha @@ -1,6 +1,9 @@ +use bufio; use hare::ast; -use hare::lex; use hare::lex::{ltok}; +use hare::lex; +use io; +use strings; fn ident_trailing(lexer: *lex::lexer) ((ast::ident, bool) | error) = { let ident: []str = []; @@ -32,3 +35,11 @@ export fn ident(lexer: *lex::lexer) (ast::ident | error) = { synassert(mkloc(lexer), !ident.1, "Unexpected trailing :: in ident")?; return ident.0; }; + +// Parses an identifier from a string. +export fn identstr(in: str) (ast::ident | error) = { + const buf = bufio::fixed(strings::toutf8(in), io::mode::READ); + defer io::close(buf); + const lexer = lex::init(buf, "<string>"); + return ident(&lexer); +};