hare

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

commit 860bc8ef6b90263db70cbacbbdecc423dcca95dc
parent 7c19e7b72c5b33b641deaf68ae19266c308b6a73
Author: Sebastian <sebastian@sebsite.pw>
Date:   Sat, 30 Sep 2023 23:36:52 -0400

haredoc: distinguish between modules and decls better

- If an identifier could refer to either a declaration or a module, the
  declaration is shown, with a notice for tty that the identifier is
  ambiguous.
- The identifier may have a trailing ::, in which case declarations are
  never shown, only modules.
- In addition, parseident has been improved a bit, and also some memory
  leaks were also fixed if they were nearby.

Signed-off-by: Sebastian <sebastian@sebsite.pw>

Diffstat:
Mcmd/haredoc/doc/tty.ha | 17+++++++++++++++++
Mcmd/haredoc/doc/types.ha | 2+-
Mcmd/haredoc/main.ha | 186++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
3 files changed, 131 insertions(+), 74 deletions(-)

diff --git a/cmd/haredoc/doc/tty.ha b/cmd/haredoc/doc/tty.ha @@ -24,6 +24,23 @@ export fn emit_tty(ctx: *context) (void | error) = { }; const summary = ctx.summary; + if (ctx.ambiguous) { + const id = unparse::identstr(ctx.ident); + defer free(id); + + if (!no_color) fmt::fprintf(ctx.out, "\x1b[{}m", + color(unparse::synkind::COMMENT))?; + fmt::fprint(ctx.out, "// ")?; + if (!no_color) fmt::fprint(ctx.out, "\x1b[93m")?; + fmt::fprint(ctx.out, "NOTE")?; + if (!no_color) fmt::fprintf(ctx.out, "\x1b[m" "\x1b[{}m", + color(unparse::synkind::COMMENT))?; + fmt::fprintf(ctx.out, ": {} also refers to module [[{}::]]", + id, id)?; + if (!no_color) fmt::fprint(ctx.out, "\x1b[m")?; + fmt::fprintln(ctx.out, "\n")?; + }; + match (ctx.readme) { case let readme: io::file => let rbuf: [os::BUFSZ]u8 = [0...]; diff --git a/cmd/haredoc/doc/types.ha b/cmd/haredoc/doc/types.ha @@ -51,7 +51,7 @@ export type context = struct { mctx: *module::context, ident: ast::ident, tags: []str, - modpath: str, + ambiguous: bool, srcs: module::srcset, submods: []str, summary: summary, diff --git a/cmd/haredoc/main.ha b/cmd/haredoc/main.ha @@ -93,92 +93,130 @@ fn doc(name: str, cmd: *getopt::command) (void | error) = { fmt::fatal("Option -a must be used only with -Fhare or -Ftty"); }; - let decls: []ast::decl = []; - defer free(decls); - let ctx = module::context { harepath = harepath(), harecache = harecache(), tags = tags, }; - let decl = ""; - let (modpath, srcs, id) = if (len(cmd.args) == 0) { - let (modpath, srcs) = module::find(&ctx, []: ast::ident)?; - yield (modpath, srcs, []: ast::ident); + let declpath = ""; + defer free(declpath); + let declsrcs = module::srcset { ... }; + defer module::finish_srcset(&declsrcs); + let modpath = ""; + defer free(modpath); + let modsrcs = module::srcset { ... }; + defer module::finish_srcset(&modsrcs); + let id: ast::ident = []; + defer free(id); + + if (len(cmd.args) == 0) { + let (p, s) = module::find(&ctx, []: ast::ident)?; + modpath = strings::dup(p); + modsrcs = s; } else match (parseident(cmd.args[0])) { - case let id: ast::ident => - // first assume it's a module - yield match (module::find(&ctx, id)) { - case let r: (str, module::srcset) => - yield (r.0, r.1, id); + case let ident: (ast::ident, bool) => + id = ident.0; + const trailing = ident.1; + if (!trailing) { + // check if it's an ident inside a module + match (module::find(&ctx, id[..len(id)-1])) { + case let s: (str, module::srcset) => + declpath = strings::dup(s.0); + declsrcs = s.1; + case let e: module::error => + module::finish_error(e); + }; + }; + // check if it's a module + match (module::find(&ctx, id)) { + case let s: (str, module::srcset) => + modpath = strings::dup(s.0); + modsrcs = s.1; case let e: module::error => module::finish_error(e); - // then assume it's an ident inside a module - decl = id[len(id)-1]; - id = id[..len(id)-1]; - let (modpath, srcs) = module::find(&ctx, id)?; - yield (modpath, srcs, id); + if (declpath == "") { + const id = unparse::identstr(id); + fmt::fatalf("Could not find {}{}", id, + if (trailing) "::" else ""); + }; }; - case => - let buf = path::buffer { ... }; - path::set(&buf, cmd.args[0])?; - let (modpath, srcs) = module::find(&ctx, &buf)?; - yield (modpath, srcs, []: ast::ident); - }; - - for (let i = 0z; i < len(srcs.ha); i += 1) { - let u = doc::scan(srcs.ha[i])?; - ast::imports_finish(u.imports); - append(decls, u.decls...); + case void => + let buf = path::init(cmd.args[0])?; + let (p, s) = module::find(&ctx, &buf)?; + modpath = strings::dup(p); + modsrcs = s; }; - const rpath = path::init(modpath, "README")!; - const readme: (io::file | void) = if (decl == "") { - yield match (os::open(path::string(&rpath))) { - case let err: fs::error => - yield void; - case let f: io::file => - yield f; + let decls: []ast::decl = []; + defer { + for (let i = 0z; i < len(decls); i += 1) { + ast::decl_finish(decls[i]); }; - } else void; - - defer match (readme) { - case void => void; - case let f: io::file => - io::close(f)!; + free(decls); }; - if (decl != "") { + if (declpath != "") { + for (let i = 0z; i < len(declsrcs.ha); i += 1) { + let u = doc::scan(declsrcs.ha[i])?; + ast::imports_finish(u.imports); + append(decls, u.decls...); + }; let new: []ast::decl = []; for (let i = 0z; i < len(decls); i += 1) { - if (has_decl(decls[i], decl)) { + if (has_decl(decls[i], id[len(id) - 1])) { append(new, decls[i]); } else { ast::decl_finish(decls[i]); }; }; - if (len(new) == 0) { - fmt::fatalf("Could not find {}::{}", - unparse::identstr(id), decl); - }; free(decls); decls = new; + if (len(new) == 0) { + if (modpath == "") { + const id = unparse::identstr(id); + fmt::fatalf("Could not find {}", id); + }; + } else { + show_undocumented = true; + }; + }; - show_undocumented = true; + let readme: (io::file | void) = void; + defer match (readme) { + case void => void; + case let f: io::file => + io::close(f)!; }; - defer for (let i = 0z; i < len(decls); i += 1) { - ast::decl_finish(decls[i]); + const ambiguous = modpath != "" && len(decls) > 0; + + if (len(decls) == 0) { + for (let i = 0z; i < len(modsrcs.ha); i += 1) { + let u = doc::scan(modsrcs.ha[i])?; + ast::imports_finish(u.imports); + append(decls, u.decls...); + }; + + const rpath = path::init(modpath, "README")!; + match (os::open(path::string(&rpath))) { + case let f: io::file => + readme = f; + case fs::error => void; + }; }; + const submods: []str = if (!ambiguous && modpath != "") { + yield doc::submodules(modpath)?; + } else []; + const srcs = if (!ambiguous && modpath != "") modsrcs else declsrcs; const ctx = doc::context { mctx = &ctx, ident = id, tags = tags, - modpath = modpath, + ambiguous = ambiguous, srcs = srcs, - submods = if (decl == "") doc::submodules(modpath)? else [], + submods = submods, summary = doc::sort_decls(decls), format = fmt, template = template, @@ -208,30 +246,31 @@ fn doc(name: str, cmd: *getopt::command) (void | error) = { // converted to strings and there must be no trailing tokens that don't belong // to the ident in the string. For example, this function will parse `rt::abort` // as a valid identifier. -fn parseident(in: str) (ast::ident | parse::error) = { +fn parseident(in: str) ((ast::ident, bool) | void) = { const buf = memio::fixed(strings::toutf8(in)); const lexer = lex::init(&buf, "<string>"); defer lex::finish(&lexer); - // XXX: errdefer let success = false; let ident: ast::ident = []; defer if (!success) ast::ident_free(ident); + let trailing = false; let z = 0z; for (true) { - const tok = lex::lex(&lexer)?; + const tok = lex::lex(&lexer)!; const name = if (tok.0 == lex::ltok::NAME) { yield tok.1 as str; } else if (tok.0 < lex::ltok::LAST_KEYWORD) { yield strings::dup(lex::tokstr(tok)); + } else if (tok.0 == lex::ltok::EOF && len(ident) > 0) { + trailing = true; + break; } else { lex::unlex(&lexer, tok); - const loc = lex::mkloc(&lexer); - const why = "Unexpected trailing :: in ident"; - return (loc, why): lex::syntax: parse::error; + return; }; append(ident, name); z += len(name); - const tok = lex::lex(&lexer)?; + const tok = lex::lex(&lexer)!; switch (tok.0) { case lex::ltok::EOF => break; @@ -239,19 +278,14 @@ fn parseident(in: str) (ast::ident | parse::error) = { z += 1; case => lex::unlex(&lexer, tok); - const loc = lex::mkloc(&lexer); - const why = fmt::asprintf("Unexpected '{}' in ident", - lex::tokstr(tok)); - return (loc, why): lex::syntax: parse::error; + return; }; }; if (z > ast::IDENT_MAX) { - const loc = lex::mkloc(&lexer); - const why = "Identifier exceeds maximum length"; - return (loc, why): lex::syntax: parse::error; + return; }; success = true; - return ident; + return (ident, trailing); }; fn init_tty(ctx: *doc::context) io::handle = { @@ -333,10 +367,16 @@ fn emit(ctx: *doc::context) (void | error) = { }; @test fn parseident() void = { - assert(ast::ident_eq(parseident("hare::lex") as ast::ident, - ["hare", "lex"])); - assert(ast::ident_eq(parseident("rt::abort") as ast::ident, - ["rt", "abort"])); - assert(parseident("strings::dup*{}&@") is parse::error); - assert(parseident("foo::bar::") is parse::error); + let (ident, trailing) = parseident("hare::lex") as (ast::ident, bool); + assert(ast::ident_eq(ident, ["hare", "lex"])); + assert(!trailing); + let (ident, trailing) = parseident("rt::abort") as (ast::ident, bool); + assert(ast::ident_eq(ident, ["rt", "abort"])); + assert(!trailing); + let (ident, trailing) = parseident("foo::bar::") as (ast::ident, bool); + assert(ast::ident_eq(ident, ["foo", "bar"])); + assert(trailing); + assert(parseident("strings::dup*{}&@") is void); + assert(parseident("") is void); + assert(parseident("::") is void); };