hare

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

commit 3c10957914e8ef2d9be8715b8cef05b36ad89c4f
parent ac309e7aa4d1ef076df0dd06471d4c1c83b5bb34
Author: Drew DeVault <sir@cmpwn.com>
Date:   Thu, 18 Mar 2021 16:11:29 -0400

hare::module: refactor scan

This is mainly prep work. The "eligible" function has been removed,
because we need access to enough of its internals to no longer justify
it being separate. Future commits will continue this work by sorting the
list of inputs (to obtain a consistent module hash) and by filtering
them so that less-specific tagsets are discarded in favor of
more-specific sets.

Diffstat:
Mhare/module/scan.ha | 197++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mhare/module/types.ha | 5+++++
Mpath/names.ha | 40+++++++++++++++++++++++-----------------
3 files changed, 129 insertions(+), 113 deletions(-)

diff --git a/hare/module/scan.ha b/hare/module/scan.ha @@ -56,6 +56,31 @@ export fn scan(ctx: *context, path: str) (version | error) = { return ver; }; +// Given a name and whether or not it represents a directory, parses it into the +// basename, extension, and tag set. +fn parse_name(name: str) (str, str, []tag) = { + let ext = path::extension(name); + let base = ext.0, ext = ext.1; + + let p = strings::index(base, '+'); + let m = strings::index(base, '-'); + if (p is void && m is void) { + return (base, ext, []); + }; + let i: size = + if (p is void && m is size) m: size + else if (m is void && p is size) p: size + else if (m: size < p: size) m: size + else p: size; + let tags = strings::sub(base, i, strings::end); + let tags = match (parse_tags(tags)) { + void => return (base, ext, []), + t: []tag => t, + }; + let base = strings::sub(base, 0, i); + return (base, ext, tags); +}; + fn scan_directory( ctx: *context, ver: *version, @@ -63,61 +88,61 @@ fn scan_directory( path: str, iter: *fs::iterator, ) (void | error) = { - for (true) match (fs::next(iter)) { - void => break, - ent: fs::dirent => switch (ent.ftype) { + let files: []str = [], dirs: []str = []; + defer { + for (let i = 0z; i < len(files); i += 1) { + free(files[i]); + }; + free(files); + + for (let i = 0z; i < len(dirs); i += 1) { + free(dirs[i]); + }; + free(dirs); + }; + + for (true) { + let ent = match (fs::next(iter)) { + void => break, + ent: fs::dirent => ent, + }; + + switch (ent.ftype) { fs::mode::LINK => abort(), // TODO - fs::mode::DIR => { - let d = strings::to_utf8(ent.name); - if (len(d) == 0 || ( - !strings::has_prefix(ent.name, "+") && - !strings::has_prefix(ent.name, "-"))) { - continue; - }; - if (!eligible(ctx, ent.name, true)) { - continue; - }; - let p = path::join(path, ent.name); - let iter = fs::iter(ctx.fs, p)?; - scan_directory(ctx, ver, sha, p, iter)?; - }, - fs::mode::REG => if (eligible(ctx, ent.name, false)) { - let p = path::join(path, ent.name); - let st = fs::stat(ctx.fs, p)?; - let in = input { - path = fs::resolve(ctx.fs, p), - stat = st, - ft = type_for_ext(ent.name) as filetype, - hash = scan_file(ctx, p, &ver.depends)?, - ... - }; - append(ver.inputs, in); - hash::write(sha, in.hash); - }, - * => void, - }, + fs::mode::DIR => append(dirs, strings::dup(ent.name)), + fs::mode::REG => append(files, strings::dup(ent.name)), + }; }; -}; -// Looks up a module by its identifier from HAREPATH, and returns a [version] -// which includes all eligible build inputs. -export fn lookup(ctx: *context, name: ast::ident) (version | error) = { - let ipath = ident_path(name); - for (let i = len(ctx.paths); i > 0; i -= 1) { - let cand = path::join(ctx.paths[i - 1], ipath); - defer free(cand); - match (scan(ctx, cand)) { - v: version => return v, - e: error => void, + // TODO: + // - sort entries (for a consistent module hash) + // - filter out less specific tag sets for a given basename + for (let i = 0z; i < len(dirs); i += 1) { + let name = dirs[i]; + let tags = parse_name(name).2; + defer tags_free(tags); + + let d = strings::to_utf8(name); + if (len(d) == 0 || ( + !strings::has_prefix(name, "+") && + !strings::has_prefix(name, "-"))) { + continue; }; + if (!tags_compat(ctx.tags, tags)) { + continue; + }; + + let p = path::join(path, name); + let iter = fs::iter(ctx.fs, p)?; + scan_directory(ctx, ver, sha, p, iter)?; }; - return module_not_found; -}; -fn eligible(ctx: *context, name: str, dir: bool) bool = { - if (!dir) { + for (let i = 0z; i < len(files); i += 1) { + let name = files[i]; + let parsed = parse_name(name); + let base = parsed.0, ext = parsed.1, tags = parsed.2; + let eligible = false; - const ext = path::extension(name); static const exts = [".ha", ".s"]; for (let i = 0z; i < len(exts); i += 1) { if (exts[i] == ext) { @@ -125,63 +150,43 @@ fn eligible(ctx: *context, name: str, dir: bool) bool = { break; }; }; - if (!eligible) { - return false; + if (!eligible || !tags_compat(ctx.tags, tags)) { + tags_free(tags); + continue; }; - }; - // XXX: It might be nice if the stdlib offered search functions which - // support multiple needles - let p = strings::index(name, '+'); - let m = strings::index(name, '-'); - if (p is void && m is void) { - return true; - }; - let i: size = - if (p is void && m is size) m: size - else if (m is void && p is size) p: size - else if (m: size < p: size) m: size - else p: size; - let tags = match (strings::index(name, '.')) { - void => strings::sub(name, i, strings::end), - e: size => strings::sub(name, i, e), - }; - let tags = match (parse_tags(tags)) { - void => return false, - t: []tag => t, + let p = path::join(path, name); + let st = fs::stat(ctx.fs, p)?; + let in = input { + path = fs::resolve(ctx.fs, p), + stat = st, + ft = type_for_ext(name) as filetype, + hash = scan_file(ctx, p, &ver.depends)?, + tags = tags, + ... + }; + append(ver.inputs, in); + hash::write(sha, in.hash); }; - defer tags_free(tags); - return tags_compat(ctx.tags, tags); }; -@test fn eligible() void = { - let ctx = context { - tags = [ - tag { name = "incl", mode = tag_mode::INCLUSIVE }, - tag { name = "excl", mode = tag_mode::EXCLUSIVE }, - ], +// Looks up a module by its identifier from HAREPATH, and returns a [version] +// which includes all eligible build inputs. +export fn lookup(ctx: *context, name: ast::ident) (version | error) = { + let ipath = ident_path(name); + for (let i = len(ctx.paths); i > 0; i -= 1) { + let cand = path::join(ctx.paths[i - 1], ipath); + defer free(cand); + match (scan(ctx, cand)) { + v: version => return v, + e: error => void, + }; }; - assert(eligible(&ctx, "foo.ha", false)); - assert(eligible(&ctx, "foo.s", false)); - assert(eligible(&ctx, "foo+incl.ha", false)); - assert(eligible(&ctx, "foo-excl.ha", false)); - assert(eligible(&ctx, "foo-other.ha", false)); - assert(eligible(&ctx, "foo+incl-excl.ha", false)); - - assert(!eligible(&ctx, "foo.txt", false)); - assert(!eligible(&ctx, "foo-incl.ha", false)); - assert(!eligible(&ctx, "foo+excl.ha", false)); - assert(!eligible(&ctx, "foo+other.ha", false)); - assert(!eligible(&ctx, "foo-incl+excl.ha", false)); - - assert(eligible(&ctx, "+incl", true)); - assert(eligible(&ctx, "-excl", true)); - assert(!eligible(&ctx, "-incl", true)); - assert(!eligible(&ctx, "+excl", true)); + return module_not_found; }; fn type_for_ext(name: str) (filetype | void) = { - const ext = path::extension(name); + const ext = path::extension(name).1; return if (ext == ".ha") filetype::HARE else if (ext == ".s") filetype::ASSEMBLY diff --git a/hare/module/types.ha b/hare/module/types.ha @@ -42,6 +42,11 @@ export type input = struct { path: str, ft: filetype, stat: fs::filestat, + + // Name without any tags + basename: str, + // Tags applicable to input + tags: []tag, }; // The requested module could not be found. diff --git a/path/names.ha b/path/names.ha @@ -47,33 +47,39 @@ export fn basename(path: str) str = { assert(basename("foo") == "foo"); }; -// Returns the file extension for a path. The return value is borrowed from the -// input, see [strings::dup] to extend its lifetime. +// Returns the file name and extension for a path. The return value is borrowed +// from the input, see [strings::dup] to extend its lifetime. // -// The return value includes the '.' character. +// The extension includes the '.' character. // -// extension("foo/example") => "" -// extension("foo/example.txt") => ".txt" -// extension("foo/example.tar.gz") => ".tar.gz" -export fn extension(p: str) str = { +// extension("foo/example") => ("example", "") +// extension("foo/example.txt") => ("example", ".txt") +// extension("foo/example.tar.gz") => ("example", ".tar.gz") +export fn extension(p: str) (str, str) = { + let p = basename(p); let b = strings::to_utf8(p); if (len(b) == 0 || b[len(b) - 1] == PATHSEP) { - return ""; + return (p, ""); }; - let b = strings::to_utf8(basename(p)); + let b = strings::to_utf8(p); let i = match (bytes::index(b, '.': u32: u8)) { - void => return "", + void => return (p, ""), z: size => z, }; let e = b[i..]; - return strings::from_utf8_unsafe(e); + let n = b[..i]; + return (strings::from_utf8_unsafe(n), strings::from_utf8_unsafe(e)); }; @test fn extension() void = { - assert(extension("") == ""); - assert(extension("foo/") == ""); - assert(extension("foo/bar") == ""); - assert(extension("foo/bar.txt") == ".txt"); - assert(extension("foo/bar.tar.gz") == ".tar.gz"); - assert(extension("foo.bar/baz.ha") == ".ha"); + assert(extension("").0 == ""); + assert(extension("").1 == ""); + assert(extension("foo/bar").0 == "bar"); + assert(extension("foo/bar").1 == ""); + assert(extension("foo/bar.txt").0 == "bar"); + assert(extension("foo/bar.txt").1 == ".txt"); + assert(extension("foo/bar.tar.gz").0 == "bar"); + assert(extension("foo/bar.tar.gz").1 == ".tar.gz"); + assert(extension("foo.bar/baz.ha").0 == "baz"); + assert(extension("foo.bar/baz.ha").1 == ".ha"); };