hare

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

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:
Mcmd/hare/subcmds.ha | 84++++++++++++++++++++-----------------------------------------------------------
Mhare/module/scan.ha | 7++-----
Ahare/module/walk.ha | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 3++-
Mstdlib.mk | 6++++--
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'