hare

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

commit c26cce3953435c6f580c9d4b9e3f3179f5a3ab97
parent 45965b48675c1cf9d22f77d9db717fb4187e98fb
Author: Autumn! <autumnull@posteo.net>
Date:   Sun,  7 May 2023 01:43:31 +0000

path: replace extension() with stack-based functions

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

Diffstat:
Mcmd/hare/plan.ha | 2+-
Mcmd/hare/schedule.ha | 9+++++++--
Mcmd/haredoc/main.ha | 4++--
Mcmd/haredoc/resolver.ha | 4++--
Mhare/module/scan.ha | 22++++++++++++++++------
Mpath/README | 2+-
Apath/ext_stack.ha | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dpath/names.ha | 61-------------------------------------------------------------
Mscripts/gen-stdlib | 2+-
Mstdlib.mk | 4++--
10 files changed, 154 insertions(+), 78 deletions(-)

diff --git a/cmd/hare/plan.ha b/cmd/hare/plan.ha @@ -95,7 +95,7 @@ fn mkplan( const name = p.0, ext = p.1, tags = p.2; defer module::tags_free(tags); - if (len(tags) >= ntag && name == "hare" && ext == ".sc" + if (len(tags) >= ntag && name == "hare" && ext == "sc" && module::tagcompat(ctx.tags, tags)) { ntag = len(tags); path::set(&buf, rtdir, d.name)!; diff --git a/cmd/hare/schedule.ha b/cmd/hare/schedule.ha @@ -264,9 +264,14 @@ fn sched_hare_object( let current = false; let output = if (output is str) { + static let buf = path::buffer{...}; + path::set(&buf, output as str)!; // TODO: Should we use the cache here? - const (_, ext) = path::extension(output as str); - const expected = if (mixed) ".a" else ".o"; + const ext = match (path::peek_ext(&buf)) { + case let s: str => yield s; + case void => yield ""; + }; + const expected = if (mixed) "a" else "o"; if (ext != expected) { fmt::errorfln("Warning: Expected output file extension {}, found {}", expected, output)!; diff --git a/cmd/haredoc/main.ha b/cmd/haredoc/main.ha @@ -146,8 +146,8 @@ export fn main() void = { for (let i = 0z; i < len(version.inputs); i += 1) { const in = version.inputs[i]; - const ext = path::extension(in.path); - if (ext.1 != ".ha") { + const ext = path::peek_ext(&path::init(in.path)!); + if (ext is void || ext as str != "ha") { continue; }; match (scan(in.path)) { diff --git a/cmd/haredoc/resolver.ha b/cmd/haredoc/resolver.ha @@ -141,8 +141,8 @@ fn lookup_remote_enum(ctx: *context, what: ast::ident) (ast::ident | void) = { }; for (let i = 0z; i < len(version.inputs); i += 1) { const in = version.inputs[i]; - const ext = path::extension(in.path); - if (ext.1 != ".ha") { + const ext = path::peek_ext(&path::init(in.path)!); + if (ext is void || ext as str != "ha") { continue; }; match (scan(in.path)) { diff --git a/hare/module/scan.ha b/hare/module/scan.ha @@ -100,8 +100,16 @@ export fn scan(ctx: *context, path: str) (version | error) = { // Given a file or directory name, parses it into the basename, extension, and // tag set. export fn parsename(name: str) (str, str, []tag) = { - let ext = path::extension(name); - let base = ext.0, ext = ext.1; + static let buf = path::buffer {...}; + path::set(&buf, name)!; + let ext = match (path::pop_ext(&buf)) { + case void => yield ""; + case let s: str => yield strings::dup(s); + }; + let base = match (path::peek(&buf)) { + case void => yield ""; + case let s: str => yield strings::dup(s); + }; let p = strings::index(base, '+'); let m = strings::index(base, '-'); @@ -248,7 +256,7 @@ fn scan_directory( let base = parsed.0, ext = parsed.1, tags = parsed.2; let eligible = false; - static const exts = [".ha", ".s"]; + static const exts = ["ha", "s"]; for (let i = 0z; i < len(exts); i += 1) { if (exts[i] == ext) { eligible = true; @@ -332,10 +340,12 @@ export fn lookup(ctx: *context, name: ast::ident) (version | error) = { }; fn type_for_ext(name: str) (filetype | void) = { - const ext = path::extension(name).1; + static let buf = path::buffer {...}; + path::set(&buf, name)!; + const ext = path::peek_ext(&buf) as str; return - if (ext == ".ha") filetype::HARE - else if (ext == ".s") filetype::ASSEMBLY + if (ext == "ha") filetype::HARE + else if (ext == "s") filetype::ASSEMBLY else void; }; diff --git a/path/README b/path/README @@ -26,7 +26,7 @@ would be exceeded. // Statically allocated static let buf = path::buffer { ... }; - path::set(&buf); + path::set(&buf)!; // Heap allocated let buf = alloc(path::init()!); diff --git a/path/ext_stack.ha b/path/ext_stack.ha @@ -0,0 +1,122 @@ +// License: MPL-2.0 +use bytes; +use strings; + +// Add extensions onto the end of the final path segment. The separating '.' +// will be inserted for you. If the final path segment consists entirely of dots +// or the path is root, this function will return [[cant_extend]]. +export fn push_ext(buf: *buffer, ext: str...) (str | error) = { + match (peek(buf)) { + case void => return cant_extend; + case let s: str => if (strings::ltrim(s, '.') == "") return cant_extend; + }; + for (let i = 0z; i < len(ext); i += 1) { + const newend = buf.end + 1 + len(ext[i]); + if (PATH_MAX < newend) return too_long; + buf.buf[buf.end] = '.'; + buf.buf[buf.end+1..newend] = strings::toutf8(ext[i]); + buf.end = newend; + }; + return string(buf); +}; + +// Remove and return the final extension in a path. The result will not +// include the leading '.'. The result is borrowed from the buffer. Leading dots +// will be ignored when looking for extensions, such that ".ssh" isn't +// considered to have any extensions. +export fn pop_ext(buf: *buffer) (str | void) = { + const ext = split_ext(buf); + buf.end = ext.0; + return ext.1; +}; + +// Examine the final extension in a path. The result will not +// include the leading '.'. The result is borrowed from the buffer. Leading dots +// will be ignored when looking for extensions, such that ".ssh" isn't +// considered to have any extensions. +export fn peek_ext(buf: *buffer) (str | void) = split_ext(buf).1; + +// helper function, returns (end of non-extension, extension string) +fn split_ext(buf: *buffer) (size, (str | void)) = match (peek(buf)) { +case void => return (buf.end, void); +case let s: str => + const bs = strings::toutf8(s); + bs = bytes::ltrim(bs, '.'); + match (bytes::rindex(bs, '.')) { + case void => return (buf.end, void); + case let i: size => + return (buf.end - len(bs) + i, strings::fromutf8_unsafe(bs[i+1..])); + }; +}; + +// Remove and return all the extensions in a path. The result will not +// include the leading '.', but will include separating dots. Leading dots will +// be ignored when looking for extensions, such that ".ssh" isn't considered to +// have any extensions. The result is borrowed from the buffer. +export fn pop_exts(buf: *buffer) (str | void) = match (peek(buf)) { +case void => return void; +case let s: str => + const bs = strings::toutf8(s); + bs = bytes::ltrim(bs, '.'); + match (bytes::index(bs, '.')) { + case void => return void; + case let i: size => + buf.end -= len(bs) - i; + return strings::fromutf8_unsafe(bs[i+1..]); + }; +}; + +@test fn ext() void = { + // push_ext + let buf = init()!; + assert(push_ext(&buf, "bash") is cant_extend); + set(&buf, pathsepstr)!; + assert(push_ext(&buf, "bash") is cant_extend); + set(&buf, "....")!; + assert(push_ext(&buf, "bash") is cant_extend); + set(&buf, "bashrc")!; + assert(push_ext(&buf, "bash") as str == "bashrc.bash"); + set(&buf, ".bashrc")!; + assert(push_ext(&buf, "bash") as str == ".bashrc.bash"); + + // pop_ext + set(&buf)!; + assert(pop_ext(&buf) is void); + set(&buf, "..")!; + assert(pop_ext(&buf) is void); + set(&buf, pathsepstr)!; + assert(pop_ext(&buf) is void); + + set(&buf, "index.html.tmpl")!; + assert(pop_ext(&buf) as str == "tmpl"); + assert(string(&buf) == "index.html"); + assert(pop_ext(&buf) as str == "html"); + assert(string(&buf) == "index"); + assert(pop_ext(&buf) is void); + assert(string(&buf) == "index"); + + set(&buf, ".secret.tar.gz")!; + assert(pop_ext(&buf) as str == "gz"); + assert(string(&buf) == ".secret.tar"); + assert(pop_ext(&buf) as str == "tar"); + assert(string(&buf) == ".secret"); + assert(pop_ext(&buf) is void); + assert(string(&buf) == ".secret"); + + set(&buf, "..ext")!; + assert(pop_ext(&buf) is void); + assert(string(&buf) == "..ext"); + + // pop_exts + set(&buf, "index.html.tmpl")!; + assert(pop_exts(&buf) as str == "html.tmpl"); + assert(string(&buf) == "index"); + assert(pop_exts(&buf) is void); + assert(string(&buf) == "index"); + + set(&buf, ".secret.tar.gz")!; + assert(pop_exts(&buf) as str == "tar.gz"); + assert(string(&buf) == ".secret"); + assert(pop_ext(&buf) is void); + assert(string(&buf) == ".secret"); +}; diff --git a/path/names.ha b/path/names.ha @@ -1,61 +0,0 @@ -// License: MPL-2.0 -// (c) 2022 Alexey Yerin <yyp@disroot.org> -// (c) 2021-2022 Drew DeVault <sir@cmpwn.com> -// (c) 2021 Ember Sawady <ecs@d2evs.net> -use bytes; -use strings; - -// 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 extension includes the '.' character. -// -// extension("foo/example") => ("example", "") -// extension("foo/example.txt") => ("example", ".txt") -// extension("foo/example.tar.gz") => ("example.tar", ".gz") -// this change is only here until the next commit, rebase is annoying -export fn extension(p: str) (str, str) = { - if (p == "") { - return ("", ""); - }; - let p = basename(p); - let b = strings::toutf8(p); - if (len(b) == 0 || b[len(b) - 1] == PATHSEP) { - return (p, ""); - }; - let i = match (bytes::rindex(b, '.')) { - case void => - return (p, ""); - case let z: size => - yield z; - }; - let e = b[i..]; - let n = b[..i]; - return (strings::fromutf8_unsafe(n), strings::fromutf8_unsafe(e)); -}; - -@test fn extension() void = { - assertpatheql(&ext0, "", ""); - assertpatheql(&ext1, "", ""); - assertpatheql(&ext0, "bar", "foo", "bar"); - assertpatheql(&ext1, "", "foo", "bar"); - assertpatheql(&ext0, "bar", "foo", "bar.txt"); - assertpatheql(&ext1, ".txt", "foo", "bar.txt"); - assertpatheql(&ext0, "bar.tar", "foo", "bar.tar.gz"); - assertpatheql(&ext1, ".gz", "foo", "bar.tar.gz"); - assertpatheql(&ext0, "baz", "foo.bar", "baz.ha"); - assertpatheql(&ext1, ".ha", "foo.bar", "baz.ha"); -}; - -fn assertpatheql( - func: *fn(path: str) const str, - expected: str, - path: str... -) void = { - const s = strings::join(pathsepstr, path...); - assert(func(s) == expected); - free(s); -}; - -fn ext0(p: str) const str = extension(p).0; -fn ext1(p: str) const str = extension(p).1; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -1205,7 +1205,7 @@ path() { buffer.ha \ error.ha \ stack.ha \ - names.ha \ + ext_stack.ha \ posix.ha \ iter.ha gen_ssa path strings bytes errors diff --git a/stdlib.mk b/stdlib.mk @@ -1838,7 +1838,7 @@ stdlib_path_any_srcs = \ $(STDLIB)/path/buffer.ha \ $(STDLIB)/path/error.ha \ $(STDLIB)/path/stack.ha \ - $(STDLIB)/path/names.ha \ + $(STDLIB)/path/ext_stack.ha \ $(STDLIB)/path/posix.ha \ $(STDLIB)/path/iter.ha @@ -4113,7 +4113,7 @@ testlib_path_any_srcs = \ $(STDLIB)/path/buffer.ha \ $(STDLIB)/path/error.ha \ $(STDLIB)/path/stack.ha \ - $(STDLIB)/path/names.ha \ + $(STDLIB)/path/ext_stack.ha \ $(STDLIB)/path/posix.ha \ $(STDLIB)/path/iter.ha