hare

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

commit 07d575be5f820ea9870be8a6cdb25e760fc13df5
parent e88492d2c78696639fb98f866cf81cc77764f97e
Author: Autumn! <autumnull@posteo.net>
Date:   Fri,  3 Mar 2023 13:16:54 +0000

Implement deps subcommand

Signed-off-by: Autumn! <autumnull@posteo.net>

Diffstat:
MMakefile | 1+
Acmd/hare/deps.ha | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcmd/hare/subcmds.ha | 111++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 231 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile @@ -23,6 +23,7 @@ all: include stdlib.mk hare_srcs = \ + ./cmd/hare/deps.ha \ ./cmd/hare/main.ha \ ./cmd/hare/plan.ha \ ./cmd/hare/progress.ha \ diff --git a/cmd/hare/deps.ha b/cmd/hare/deps.ha @@ -0,0 +1,122 @@ +use fmt; +use hare::module; +use hare::parse; +use io; +use os; +use sort; +use strings; + +type depnode = struct { + ident: str, + depends: []size, + depth: uint, +}; + +// the start of the cycle in the stack +type dep_cycle = !size; + +// depth-first initial exploration, cycle-detection, reverse topological sort +fn explore_deps(ctx: *module::context, stack: *[]str, visited: *[]depnode, ident: str) (size | dep_cycle) = { + // check for cycles + for (let i = 0z; i < len(stack); i += 1) { + if (ident == stack[i]) { + append(stack, ident); + return i: dep_cycle; + }; + }; + + // return existing depnode if visited already + for (let i = 0z; i < len(visited); i += 1) { + if (ident == visited[i].ident) return i; + }; + append(stack, ident); + + let this = depnode{ident = strings::dup(ident), depends = [], depth = 0}; + let ver = match (module::lookup(ctx, parse::identstr(ident)!)) { + case let e: module::error => + fmt::fatal(module::strerror(e)); + case let ver: module::version => + yield ver; + }; + for (let i = 0z; i < len(ver.depends); i += 1) { + const name = strings::join("::", ver.depends[i]...); + defer free(name); + const child = explore_deps(ctx, stack, visited, name)?; + append(this.depends, child); + }; + // reverse-sort depends so that we know the last in the list is the + // "final" child during show_deps + sort::sort(this.depends, size(size), &cmpsz); + + static delete(stack[len(stack)-1]); + append(visited, this); + return len(visited) - 1; +}; + +// sorts in reverse +fn cmpsz(a: const *void, b: const *void) int = (*(b: *size) - *(a: *size)): int; + +type link = struct { + depth: uint, + child: size, + final: bool, +}; + +fn show_deps(depnodes: *[]depnode) void = { + let links: []link = []; + defer free(links); + // traverse in reverse because reverse-topo-sort + for (let i = len(depnodes) - 1; 0 <= i && i < len(depnodes); i -= 1) { + for (let j = 0z; j < len(links); j += 1) { + if (i < links[j].child) continue; + if (depnodes[i].depth < links[j].depth + 1) depnodes[i].depth = links[j].depth + 1; + }; + + // print in-between row + for (let d = 0u; d < depnodes[i].depth; d += 1) { + let passing = false; + for (let j = 0z; j < len(links); j += 1) { + if (i < links[j].child) continue; + if (d == links[j].depth) { + passing = true; + }; + }; + fmt::print(if (passing) "│ " else " ")!; + }; + if (i < len(depnodes) - 1) fmt::println()!; + + // print row itself + let on_path = false; + for (let d = 0u; d < depnodes[i].depth; d += 1) { + let connected = false; + let passing = false; + let final = false; + for (let j = 0z; j < len(links); j += 1) { + if (i < links[j].child) continue; + if (d == links[j].depth) { + passing = true; + if (i == links[j].child) { + connected = true; + on_path = true; + if (links[j].final) final = true; + }; + }; + }; + fmt::print( + if (final) "└──" + else if (connected) "├──" + else if (on_path) "───" + else if (passing) "│ " + else " " + )!; + }; + fmt::println(depnodes[i].ident)!; + for (let j = 0z; j < len(depnodes[i].depends); j += 1) { + append(links, link{ + depth = depnodes[i].depth, + child = depnodes[i].depends[j], + final = len(depnodes[i].depends) == j + 1, + }); + }; + }; +}; diff --git a/cmd/hare/subcmds.ha b/cmd/hare/subcmds.ha @@ -16,6 +16,7 @@ use io; use os::exec; use os; use path; +use sort; use strings; use unix::tty; @@ -209,19 +210,123 @@ fn cache(args: []str) void = { abort("cache subcommand not implemented yet."); // TODO }; +type deps_goal = enum { + DOT, + MAKE, + TERM, +}; + fn deps(args: []str) void = { const help: []getopt::help = [ "prints dependency information for a Hare program", ('d', "print dot syntax for use with graphviz"), - ('M', "print rules for POSIX make"), + ('M', "build-dir", "print rules for POSIX make"), ('T', "tags...", "set build tags"), ('X', "tags...", "unset build tags"), - "<path>", + "<path|module>", ]; const cmd = getopt::parse(args, help...); defer getopt::finish(&cmd); - abort("deps subcommand not implemented yet."); // TODO + let build_target = default_target(); + let tags = module::tags_dup(build_target.tags); + defer module::tags_free(tags); + + let build_dir: str = ""; + let goal = deps_goal::TERM; + for (let i = 0z; i < len(cmd.opts); i += 1) { + let opt = cmd.opts[i]; + switch (opt.0) { + case 'd' => + goal = deps_goal::DOT; + case 'M' => + goal = deps_goal::MAKE; + build_dir = opt.1; + case 'T' => + tags = match (addtags(tags, opt.1)) { + case void => + fmt::fatal("Error parsing tags"); + case let t: []module::tag => + yield t; + }; + case 'X' => + tags = match (deltags(tags, opt.1)) { + case void => + fmt::fatal("Error parsing tags"); + case let t: []module::tag => + yield t; + }; + case => + abort(); + }; + }; + + const input = + if (len(cmd.args) == 0) os::getcwd() + else if (len(cmd.args) == 1) cmd.args[0] + else { + getopt::printusage(os::stderr, args[0], help...); + os::exit(1); + }; + + const ctx = module::context_init(tags, [], HAREPATH); + defer module::context_finish(&ctx); + + const ver = match (parse::identstr(input)) { + case let ident: ast::ident => + yield match (module::lookup(&ctx, ident)) { + case let ver: module::version => + yield ver; + case let err: module::error => + fmt::fatal("Error scanning input module:", + module::strerror(err)); + }; + case parse::error => + yield match (module::scan(&ctx, input)) { + case let ver: module::version => + yield ver; + case let err: module::error => + fmt::fatal("Error scanning input path:", + module::strerror(err)); + }; + }; + + let visited: []depnode = []; + let stack: []str = []; + defer free(stack); + const ctx = module::context_init([], [], HAREPATH); + + let toplevel = depnode{ident = strings::dup(path::basename(input)), depends = [], depth = 0}; + + for (let i = 0z; i < len(ver.depends); i += 1) { + const name = strings::join("::", ver.depends[i]...); + defer free(name); + const child = match (explore_deps(&ctx, &stack, &visited, name)) { + case let index: size => yield index; + case let start: dep_cycle => + const chain = strings::join(" -> ", stack[start..]...); + defer free(chain); + fmt::errorln("Dependency cycle detected:", chain)!; + os::exit(1); + }; + append(toplevel.depends, child); + }; + + sort::sort(toplevel.depends, size(size), &cmpsz); + append(visited, toplevel); + defer for (let i = 0z; i < len(visited); i += 1) { + free(visited[i].ident); + free(visited[i].depends); + }; + + switch (goal) { + case deps_goal::TERM => + show_deps(&visited); + case deps_goal::DOT => + abort("-d option not implemented yet"); + case deps_goal::MAKE => + abort("-M option not implemented yet"); + }; }; fn release(args: []str) void = {