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:
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