commit 1fc77c61cc28b12d6ba48530ca2fbd5d1938f291
parent d49f8e4dd921806e525d4b788372dc79c383d276
Author: Drew DeVault <sir@cmpwn.com>
Date: Sat, 4 Dec 2021 17:45:17 +0100
hare::module: implement walk
This could be more efficient (by not re-walking the same directory
thrice in walk and scan and hare test), but it can be improved later.
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Fixes: https://todo.sr.ht/~sircmpwn/hare/510
Diffstat:
5 files changed, 120 insertions(+), 71 deletions(-)
diff --git a/cmd/hare/subcmds.ha b/cmd/hare/subcmds.ha
@@ -369,57 +369,6 @@ fn run(args: []str) void = {
exec::exec(&cmd);
};
-fn sched_walk(plan: *plan, ident: ast::ident, link: *[]*task) void = {
- const path = module::identpath(ident);
- const it = os::iter(path)?;
- free(path);
- for (true) :loop {
- match (fs::next(it)) {
- case ent: fs::dirent =>
- if (ent.name == "." || ent.name == "..") {
- continue;
- };
- if (ent.ftype & fs::mode::DIR != fs::mode::DIR) {
- continue;
- };
- const d = utf8::decode(ent.name);
- match (utf8::next(&d)) {
- case void =>
- break;
- case (utf8::more | utf8::invalid) =>
- continue :loop;
- case r: rune =>
- if (!ascii::isalpha(r) && r != '_') {
- continue :loop;
- };
- };
- for (true) match (utf8::next(&d)) {
- case void =>
- break;
- case (utf8::more | utf8::invalid) =>
- continue :loop;
- case r: rune =>
- if (!ascii::isalnum(r) && r != '_') {
- continue :loop;
- };
- };
- let new = ast::ident_dup(ident);
- append(new, strings::dup(ent.name));
- sched_walk(plan, new, link);
-
- match (module::lookup(plan.context, new)) {
- case ver: module::version =>
- if (len(ver.inputs) == 0) continue;
- case module::error =>
- continue;
- };
- sched_module(plan, new, link);
- case void =>
- break;
- };
- };
-};
-
fn test(args: []str) void = {
const help: []getopt::help = [
"compiles and runs tests for Hare programs",
@@ -502,27 +451,36 @@ fn test(args: []str) void = {
const plan = mkplan(&ctx, libs);
defer plan_finish(&plan);
- const ver = match (module::scan(&ctx, input)) {
- case ver: module::version =>
- yield ver;
+ let depends: []*task = [];
+ sched_module(&plan, ["rt"], &depends);
+
+ let items = match (module::walk(&ctx, input)) {
+ case items: []ast::ident =>
+ yield items;
case err: module::error =>
- fmt::fatal("Error scanning input module: {}",
+ fmt::fatal("Error scanning source root: {}",
module::strerror(err));
};
- let depends: []*task = [];
- sched_module(&plan, ["rt"], &depends);
- sched_walk(&plan, [], &depends);
+ defer module::walk_free(items);
+ for (let i = 0z; i < len(items); i += 1) {
+ if (len(items[i]) > 0 && items[i][0] == "cmd") {
+ continue;
+ };
+ match (module::lookup(plan.context, items[i])) {
+ case ver: module::version =>
+ if (len(ver.inputs) == 0) continue;
+ case module::error =>
+ continue;
+ };
+ sched_module(&plan, items[i], &depends);
+ };
const have_output = len(output) != 0;
if (!have_output) {
output = mkfile(&plan, "", "out");
};
- if (len(ver.inputs) == 0) {
- sched_ld(&plan, strings::dup(output), depends...);
- } else {
- sched_hare_exe(&plan, ver, strings::dup(output), depends...);
- };
+ sched_ld(&plan, strings::dup(output), depends...);
match (plan_execute(&plan, verbose)) {
case void => void;
case !exec::exit_status =>
diff --git a/hare/module/scan.ha b/hare/module/scan.ha
@@ -73,10 +73,7 @@ export fn scan(ctx: *context, path: str) (version | error) = {
};
scan_directory(ctx, &ver, &sha, path, iter)?;
- let readme = path::join(path, "README");
- defer free(readme);
- if (len(ver.inputs) == 0 && !fs::exists(ctx.fs, readme)) {
- // README is a special case for haredoc
+ if (len(ver.inputs) == 0) {
return module_not_found;
};
@@ -141,7 +138,7 @@ fn scan_directory(
};
for (true) {
- let ent = match (fs::next(iter)) {
+ const ent = match (fs::next(iter)) {
case void =>
break;
case ent: fs::dirent =>
diff --git a/hare/module/walk.ha b/hare/module/walk.ha
@@ -0,0 +1,91 @@
+use errors;
+use fs;
+use hare::ast;
+use path;
+use strings;
+
+// Recursively scans the filesystem to find valid Hare modules for the given
+// [[context]], given the path to the entry point. The caller must free the
+// return value with [[walk_free]].
+export fn walk(ctx: *context, path: str) ([]ast::ident | error) = {
+ let items: []ast::ident = [];
+ _walk(ctx, path, &items, [])?;
+ return items;
+};
+
+fn _walk(
+ ctx: *context,
+ path: str,
+ items: *[]ast::ident,
+ ns: ast::ident,
+) (void | error) = {
+ match (scan(ctx, path)) {
+ case error =>
+ void;
+ case ver: version =>
+ append(items, ns);
+ };
+
+ let iter = match (fs::iter(ctx.fs, path)) {
+ case fs::wrongtype =>
+ return; // Single file "module"
+ case err: fs::error =>
+ return err;
+ case iter: *fs::iterator =>
+ yield iter;
+ };
+ for (true) {
+ const ent = match (fs::next(iter)) {
+ case void =>
+ break;
+ case ent: fs::dirent =>
+ yield ent;
+ };
+
+ if (strings::hasprefix(ent.name, "+")
+ || strings::hasprefix(ent.name, "-")
+ || strings::hasprefix(ent.name, ".")) {
+ continue;
+ };
+
+ switch (ent.ftype) {
+ case fs::mode::DIR =>
+ // TODO: Test that this is a valid name (grammar)
+ let subpath = path::join(path, ent.name);
+ defer free(subpath);
+ let newns = ast::ident_dup(ns);
+ append(newns, strings::dup(ent.name));
+ _walk(ctx, subpath, items, newns)?;
+ case fs::mode::LINK =>
+ let linkpath = path::join(path, ent.name);
+ defer free(linkpath);
+ let linkpath = fs::readlink(ctx.fs, linkpath)?;
+ defer free(linkpath);
+ if (!path::abs(linkpath)) {
+ let newpath = path::join(path, linkpath);
+ free(linkpath);
+ linkpath = newpath;
+ };
+
+ const st = fs::stat(ctx.fs, linkpath)?;
+ if (fs::isdir(st.mode)) {
+ let subpath = path::join(path, ent.name);
+ defer free(subpath);
+ let newns = ast::ident_dup(ns);
+ append(newns, strings::dup(ent.name));
+ _walk(ctx, subpath, items, newns)?;
+ };
+ case fs::mode::REG =>
+ void; // no-op
+ case => abort();
+ };
+ };
+};
+
+// Frees resources associated with the return value of [[walk]].
+export fn walk_free(items: []ast::ident) void = {
+ for (let i = 0z; i < len(items); i += 1) {
+ ast::ident_free(items[i]);
+ };
+ free(items);
+};
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -555,7 +555,8 @@ hare_module() {
types.ha \
context.ha \
scan.ha \
- manifest.ha
+ manifest.ha \
+ walk.ha
gen_ssa hare::module \
hare::ast hare::lex hare::parse hare::unparse strio fs io strings hash \
crypto::sha256 dirs bytes encoding::utf8 ascii fmt time slice bufio \
diff --git a/stdlib.mk b/stdlib.mk
@@ -967,7 +967,8 @@ stdlib_hare_module_any_srcs= \
$(STDLIB)/hare/module/types.ha \
$(STDLIB)/hare/module/context.ha \
$(STDLIB)/hare/module/scan.ha \
- $(STDLIB)/hare/module/manifest.ha
+ $(STDLIB)/hare/module/manifest.ha \
+ $(STDLIB)/hare/module/walk.ha
$(HARECACHE)/hare/module/hare_module-any.ssa: $(stdlib_hare_module_any_srcs) $(stdlib_rt) $(stdlib_hare_ast_$(PLATFORM)) $(stdlib_hare_lex_$(PLATFORM)) $(stdlib_hare_parse_$(PLATFORM)) $(stdlib_hare_unparse_$(PLATFORM)) $(stdlib_strio_$(PLATFORM)) $(stdlib_fs_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_strings_$(PLATFORM)) $(stdlib_hash_$(PLATFORM)) $(stdlib_crypto_sha256_$(PLATFORM)) $(stdlib_dirs_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM)) $(stdlib_encoding_utf8_$(PLATFORM)) $(stdlib_ascii_$(PLATFORM)) $(stdlib_fmt_$(PLATFORM)) $(stdlib_time_$(PLATFORM)) $(stdlib_slice_$(PLATFORM)) $(stdlib_bufio_$(PLATFORM)) $(stdlib_strconv_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) $(stdlib_encoding_hex_$(PLATFORM)) $(stdlib_sort_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_temp_$(PLATFORM))
@printf 'HAREC \t$@\n'
@@ -2697,7 +2698,8 @@ testlib_hare_module_any_srcs= \
$(STDLIB)/hare/module/types.ha \
$(STDLIB)/hare/module/context.ha \
$(STDLIB)/hare/module/scan.ha \
- $(STDLIB)/hare/module/manifest.ha
+ $(STDLIB)/hare/module/manifest.ha \
+ $(STDLIB)/hare/module/walk.ha
$(TESTCACHE)/hare/module/hare_module-any.ssa: $(testlib_hare_module_any_srcs) $(testlib_rt) $(testlib_hare_ast_$(PLATFORM)) $(testlib_hare_lex_$(PLATFORM)) $(testlib_hare_parse_$(PLATFORM)) $(testlib_hare_unparse_$(PLATFORM)) $(testlib_strio_$(PLATFORM)) $(testlib_fs_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) $(testlib_hash_$(PLATFORM)) $(testlib_crypto_sha256_$(PLATFORM)) $(testlib_dirs_$(PLATFORM)) $(testlib_bytes_$(PLATFORM)) $(testlib_encoding_utf8_$(PLATFORM)) $(testlib_ascii_$(PLATFORM)) $(testlib_fmt_$(PLATFORM)) $(testlib_time_$(PLATFORM)) $(testlib_slice_$(PLATFORM)) $(testlib_bufio_$(PLATFORM)) $(testlib_strconv_$(PLATFORM)) $(testlib_os_$(PLATFORM)) $(testlib_encoding_hex_$(PLATFORM)) $(testlib_sort_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_temp_$(PLATFORM))
@printf 'HAREC \t$@\n'