commit ce2dbbfe3fb9e05d02cab0a6296a570227c13b95
parent 0650341725382046212753b715113b323b10fc23
Author: Drew DeVault <sir@cmpwn.com>
Date: Sat, 8 Jan 2022 09:39:41 +0100
pathbuf: initial commit
This will be expanded upon.
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
5 files changed, 172 insertions(+), 0 deletions(-)
diff --git a/path/+freebsd.ha b/path/+freebsd.ha
@@ -1,2 +1,5 @@
// Path separator, platform-specific.
export def PATHSEP: u8 = '/': u32: u8;
+
+// Maximum length of a file path for this platform.
+export def PATH_MAX: size = 4096;
diff --git a/path/+linux.ha b/path/+linux.ha
@@ -1,2 +1,5 @@
// Path separator, platform-specific.
export def PATHSEP: u8 = '/': u32: u8;
+
+// Maximum length of a file path for this platform.
+export def PATH_MAX: size = 4096;
diff --git a/pathbuf/buffer.ha b/pathbuf/buffer.ha
@@ -0,0 +1,127 @@
+use bytes;
+use errors;
+use path;
+use strings;
+
+export type buffer = struct {
+ buf: [path::PATH_MAX]u8,
+ cur: []u8,
+};
+
+// Initializes a new path buffer.
+export fn init() buffer = {
+ let buf = buffer { ... };
+ reset(&buf);
+ return buf;
+};
+
+// Initializes a caller-allocated path buffer.
+//
+// let buf = pathbuf::buffer { ... };
+// pathbuf::init_static(&buf);
+export fn init_static(buf: *buffer) void = {
+ reset(buf);
+};
+
+// Resets a path buffer to its initial state (an empty path).
+export fn reset(buf: *buffer) void = {
+ buf.cur = buf.buf[..0];
+};
+
+// Creates a copy of another path buffer, which can be modified without
+// affecting the original.
+export fn dup(buf: *buffer) buffer = {
+ let new = buffer { ... };
+ new.buf[..] = buf.buf[..];
+ new.cur = new.buf[..0];
+ return new;
+};
+
+// Like [[dup]], but the new buffer is allocated by the caller.
+export fn dup_static(new: *buffer, old: *buffer) void = {
+ new.buf[..] = old.buf[..];
+ new.cur = old.buf[..0];
+};
+
+// Returns the current path stored in this buffer. The path will always be
+// normalized, which is to say that it will not include any "." or ".."
+// components, or repeated path separators (e.g. "/usr//bin/../bin/./hare"
+// becomes "/usr/bin/hare").
+export fn path(buf: *buffer) str = {
+ return strings::fromutf8(buf.cur);
+};
+
+// Overwrites the contents of a [[buffer]] with an arbitrary path.
+export fn setpath(buf: *buffer, path: str) (void | errors::overflow) = {
+ const path = strings::toutf8(path);
+ const tok = bytes::tokenize(path, [path::PATHSEP]);
+ for (true) {
+ const next = match (bytes::next_token(&tok)) {
+ case let tok: []u8 =>
+ yield tok;
+ case void =>
+ break;
+ };
+ appendnorm(buf, next)?;
+ };
+};
+
+// Normalizes and appends a path component to a buffer.
+//
+// Invariant: elem must either be equal to [path::PATHSEP], or contain no path
+// separators.
+fn appendnorm(buf: *buffer, elem: (str | []u8)) (void | errors::overflow) = {
+ const elem = match (elem) {
+ case let elem: []u8 =>
+ yield elem;
+ case let string: str =>
+ yield strings::toutf8(string);
+ };
+ if (len(elem) == 1 && elem[0] == path::PATHSEP) {
+ if (len(buf.cur) == 0) {
+ static append(buf.cur, path::PATHSEP);
+ return;
+ };
+ return;
+ } else if (bytes::equal(elem, ['.': u8])) {
+ return;
+ } else if (bytes::equal(elem, ['.': u8, '.': u8])) {
+ abort(); // TODO
+ };
+ if (len(buf.cur) + len(elem) + 1 >= path::PATH_MAX) {
+ return errors::overflow;
+ };
+ if (len(buf.cur) > 1 && buf.cur[len(buf.cur) - 1] != path::PATHSEP) {
+ static append(buf.cur, path::PATHSEP);
+ };
+ static append(buf.cur, elem...);
+};
+
+@test fn appendnorm() void = {
+ let buf = init();
+ assert(path(&buf) == "");
+ appendnorm(&buf, "foo")!;
+ appendnorm(&buf, "bar")!;
+ appendnorm(&buf, "baz")!;
+ assert(path(&buf) == "foo/bar/baz");
+ appendnorm(&buf, ".")!;
+ appendnorm(&buf, "bad")!;
+ appendnorm(&buf, ".")!;
+ assert(path(&buf) == "foo/bar/baz/bad");
+
+ let buf = init();
+ appendnorm(&buf, "/")!;
+ appendnorm(&buf, "foo")!;
+ appendnorm(&buf, "bar")!;
+ appendnorm(&buf, "baz")!;
+ assert(path(&buf) == "/foo/bar/baz");
+ appendnorm(&buf, "/")!;
+ appendnorm(&buf, "/")!;
+ assert(path(&buf) == "/foo/bar/baz");
+
+ let buf = init();
+ appendnorm(&buf, "/")!;
+ appendnorm(&buf, "/")!;
+ appendnorm(&buf, "/")!;
+ assert(path(&buf) == "/");
+};
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -920,6 +920,12 @@ path() {
gen_ssa path strings bufio bytes io
}
+pathbuf() {
+ gen_srcs pathbuf \
+ buffer.ha
+ gen_ssa pathbuf path
+}
+
gensrcs_strconv() {
gen_srcs strconv \
types.ha \
@@ -1181,6 +1187,7 @@ math::random
os linux freebsd
os::exec linux freebsd
path
+pathbuf
shlex
slice
sort
diff --git a/stdlib.mk b/stdlib.mk
@@ -500,6 +500,12 @@ stdlib_deps_any+=$(stdlib_path_any)
stdlib_path_linux=$(stdlib_path_any)
stdlib_path_freebsd=$(stdlib_path_any)
+# gen_lib pathbuf (any)
+stdlib_pathbuf_any=$(HARECACHE)/pathbuf/pathbuf-any.o
+stdlib_deps_any+=$(stdlib_pathbuf_any)
+stdlib_pathbuf_linux=$(stdlib_pathbuf_any)
+stdlib_pathbuf_freebsd=$(stdlib_pathbuf_any)
+
# gen_lib shlex (any)
stdlib_shlex_any=$(HARECACHE)/shlex/shlex-any.o
stdlib_deps_any+=$(stdlib_shlex_any)
@@ -1487,6 +1493,16 @@ $(HARECACHE)/path/path-any.ssa: $(stdlib_path_any_srcs) $(stdlib_rt) $(stdlib_st
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Npath \
-t$(HARECACHE)/path/path.td $(stdlib_path_any_srcs)
+# pathbuf (+any)
+stdlib_pathbuf_any_srcs= \
+ $(STDLIB)/pathbuf/buffer.ha
+
+$(HARECACHE)/pathbuf/pathbuf-any.ssa: $(stdlib_pathbuf_any_srcs) $(stdlib_rt) $(stdlib_path_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/pathbuf
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Npathbuf \
+ -t$(HARECACHE)/pathbuf/pathbuf.td $(stdlib_pathbuf_any_srcs)
+
# shlex (+any)
stdlib_shlex_any_srcs= \
$(STDLIB)/shlex/split.ha
@@ -2251,6 +2267,12 @@ testlib_deps_any+=$(testlib_path_any)
testlib_path_linux=$(testlib_path_any)
testlib_path_freebsd=$(testlib_path_any)
+# gen_lib pathbuf (any)
+testlib_pathbuf_any=$(TESTCACHE)/pathbuf/pathbuf-any.o
+testlib_deps_any+=$(testlib_pathbuf_any)
+testlib_pathbuf_linux=$(testlib_pathbuf_any)
+testlib_pathbuf_freebsd=$(testlib_pathbuf_any)
+
# gen_lib shlex (any)
testlib_shlex_any=$(TESTCACHE)/shlex/shlex-any.o
testlib_deps_any+=$(testlib_shlex_any)
@@ -3274,6 +3296,16 @@ $(TESTCACHE)/path/path-any.ssa: $(testlib_path_any_srcs) $(testlib_rt) $(testlib
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Npath \
-t$(TESTCACHE)/path/path.td $(testlib_path_any_srcs)
+# pathbuf (+any)
+testlib_pathbuf_any_srcs= \
+ $(STDLIB)/pathbuf/buffer.ha
+
+$(TESTCACHE)/pathbuf/pathbuf-any.ssa: $(testlib_pathbuf_any_srcs) $(testlib_rt) $(testlib_path_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/pathbuf
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Npathbuf \
+ -t$(TESTCACHE)/pathbuf/pathbuf.td $(testlib_pathbuf_any_srcs)
+
# shlex (+any)
testlib_shlex_any_srcs= \
$(STDLIB)/shlex/split.ha \