hare

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

commit f1ea5921fca6696f8a3b22638a0839eb40540d66
parent 513dd8dbfbb955f330211c29616a9d59b3729c9f
Author: Byron Torres <b@torresjrjr.com>
Date:   Fri, 15 Dec 2023 19:06:00 +0000

path: rename type 'buffer' to 'path'

Diffstat:
Mcmd/hare/build.ha | 10+++++-----
Mcmd/hare/build/gather.ha | 6+++---
Mcmd/hare/build/queue.ha | 16++++++++--------
Mcmd/hare/build/util.ha | 12++++++------
Mcmd/hare/cache.ha | 16++++++++--------
Mcmd/hare/deps.ha | 6+++---
Mcmd/haredoc/main.ha | 2+-
Mcmd/parsechk/main.ha | 20++++++++++----------
Mdebug/dwarf/addr_to_line.ha | 2+-
Mdirs/xdg.ha | 16++++++++--------
Mfs/fs.ha | 6+++---
Mfs/util.ha | 24++++++++++++------------
Mhare/lex/lex.ha | 6+++---
Mhare/module/cache.ha | 4++--
Mhare/module/deps.ha | 30+++++++++++++++---------------
Mhare/module/srcs.ha | 56++++++++++++++++++++++++++++----------------------------
Mhare/module/types.ha | 6+++---
Mmakefiles/freebsd.aarch64.mk | 2+-
Mmakefiles/freebsd.riscv64.mk | 2+-
Mmakefiles/freebsd.x86_64.mk | 2+-
Mmakefiles/linux.aarch64.mk | 2+-
Mmakefiles/linux.riscv64.mk | 2+-
Mmakefiles/linux.x86_64.mk | 2+-
Mmakefiles/openbsd.aarch64.mk | 2+-
Mmakefiles/openbsd.riscv64.mk | 2+-
Mmakefiles/openbsd.x86_64.mk | 2+-
Mos/+freebsd/dirfdfs.ha | 6+++---
Mos/+linux/dirfdfs.ha | 6+++---
Mos/+linux/shm.ha | 2+-
Mos/+openbsd/dirfdfs.ha | 8++++----
Mos/exec/+openbsd/platform_cmd.ha | 2+-
Mos/exec/cmd.ha | 18+++++++++---------
Mpath/README | 18+++++++++---------
Dpath/buffer.ha | 64----------------------------------------------------------------
Mpath/ext_stack.ha | 153++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mpath/iter.ha | 32++++++++++++++++----------------
Apath/path.ha | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpath/prefix.ha | 78+++++++++++++++++++++++++++++++++++++++---------------------------------------
Mpath/stack.ha | 172++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mtemp/+freebsd.ha | 8++++----
Mtemp/+linux.ha | 8++++----
Mtime/chrono/tzdb.ha | 2+-
42 files changed, 449 insertions(+), 448 deletions(-)

diff --git a/cmd/hare/build.ha b/cmd/hare/build.ha @@ -242,18 +242,18 @@ fn run(name: str, path: str, args: []str) error = { }; fn get_output(goal: build::stage, input: str) (str | error) = { - static let buf = path::buffer { ... }; + static let p = path::path { ... }; let stat = os::stat(input)?; - path::set(&buf, os::realpath(input)?)?; + path::set(&p, os::realpath(input)?)?; if (!fs::isdir(stat.mode)) { - path::pop_ext(&buf); + path::pop_ext(&p); }; // don't add the .bin extension if the goal is to create a binary if (goal != build::stage::BIN) { - path::push_ext(&buf, build::stage_ext[goal])?; + path::push_ext(&p, build::stage_ext[goal])?; }; - const output = match (path::peek(&buf)) { + const output = match (path::peek(&p)) { case let s: str => yield s; case void => diff --git a/cmd/hare/build/gather.ha b/cmd/hare/build/gather.ha @@ -10,7 +10,7 @@ use strings; export fn gather(ctx: *context, input: str) ([]module::module | error) = { let mods: []module::module = []; - path::set(&buf, input)!; + path::set(&pathbuf, input)!; module::gather(&ctx.ctx, &mods, ["rt"])?; if (ctx.test) { module::gather(&ctx.ctx, &mods, ["test"])?; @@ -19,10 +19,10 @@ export fn gather(ctx: *context, input: str) ([]module::module | error) = { module::gather(&ctx.ctx, &mods, ["debug"])?; }; const nsubmods = if (ctx.submods) { - module::gather_submodules(&ctx.ctx, &mods, &buf)?; + module::gather_submodules(&ctx.ctx, &mods, &pathbuf)?; } else 0z; - ctx.top = match (module::gather(&ctx.ctx, &mods, &buf)) { + ctx.top = match (module::gather(&ctx.ctx, &mods, &pathbuf)) { case let top: size => yield top; case let e: module::error => diff --git a/cmd/hare/build/queue.ha b/cmd/hare/build/queue.ha @@ -127,16 +127,16 @@ fn run_task(ctx: *context, jobs: *[]job, t: *task) (bool | error) = { let out = get_cache(ctx, t.idx, t.kind)?; defer free(out); - path::set(&buf, out)?; - let lock = path::push_ext(&buf, "lock")?; + path::set(&pathbuf, out)?; + let lock = path::push_ext(&pathbuf, "lock")?; let lock = os::create(lock, 0o644, fs::flag::WRONLY)?; if (!io::lock(lock, false, io::lockop::EXCLUSIVE)?) { io::close(lock)?; return false; }; - path::set(&buf, out)?; - let tmp = path::push_ext(&buf, "tmp")?; + path::set(&pathbuf, out)?; + let tmp = path::push_ext(&pathbuf, "tmp")?; // TODO: use os::mkfile once that's supported on freebsd and openbsd io::close(os::create(tmp, 0o644)?)?; @@ -183,8 +183,8 @@ fn run_task(ctx: *context, jobs: *[]job, t: *task) (bool | error) = { case let c: exec::command => yield c; }; - path::set(&buf, out)?; - let output = os::create(path::push_ext(&buf, "log")?, 0o644)?; + path::set(&pathbuf, out)?; + let output = os::create(path::push_ext(&pathbuf, "log")?, 0o644)?; defer io::close(output)!; exec::addfile(&cmd, os::stdout_file, output); exec::addfile(&cmd, os::stderr_file, output); @@ -216,9 +216,9 @@ fn await_task(ctx: *context, jobs: *[]job) (size | void | error) = { let out = get_cache(ctx, t.idx, t.kind)?; defer free(out); - path::set(&buf, out)?; + path::set(&pathbuf, out)?; - let output = os::open(path::push_ext(&buf, "log")?)?; + let output = os::open(path::push_ext(&pathbuf, "log")?)?; defer io::close(output)!; let output = io::drain(output)?; defer free(output); diff --git a/cmd/hare/build/util.ha b/cmd/hare/build/util.ha @@ -17,7 +17,7 @@ use strings; use time; // for use as a scratch buffer -let buf = path::buffer { ... }; +let pathbuf = path::path { ... }; fn get_deps(ctx: *context, t: *task) []str = { let mod = ctx.mods[t.idx]; @@ -118,15 +118,15 @@ fn get_flags(ctx: *context, t: *task) ([]str | error) = { append(flags, strings::dup("-m.main"))!; }; append(flags, strings::dup("-M"))!; - path::set(&buf, mod.path)?; + path::set(&pathbuf, mod.path)?; for (let i = 0z; i < len(mod.ns); i += 1) { - path::pop(&buf); + path::pop(&pathbuf); }; - append(flags, strings::concat(path::string(&buf), "/"))!; + append(flags, strings::concat(path::string(&pathbuf), "/"))!; - path::set(&buf, mod.path)?; + path::set(&pathbuf, mod.path)?; let test = ctx.test && t.idx == ctx.top; - test ||= path::trimprefix(&buf, os::getcwd()) is str && ctx.submods; + test ||= path::trimprefix(&pathbuf, os::getcwd()) is str && ctx.submods; if (test) { append(flags, strings::dup("-T"))!; }; diff --git a/cmd/hare/cache.ha b/cmd/hare/cache.ha @@ -36,8 +36,8 @@ fn cache(name: str, cmd: *getopt::command) (void | error) = { }; os::mkdirs(cachedir, 0o755)!; - let buf = path::init(cachedir)?; - let sz = dirsize(&buf)?; + let p = path::init(cachedir)?; + let sz = dirsize(&p)?; const suffix = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]; let i = 0z; for (i < len(suffix) - 1 && sz >= 1024; i += 1) { @@ -46,19 +46,19 @@ fn cache(name: str, cmd: *getopt::command) (void | error) = { fmt::printfln("{} ({} {})", cachedir, sz, suffix[i])?; }; -fn dirsize(buf: *path::buffer) (size | error) = { +fn dirsize(p: *path::path) (size | error) = { let s = 0z; - let it = os::iter(path::string(buf))?; + let it = os::iter(path::string(p))?; defer fs::finish(it); for (let d => fs::next(it)?) { - path::push(buf, d.name)?; - let stat = os::stat(path::string(buf))?; + path::push(p, d.name)?; + let stat = os::stat(path::string(p))?; s += stat.sz; if (fs::isdir(stat.mode)) { - s += dirsize(buf)?; + s += dirsize(p)?; }; - path::pop(buf); + path::pop(p); }; return s; }; diff --git a/cmd/hare/deps.ha b/cmd/hare/deps.ha @@ -68,9 +68,9 @@ fn deps(name: str, cmd: *getopt::command) (void | error) = { case let id: ast::ident => yield id; case parse::error => - static let buf = path::buffer { ... }; - path::set(&buf, os::realpath(input)?)?; - yield &buf; + static let p = path::path { ... }; + path::set(&p, os::realpath(input)?)?; + yield &p; }; if (submodules) { diff --git a/cmd/haredoc/main.ha b/cmd/haredoc/main.ha @@ -243,7 +243,7 @@ fn doc(name: str, cmd: *getopt::command) (void | error) = { }; const rpath = match (path::init(modpath, "README")) { - case let buf: path::buffer => + case let buf: path::path => yield buf; case let err: path::error => assert(err is path::too_long); diff --git a/cmd/parsechk/main.ha b/cmd/parsechk/main.ha @@ -12,29 +12,29 @@ use os; use path; export fn main() void = { - let buf = path::init()!; + let p = path::init()!; let status: int = os::status::SUCCESS; - iter(&buf, &status); + iter(&p, &status); os::exit(status); }; -fn iter(buf: *path::buffer, status: *int) void = { - let it = os::iter(path::string(buf))!; +fn iter(p: *path::path, status: *int) void = { + let it = os::iter(path::string(p))!; defer fs::finish(it); for (let ent => fs::next(it)!) { - path::push(buf, ent.name)!; - defer path::pop(buf); - const st = os::stat(path::string(buf))!; + path::push(p, ent.name)!; + defer path::pop(p); + const st = os::stat(path::string(p))!; if (fs::isfile(st.mode)) { - match (path::peek_ext(buf)) { + match (path::peek_ext(p)) { case let s: str => if (s == "ha") { - parse(path::string(buf), status); + parse(path::string(p), status); }; case void => void; }; } else if (fs::isdir(st.mode)) { - iter(buf, status); + iter(p, status); }; }; }; diff --git a/debug/dwarf/addr_to_line.ha b/debug/dwarf/addr_to_line.ha @@ -82,7 +82,7 @@ export fn addr_to_line( }; const file = &prog.head.files[state.file - 1]; - static let path = path::buffer { ... }; + static let path = path::path { ... }; path::set(&path)!; diff --git a/dirs/xdg.ha b/dirs/xdg.ha @@ -9,11 +9,11 @@ use path; use unix; fn lookup(prog: str, var: str, default: str) str = { - static let buf = path::buffer { ... }; - path::set(&buf)!; + static let p = path::path { ... }; + path::set(&p)!; match (os::getenv(var)) { case let s: str => - const path = path::push(&buf, s, prog)!; + const path = path::push(&p, s, prog)!; if (!path::abs(path)) { yield; }; @@ -30,7 +30,7 @@ fn lookup(prog: str, var: str, default: str) str = { }; const home = os::getenv("HOME") as str; - const path = path::set(&buf, home, default, prog)!; + const path = path::set(&p, home, default, prog)!; match (os::mkdirs(path, 0o755)) { case let err: fs::error => fmt::fatalf("Error creating {}: {}", @@ -57,8 +57,8 @@ export fn cache(prog: str) str = lookup(prog, "XDG_CACHE_HOME", ".cache"); // statically allocated and will be overwritten on subsequent calls to any // function in the dirs module. export fn data(prog: str) str = { - static let buf = path::buffer { ... }; - const fragment = path::set(&buf, ".local", "share")!; + static let p = path::path { ... }; + const fragment = path::set(&p, ".local", "share")!; return lookup(prog, "XDG_DATA_HOME", fragment); }; @@ -67,8 +67,8 @@ export fn data(prog: str) str = { // value is statically allocated and will be overwritten on subsequent calls to // any function in the dirs module. export fn state(prog: str) str = { - static let buf = path::buffer { ... }; - const fragment = path::set(&buf, ".local", "state")!; + static let p = path::path { ... }; + const fragment = path::set(&p, ".local", "state")!; return lookup(prog, "XDG_STATE_HOME", fragment); }; diff --git a/fs/fs.ha b/fs/fs.ha @@ -338,9 +338,9 @@ export fn resolve(fs: *fs, path: str) str = { case let f: *resolvefunc => return f(fs, path); }; - static let buf = path::buffer { ... }; - path::set(&buf, path)!; - return path::string(&buf); + static let p = path::path { ... }; + path::set(&p, path)!; + return path::string(&p); }; // Creates a new (hard) link at 'new' for the file at 'old'. diff --git a/fs/util.ha b/fs/util.ha @@ -105,7 +105,7 @@ export fn dirents_free(dirents: []dirent) void = { // Removes a directory, and anything in it. export fn rmdirall(fs: *fs, path: str) (void | error) = { match (path::init(path)) { - case let buf: path::buffer => + case let buf: path::path => return rmdirall_path(fs, &buf); case let err: path::error => assert(err is path::too_long); @@ -113,32 +113,32 @@ export fn rmdirall(fs: *fs, path: str) (void | error) = { }; }; -fn rmdirall_path(fs: *fs, buf: *path::buffer) (void | error) = { - let it = iter(fs, path::string(buf))?; +fn rmdirall_path(fs: *fs, p: *path::path) (void | error) = { + let it = iter(fs, path::string(p))?; defer finish(it); for (let ent => next(it)?) { - path::push(buf, ent.name)!; + path::push(p, ent.name)!; switch (ent.ftype & mode::DIR) { case mode::DIR => - rmdirall_path(fs, buf)?; + rmdirall_path(fs, p)?; case => - remove(fs, path::string(buf))?; + remove(fs, path::string(p))?; }; - path::pop(buf); + path::pop(p); }; - return rmdir(fs, path::string(buf)); + return rmdir(fs, path::string(p)); }; // Canonicalizes a path in this filesystem by resolving all symlinks and // collapsing any "." or ".." path components. The return value is statically // allocated and will be overwritten on subsequent calls. export fn realpath(fs: *fs, path: str) (str | error) = { - static let res = path::buffer { ... }; + static let res = path::path { ... }; path::set(&res)!; - static let pathbuf = path::buffer { ... }; - path::set(&pathbuf, resolve(fs, path))!; - const iter = path::iter(&pathbuf); + static let p = path::path { ... }; + path::set(&p, resolve(fs, path))!; + const iter = path::iter(&p); for (let item => path::nextiter(&iter)) { const item = path::push(&res, item)!; diff --git a/hare/lex/lex.ha b/hare/lex/lex.ha @@ -863,8 +863,8 @@ export fn prevloc(lex: *lexer) location = { }; export fn syntaxerr(loc: location, why: str) error = { - static let buf = path::buffer{...}; - path::set(&buf, loc.path)!; - loc.path = path::string(&buf); + static let p = path::path{...}; + path::set(&p, loc.path)!; + loc.path = path::string(&p); return (loc, why); }; diff --git a/hare/module/cache.ha b/hare/module/cache.ha @@ -8,6 +8,6 @@ use path; // calls. An error is returned if the resulting path would be longer than // [[path::MAX]]. export fn get_cache(harecache: str, modpath: str) (str | error) = { - static let buf = path::buffer { ... }; - return path::set(&buf, harecache, modpath)?; + static let p = path::path { ... }; + return path::set(&p, harecache, modpath)?; }; diff --git a/hare/module/deps.ha b/hare/module/deps.ha @@ -78,15 +78,15 @@ fn idcmp(a: const *opaque, b: const *opaque) int = { // them if necessary. cachedir should be calculated with [[get_cache]], // and srcset should be calculated with [[find]]. fn get_deps(cachedir: str, srcs: *srcset) ([]ast::ident | error) = { - static let buf = path::buffer{...}; - path::set(&buf, cachedir, "deps")?; - let rest = memio::fixed(buf.buf[buf.end..]); - buf.end += format_tags(&rest, srcs.seentags)?; - buf.end += memio::concat(&rest, ".txt")?; + static let p = path::path{...}; + path::set(&p, cachedir, "deps")?; + let rest = memio::fixed(p.buf[p.end..]); + p.end += format_tags(&rest, srcs.seentags)?; + p.end += memio::concat(&rest, ".txt")?; - let outofdate = outdated(path::string(&buf), srcs.ha, srcs.mtime); + let outofdate = outdated(path::string(&p), srcs.ha, srcs.mtime); os::mkdirs(cachedir, 0o755)?; - let depsfile = os::create(path::string(&buf), 0o644, fs::flag::RDWR)?; + let depsfile = os::create(path::string(&p), 0o644, fs::flag::RDWR)?; defer io::close(depsfile)!; io::lock(depsfile, true, io::lockop::EXCLUSIVE)?; @@ -94,13 +94,13 @@ fn get_deps(cachedir: str, srcs: *srcset) ([]ast::ident | error) = { if (outofdate) { deps = parse_deps(srcs.ha...)?; io::trunc(depsfile, 0)?; - let out = bufio::init(depsfile, [], buf.buf); + let out = bufio::init(depsfile, [], p.buf); for (let dep .. deps) { unparse::ident(&out, dep)?; fmt::fprintln(&out)?; }; } else { - let in = bufio::newscanner_static(depsfile, buf.buf); + let in = bufio::newscanner_static(depsfile, p.buf); for (let s => bufio::scan_line(&in)?) { append(deps, parse::identstr(s)?)!; }; @@ -145,18 +145,18 @@ export fn gather_submodules( defer ast::ident_free(id); let buf = match (mod) { - case let b: *path::buffer => + case let b: *path::path => yield b; case let m: ast::ident => append(id, m...)!; - static let b = path::buffer { ... }; + static let b = path::path { ... }; let res = find(ctx, id)?; defer finish_srcset(&res.1); path::set(&b, res.0)?; yield &b; }; - static let srcdir = path::buffer { ... }; + static let srcdir = path::path { ... }; srcdir = *buf; for (let i = 0z; i < len(id); i += 1) { path::pop(&srcdir); @@ -177,7 +177,7 @@ export fn gather_submodules( export fn _gather_submodules( ctx: *context, out: *[]module, - buf: *path::buffer, + buf: *path::path, mod: *ast::ident, recursive: bool = true, ) (size | error) = { @@ -254,13 +254,13 @@ fn _gather( append(out, module { name = match (mod) { - case let mod: *path::buffer => + case let mod: *path::path => yield strings::dup(path::string(mod)); case let mod: ast::ident => yield unparse::identstr(mod); }, ns = match (mod) { - case let mod: *path::buffer => + case let mod: *path::path => yield []; case let mod: ast::ident => yield ast::ident_dup(mod); diff --git a/hare/module/srcs.ha b/hare/module/srcs.ha @@ -54,10 +54,10 @@ export fn finish_srcset(srcs: *srcset) void = { // statically allocated and may be overwritten on subsequent calls. export fn find(ctx: *context, loc: location) ((str, srcset) | error) = { match (loc) { - case let buf: *path::buffer => - match (path_find(ctx, buf)) { + case let p: *path::path => + match (path_find(ctx, p)) { case let s: srcset => - return (path::string(buf), s); + return (path::string(p), s); case not_found => return attach(locstr(loc), not_found); case let e: error => @@ -71,25 +71,25 @@ export fn find(ctx: *context, loc: location) ((str, srcset) | error) = { continue; }; - static let buf = path::buffer { ... }; - path::set(&buf, os::realpath(next as str)?)?; + static let p = path::path { ... }; + path::set(&p, os::realpath(next as str)?)?; for (let part .. ident) { - path::push(&buf, part)?; + path::push(&p, part)?; }; - match (path_find(ctx, &buf)) { + match (path_find(ctx, &p)) { case let s: srcset => - return (path::string(&buf), s); + return (path::string(&p), s); case not_found => void; case let e: error => - return attach(strings::dup(path::string(&buf)), e); + return attach(strings::dup(path::string(&p)), e); }; }; return attach(locstr(ident), not_found); }; }; -fn path_find(ctx: *context, buf: *path::buffer) (srcset | error) = { +fn path_find(ctx: *context, p: *path::path) (srcset | error) = { // list of sources to return, with 3 extra fields prepended to allow // quick lookup and comparison. each item is e.g.: // ("basename", "ha", 2 (# of tags), ["path/-tag1/basename+tag2.ha"]) @@ -106,7 +106,7 @@ fn path_find(ctx: *context, buf: *path::buffer) (srcset | error) = { let mtime = time::INSTANT_MIN; let res = srcset { mtime = time::INSTANT_MIN, ... }; - _findsrcs(buf, ctx.tags, &srcs, &res, 0)?; + _findsrcs(p, ctx.tags, &srcs, &res, 0)?; for (let i = 0z; i < len(srcs); i += 1) { if (len(srcs[i].3) != 1) { return alloc(srcs[i].3...)!: file_conflict; @@ -129,9 +129,9 @@ fn path_find(ctx: *context, buf: *path::buffer) (srcset | error) = { // valid. used to allow eg. shadowing foo::bar:: without accidentally // shadowing foo:: if (len(res.ha) == 0) { - path::push(buf, "README")?; - defer path::pop(buf); - if (!os::exists(path::string(buf))) { + path::push(p, "README")?; + defer path::pop(p); + if (!os::exists(path::string(p))) { finish_srcset(&res); return not_found; }; @@ -182,13 +182,13 @@ fn path_find(ctx: *context, buf: *path::buffer) (srcset | error) = { // one tag. in any case, the method used here is fast because it gets to stop // searching as soon as it can fn _findsrcs( - buf: *path::buffer, + p: *path::path, in_tags: []str, srcs: *[](str, str, size, []str), res: *srcset, tagdepth: size, ) (void | error) = { - const pathstr = path::string(buf); + const pathstr = path::string(p); const stat = match (os::stat(pathstr)) { case let stat: fs::filestat => yield stat; @@ -206,7 +206,7 @@ fn _findsrcs( }; if (fs::isfile(stat.mode)) { - let ext = match (path::pop_ext(buf)) { + let ext = match (path::pop_ext(p)) { case void => return; case let ext: str => @@ -217,8 +217,8 @@ fn _findsrcs( case => return; }; - let filebytes = strings::toutf8(path::peek(buf) as str); - path::push_ext(buf, ext)?; + let filebytes = strings::toutf8(path::peek(p) as str); + path::push_ext(p, ext)?; let split = tagindex(filebytes); let (base, tags) = ( @@ -230,7 +230,7 @@ fn _findsrcs( case let tags: []tag => yield tags; case let e: error => - return attach(strings::dup(path::string(buf)), e); + return attach(strings::dup(path::string(p)), e); }; defer free(wanttags); if (!seentags_compat(in_tags, wanttags, &res.seentags)) { @@ -238,7 +238,7 @@ fn _findsrcs( }; let ntags = tagdepth + len(wanttags); - let bufstr = path::string(buf); + let pathstr = path::string(p); for (let i = 0z; i < len(srcs); i += 1) { if (srcs[i].0 == base && srcs[i].1 == ext) { if (srcs[i].2 > ntags) { @@ -249,7 +249,7 @@ fn _findsrcs( strings::freeall(srcs[i].3); srcs[i].3 = []; }; - append(srcs[i].3, strings::dup(bufstr))!; + append(srcs[i].3, strings::dup(pathstr))!; return; }; }; @@ -258,7 +258,7 @@ fn _findsrcs( strings::dup(base), strings::dup(ext), ntags, - alloc([strings::dup(bufstr)])!, + alloc([strings::dup(pathstr)])!, ))!; return; }; @@ -279,8 +279,8 @@ fn _findsrcs( defer fs::finish(iter); for (let d => fs::next(iter)?) { - path::push(buf, d.name)?; - defer path::pop(buf); + path::push(p, d.name)?; + defer path::pop(p); if (fs::isdir(d.ftype)) { if (tagindex(strings::toutf8(d.name)) != 0) { @@ -290,17 +290,17 @@ fn _findsrcs( case let tags: []tag => yield tags; case let e: error => - return attach(strings::dup(path::string(buf)), e); + return attach(strings::dup(path::string(p)), e); }; defer free(wanttags); if (!seentags_compat(in_tags, wanttags, &res.seentags)) { continue; }; - _findsrcs(buf, in_tags, srcs, res, + _findsrcs(p, in_tags, srcs, res, tagdepth+len(wanttags))?; } else if (fs::isfile(d.ftype)) { - _findsrcs(buf, in_tags, srcs, res, tagdepth)?; + _findsrcs(p, in_tags, srcs, res, tagdepth)?; }; }; }; diff --git a/hare/module/types.ha b/hare/module/types.ha @@ -55,14 +55,14 @@ export type context = struct { }; // The location of a module -export type location = (*path::buffer | ast::ident); +export type location = (*path::path | ast::ident); // Returns a string representation of a [[location]]. The result must be freed // by the caller. export fn locstr(loc: location) str = { match (loc) { - case let buf: *path::buffer => - return strings::dup(path::string(buf)); + case let p: *path::path => + return strings::dup(path::string(p)); case let id: ast::ident => return unparse::identstr(id); }; diff --git a/makefiles/freebsd.aarch64.mk b/makefiles/freebsd.aarch64.mk @@ -105,7 +105,7 @@ $(HARECACHE)/memio.ssa: $(memio_ha) $(HARECACHE)/bytes.td $(HARECACHE)/encoding_ @printf 'HAREC\t%s\n' "$@" @$(TDENV) $(HAREC) $(HARECFLAGS) -o $@ -t $(HARECACHE)/memio.td.tmp -N memio $(memio_ha) -path_ha = path/+freebsd.ha path/buffer.ha path/error.ha path/ext_stack.ha path/iter.ha path/posix.ha path/prefix.ha path/stack.ha +path_ha = path/+freebsd.ha path/error.ha path/ext_stack.ha path/iter.ha path/path.ha path/posix.ha path/prefix.ha path/stack.ha $(HARECACHE)/path.ssa: $(path_ha) $(HARECACHE)/bytes.td $(HARECACHE)/strings.td @mkdir -p -- "$(HARECACHE)" @printf 'HAREC\t%s\n' "$@" diff --git a/makefiles/freebsd.riscv64.mk b/makefiles/freebsd.riscv64.mk @@ -105,7 +105,7 @@ $(HARECACHE)/memio.ssa: $(memio_ha) $(HARECACHE)/bytes.td $(HARECACHE)/encoding_ @printf 'HAREC\t%s\n' "$@" @$(TDENV) $(HAREC) $(HARECFLAGS) -o $@ -t $(HARECACHE)/memio.td.tmp -N memio $(memio_ha) -path_ha = path/+freebsd.ha path/buffer.ha path/error.ha path/ext_stack.ha path/iter.ha path/posix.ha path/prefix.ha path/stack.ha +path_ha = path/+freebsd.ha path/error.ha path/ext_stack.ha path/iter.ha path/path.ha path/posix.ha path/prefix.ha path/stack.ha $(HARECACHE)/path.ssa: $(path_ha) $(HARECACHE)/bytes.td $(HARECACHE)/strings.td @mkdir -p -- "$(HARECACHE)" @printf 'HAREC\t%s\n' "$@" diff --git a/makefiles/freebsd.x86_64.mk b/makefiles/freebsd.x86_64.mk @@ -105,7 +105,7 @@ $(HARECACHE)/memio.ssa: $(memio_ha) $(HARECACHE)/bytes.td $(HARECACHE)/encoding_ @printf 'HAREC\t%s\n' "$@" @$(TDENV) $(HAREC) $(HARECFLAGS) -o $@ -t $(HARECACHE)/memio.td.tmp -N memio $(memio_ha) -path_ha = path/+freebsd.ha path/buffer.ha path/error.ha path/ext_stack.ha path/iter.ha path/posix.ha path/prefix.ha path/stack.ha +path_ha = path/+freebsd.ha path/error.ha path/ext_stack.ha path/iter.ha path/path.ha path/posix.ha path/prefix.ha path/stack.ha $(HARECACHE)/path.ssa: $(path_ha) $(HARECACHE)/bytes.td $(HARECACHE)/strings.td @mkdir -p -- "$(HARECACHE)" @printf 'HAREC\t%s\n' "$@" diff --git a/makefiles/linux.aarch64.mk b/makefiles/linux.aarch64.mk @@ -105,7 +105,7 @@ $(HARECACHE)/memio.ssa: $(memio_ha) $(HARECACHE)/bytes.td $(HARECACHE)/encoding_ @printf 'HAREC\t%s\n' "$@" @$(TDENV) $(HAREC) $(HARECFLAGS) -o $@ -t $(HARECACHE)/memio.td.tmp -N memio $(memio_ha) -path_ha = path/+linux.ha path/buffer.ha path/error.ha path/ext_stack.ha path/iter.ha path/posix.ha path/prefix.ha path/stack.ha +path_ha = path/+linux.ha path/error.ha path/ext_stack.ha path/iter.ha path/path.ha path/posix.ha path/prefix.ha path/stack.ha $(HARECACHE)/path.ssa: $(path_ha) $(HARECACHE)/bytes.td $(HARECACHE)/strings.td @mkdir -p -- "$(HARECACHE)" @printf 'HAREC\t%s\n' "$@" diff --git a/makefiles/linux.riscv64.mk b/makefiles/linux.riscv64.mk @@ -105,7 +105,7 @@ $(HARECACHE)/memio.ssa: $(memio_ha) $(HARECACHE)/bytes.td $(HARECACHE)/encoding_ @printf 'HAREC\t%s\n' "$@" @$(TDENV) $(HAREC) $(HARECFLAGS) -o $@ -t $(HARECACHE)/memio.td.tmp -N memio $(memio_ha) -path_ha = path/+linux.ha path/buffer.ha path/error.ha path/ext_stack.ha path/iter.ha path/posix.ha path/prefix.ha path/stack.ha +path_ha = path/+linux.ha path/error.ha path/ext_stack.ha path/iter.ha path/path.ha path/posix.ha path/prefix.ha path/stack.ha $(HARECACHE)/path.ssa: $(path_ha) $(HARECACHE)/bytes.td $(HARECACHE)/strings.td @mkdir -p -- "$(HARECACHE)" @printf 'HAREC\t%s\n' "$@" diff --git a/makefiles/linux.x86_64.mk b/makefiles/linux.x86_64.mk @@ -105,7 +105,7 @@ $(HARECACHE)/memio.ssa: $(memio_ha) $(HARECACHE)/bytes.td $(HARECACHE)/encoding_ @printf 'HAREC\t%s\n' "$@" @$(TDENV) $(HAREC) $(HARECFLAGS) -o $@ -t $(HARECACHE)/memio.td.tmp -N memio $(memio_ha) -path_ha = path/+linux.ha path/buffer.ha path/error.ha path/ext_stack.ha path/iter.ha path/posix.ha path/prefix.ha path/stack.ha +path_ha = path/+linux.ha path/error.ha path/ext_stack.ha path/iter.ha path/path.ha path/posix.ha path/prefix.ha path/stack.ha $(HARECACHE)/path.ssa: $(path_ha) $(HARECACHE)/bytes.td $(HARECACHE)/strings.td @mkdir -p -- "$(HARECACHE)" @printf 'HAREC\t%s\n' "$@" diff --git a/makefiles/openbsd.aarch64.mk b/makefiles/openbsd.aarch64.mk @@ -105,7 +105,7 @@ $(HARECACHE)/memio.ssa: $(memio_ha) $(HARECACHE)/bytes.td $(HARECACHE)/encoding_ @printf 'HAREC\t%s\n' "$@" @$(TDENV) $(HAREC) $(HARECFLAGS) -o $@ -t $(HARECACHE)/memio.td.tmp -N memio $(memio_ha) -path_ha = path/+openbsd.ha path/buffer.ha path/error.ha path/ext_stack.ha path/iter.ha path/posix.ha path/prefix.ha path/stack.ha +path_ha = path/+openbsd.ha path/error.ha path/ext_stack.ha path/iter.ha path/path.ha path/posix.ha path/prefix.ha path/stack.ha $(HARECACHE)/path.ssa: $(path_ha) $(HARECACHE)/bytes.td $(HARECACHE)/rt.td $(HARECACHE)/strings.td @mkdir -p -- "$(HARECACHE)" @printf 'HAREC\t%s\n' "$@" diff --git a/makefiles/openbsd.riscv64.mk b/makefiles/openbsd.riscv64.mk @@ -105,7 +105,7 @@ $(HARECACHE)/memio.ssa: $(memio_ha) $(HARECACHE)/bytes.td $(HARECACHE)/encoding_ @printf 'HAREC\t%s\n' "$@" @$(TDENV) $(HAREC) $(HARECFLAGS) -o $@ -t $(HARECACHE)/memio.td.tmp -N memio $(memio_ha) -path_ha = path/+openbsd.ha path/buffer.ha path/error.ha path/ext_stack.ha path/iter.ha path/posix.ha path/prefix.ha path/stack.ha +path_ha = path/+openbsd.ha path/error.ha path/ext_stack.ha path/iter.ha path/path.ha path/posix.ha path/prefix.ha path/stack.ha $(HARECACHE)/path.ssa: $(path_ha) $(HARECACHE)/bytes.td $(HARECACHE)/rt.td $(HARECACHE)/strings.td @mkdir -p -- "$(HARECACHE)" @printf 'HAREC\t%s\n' "$@" diff --git a/makefiles/openbsd.x86_64.mk b/makefiles/openbsd.x86_64.mk @@ -105,7 +105,7 @@ $(HARECACHE)/memio.ssa: $(memio_ha) $(HARECACHE)/bytes.td $(HARECACHE)/encoding_ @printf 'HAREC\t%s\n' "$@" @$(TDENV) $(HAREC) $(HARECFLAGS) -o $@ -t $(HARECACHE)/memio.td.tmp -N memio $(memio_ha) -path_ha = path/+openbsd.ha path/buffer.ha path/error.ha path/ext_stack.ha path/iter.ha path/posix.ha path/prefix.ha path/stack.ha +path_ha = path/+openbsd.ha path/error.ha path/ext_stack.ha path/iter.ha path/path.ha path/posix.ha path/prefix.ha path/stack.ha $(HARECACHE)/path.ssa: $(path_ha) $(HARECACHE)/bytes.td $(HARECACHE)/rt.td $(HARECACHE)/strings.td @mkdir -p -- "$(HARECACHE)" @printf 'HAREC\t%s\n' "$@" diff --git a/os/+freebsd/dirfdfs.ha b/os/+freebsd/dirfdfs.ha @@ -410,9 +410,9 @@ fn fs_resolve(fs: *fs::fs, path: str) str = { return path; }; // XXX: This approach might not be right if this fs is based on a subdir - static let buf = path::buffer { ... }; - path::set(&buf, getcwd(), path)!; - return path::string(&buf); + static let p = path::path { ... }; + path::set(&p, getcwd(), path)!; + return path::string(&p); }; fn fs_close(fs: *fs::fs) void = { diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha @@ -397,9 +397,9 @@ fn fs_resolve(fs: *fs::fs, path: str) str = { return path; }; // XXX: This approach might not be right if this fs is based on a subdir - static let buf = path::buffer { ... }; - path::set(&buf, getcwd(), path)!; - return path::string(&buf); + static let p = path::path { ... }; + path::set(&p, getcwd(), path)!; + return path::string(&p); }; fn fs_link(fs: *fs::fs, old: str, new: str) (void | fs::error) = { diff --git a/os/+linux/shm.ha b/os/+linux/shm.ha @@ -18,7 +18,7 @@ fn shm_path(name: str) (str | fs::error) = { if (name == "." || name == "..") { return errors::invalid; }; - static let buf = path::buffer { ... }; + static let buf = path::path { ... }; path::set(&buf, SHM_PATH, name)!; return path::string(&buf); }; diff --git a/os/+openbsd/dirfdfs.ha b/os/+openbsd/dirfdfs.ha @@ -428,21 +428,21 @@ fn fs_fchtimes(fd: io::file, atime: (time::instant | void), // TODO: cannot handle errors, i.e. path too long or cannot resolve. fn fs_resolve(fs: *fs::fs, path: str) str = { let fs = fs: *os_filesystem; - static let buf = path::buffer { ... }; + static let p = path::path { ... }; if (path::abs(path)) { return path; }; if (fs.dirfd == rt::AT_FDCWD) { - path::set(&buf, getcwd(), path)!; + path::set(&p, getcwd(), path)!; } else { // XXX: this is the best we can for now. we should probably // return an error - path::set(&buf, "<unknown>", path)!; + path::set(&p, "<unknown>", path)!; }; - return path::string(&buf); + return path::string(&p); }; fn fs_link(fs: *fs::fs, old: str, new: str) (void | fs::error) = { diff --git a/os/exec/+openbsd/platform_cmd.ha b/os/exec/+openbsd/platform_cmd.ha @@ -3,4 +3,4 @@ use path; -export type platform_cmd = path::buffer; +export type platform_cmd = path::path; diff --git a/os/exec/cmd.ha b/os/exec/cmd.ha @@ -167,8 +167,8 @@ export fn chdir(cmd: *command, dir: str) void = { // Similar to [[lookup]] but TOCTOU-proof fn lookup_open(name: str) (platform_cmd | void | error) = { - static let buf = path::buffer { ... }; - path::set(&buf)!; + static let pbuf = path::path { ... }; + path::set(&pbuf)!; // Try to open file directly if (strings::contains(name, "/")) { @@ -190,9 +190,9 @@ fn lookup_open(name: str) (platform_cmd | void | error) = { let tok = strings::tokenize(path, ":"); for (let item => strings::next_token(&tok)) { - path::set(&buf, item, name)!; + path::set(&pbuf, item, name)!; - match (open(path::string(&buf))) { + match (open(path::string(&pbuf))) { case (errors::noaccess | errors::noentry) => continue; case let err: error => @@ -209,8 +209,8 @@ fn lookup_open(name: str) (platform_cmd | void | error) = { // The use of this function is lightly discouraged if [[cmd]] is suitable; // otherwise you may have a TOCTOU issue. export fn lookup(name: str) (str | void) = { - static let buf = path::buffer { ... }; - path::set(&buf)!; + static let pbuf = path::path { ... }; + path::set(&pbuf)!; // Try to open file directly if (strings::contains(name, "/")) { @@ -232,12 +232,12 @@ export fn lookup(name: str) (str | void) = { let tok = strings::tokenize(path, ":"); for (let item => strings::next_token(&tok)) { - path::set(&buf, item, name)!; + path::set(&pbuf, item, name)!; - match (os::access(path::string(&buf), os::amode::X_OK)) { + match (os::access(path::string(&pbuf), os::amode::X_OK)) { case let exec: bool => if (exec) { - return path::string(&buf); + return path::string(&pbuf); }; case => void; // Keep looking }; diff --git a/path/README b/path/README @@ -4,7 +4,7 @@ Note that Hare expects paths to be valid UTF-8 strings. If you require the use of non-UTF-8 paths (ideally for only as long as it takes to delete or rename those files), see the low-level functions available from [[rt::]]. -Use of the [[buffer]] type is recommended for efficient and consistent +Use of the [[path]] type is recommended for efficient and consistent manipulation of filesystem paths. The path will always be normalized, which is to say that it will not include any of the following: @@ -19,19 +19,19 @@ Different [[fs::fs]] implementations may have different rules for normalizing paths. For use-cases in which this is relevant, [[fs::resolve]] should be used instead. -The buffer object includes an array of length [[MAX]], which can be somewhat +The [[path]] object includes an array of length [[MAX]], which can be somewhat large; on Linux it's 4095 bytes. You can allocate this on the stack in most cases, but you may prefer to allocate it elsewhere depending on your needs. -Functions in this module return [[too_long]] if the buffer's capacity would be -exceeded. +Functions in this module return [[too_long]] if the path's buffer capacity would +be exceeded. // Stack allocated - let buf = path::init()!; + let p = path::init()!; // Statically allocated - static let buf = path::buffer { ... }; - path::set(&buf)!; + static let p = path::path { ... }; + path::set(&p)!; // Heap allocated - let buf = alloc(path::init()!); - defer free(buf); + let p = alloc(path::init()!); + defer free(p); diff --git a/path/buffer.ha b/path/buffer.ha @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// (c) Hare authors <https://harelang.org> - -use strings; - -export type buffer = struct { - buf: [MAX]u8, - end: size, -}; - -// Initializes a new path buffer. -export fn init(items: str...) (buffer | error) = { - let buf = buffer { ... }; - push(&buf, items...)?; - return buf; -}; - -// Sets the value of a path buffer to a list of components, overwriting any -// previous value. Returns the new string value of the path. -export fn set(buf: *buffer, items: str...) (str | error) = { - buf.end = 0; - return push(buf, items...); -}; - -// Returns the current path stored in this buffer. -// The return value is borrowed from the buffer. Use [[strings::dup]] to -// extend the lifetime of the string. -export fn string(buf: *buffer) str = { - if (buf.end == 0) return "."; - return strings::fromutf8_unsafe(buf.buf[..buf.end]); -}; - -// Check if a path is an absolute path. -export fn abs(path: (*buffer | str)) bool = match (path) { -case let path: str => return strings::hasprefix(path, sepstr); -case let buf: *buffer => return 0 < buf.end && buf.buf[0] == SEP; -}; - -// Check if a path is the root directory. -export fn isroot(path: (*buffer | str)) bool = match (path) { -case let path: str => return path == sepstr; -case let buf: *buffer => return buf.end == 1 && buf.buf[0] == SEP; -}; - -// Replaces all instances of '/' in a string with [[SEP]]. The result is -// statically-allocated. -export fn local(path: str) str = { - static let buf: [MAX]u8 = [0...]; - return _local(path, &buf); -}; - -fn _local(path: str, buf: *[MAX]u8) str = { - let buf = buf[..0]; - const bytes = strings::toutf8(path); - - for (let byte .. bytes) { - if (byte == '/') { - static append(buf, SEP)!; - } else { - static append(buf, byte)!; - }; - }; - return strings::fromutf8(buf)!; -}; diff --git a/path/ext_stack.ha b/path/ext_stack.ha @@ -7,130 +7,131 @@ 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, exts: str...) (str | error) = { - match (peek(buf)) { +export fn push_ext(p: *path, exts: str...) (str | error) = { + match (peek(p)) { case void => return cant_extend; case let s: str => if (strings::ltrim(s, '.') == "") return cant_extend; }; for (let ext .. exts) { - const newend = buf.end + 1 + len(ext); + const newend = p.end + 1 + len(ext); if (MAX < newend) return too_long; - buf.buf[buf.end] = '.'; - buf.buf[buf.end+1..newend] = strings::toutf8(ext); - buf.end = newend; + p.buf[p.end] = '.'; + p.buf[p.end+1..newend] = strings::toutf8(ext); + p.end = newend; }; - return string(buf); + return string(p); }; -// 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; +// Remove and return the final extension in a [[path]]. The result will not +// include the leading '.'. The result is borrowed from the path's buffer. +// Leading dots will be ignored when looking for extensions, such that ".ssh" +// isn't considered to have any extensions. +export fn pop_ext(p: *path) (str | void) = { + const ext = split_ext(p); + p.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; +// Examine the final extension in a [[path]]. The result will not +// include the leading '.'. The result is borrowed from the path's buffer. +// Leading dots will be ignored when looking for extensions, such that ".ssh" +// isn't considered to have any extensions. +export fn peek_ext(p: *path) (str | void) = split_ext(p).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); +fn split_ext(p: *path) (size, (str | void)) = match (peek(p)) { +case void => return (p.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 void => return (p.end, void); case let i: size => - return (buf.end - len(bs) + i, strings::fromutf8_unsafe(bs[i+1..])); + return (p.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) = { - const ext = split_exts(buf); - buf.end = ext.0; +// considered to have any extensions. The result is borrowed from the path's +// buffer. +export fn pop_exts(p: *path) (str | void) = { + const ext = split_exts(p); + p.end = ext.0; return ext.1; }; // Examine 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 peek_exts(buf: *buffer) (str | void) = split_exts(buf).1; +// to have any extensions. The result is borrowed from the path's buffer. +export fn peek_exts(p: *path) (str | void) = split_exts(p).1; // helper function, returns (end of non-extension, extension string) -fn split_exts(buf: *buffer) (size, (str | void)) = match (peek(buf)) { -case void => return (buf.end, void); +fn split_exts(p: *path) (size, (str | void)) = match (peek(p)) { +case void => return (p.end, void); case let s: str => const bs = strings::toutf8(s); bs = bytes::ltrim(bs, '.'); match (bytes::index(bs, '.')) { - case void => return (buf.end, void); + case void => return (p.end, void); case let i: size => - return (buf.end - len(bs) + i, strings::fromutf8_unsafe(bs[i+1..])); + return (p.end - len(bs) + i, 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, sepstr)!; - 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"); + let p = init()!; + assert(push_ext(&p, "bash") is cant_extend); + set(&p, sepstr)!; + assert(push_ext(&p, "bash") is cant_extend); + set(&p, "....")!; + assert(push_ext(&p, "bash") is cant_extend); + set(&p, "bashrc")!; + assert(push_ext(&p, "bash") as str == "bashrc.bash"); + set(&p, ".bashrc")!; + assert(push_ext(&p, "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, sepstr)!; - assert(pop_ext(&buf) is void); + set(&p)!; + assert(pop_ext(&p) is void); + set(&p, "..")!; + assert(pop_ext(&p) is void); + set(&p, sepstr)!; + assert(pop_ext(&p) 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(&p, "index.html.tmpl")!; + assert(pop_ext(&p) as str == "tmpl"); + assert(string(&p) == "index.html"); + assert(pop_ext(&p) as str == "html"); + assert(string(&p) == "index"); + assert(pop_ext(&p) is void); + assert(string(&p) == "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(&p, ".secret.tar.gz")!; + assert(pop_ext(&p) as str == "gz"); + assert(string(&p) == ".secret.tar"); + assert(pop_ext(&p) as str == "tar"); + assert(string(&p) == ".secret"); + assert(pop_ext(&p) is void); + assert(string(&p) == ".secret"); - set(&buf, "..ext")!; - assert(pop_ext(&buf) is void); - assert(string(&buf) == "..ext"); + set(&p, "..ext")!; + assert(pop_ext(&p) is void); + assert(string(&p) == "..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(&p, "index.html.tmpl")!; + assert(pop_exts(&p) as str == "html.tmpl"); + assert(string(&p) == "index"); + assert(pop_exts(&p) is void); + assert(string(&p) == "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"); + set(&p, ".secret.tar.gz")!; + assert(pop_exts(&p) as str == "tar.gz"); + assert(string(&p) == ".secret"); + assert(pop_ext(&p) is void); + assert(string(&p) == ".secret"); }; diff --git a/path/iter.ha b/path/iter.ha @@ -12,16 +12,16 @@ export type iterator = struct { // Returns an [[iterator]] which yields each component of a path, moving down // through child dirs. If the path is absolute, the first component will be // the root. The iterator can be copied to save its state. -export fn iter(buf: *buffer) iterator = iterator { - cur = buf.buf[..buf.end], +export fn iter(p: *path) iterator = iterator { + cur = p.buf[..p.end], reverse = false, }; // Returns an [[iterator]] which yields each component of a path, moving up // through parent dirs. If the path is absolute, the last component will be // the root. The iterator can be copied to save its state. -export fn riter(buf: *buffer) iterator = iterator { - cur = buf.buf[..buf.end], +export fn riter(p: *path) iterator = iterator { + cur = p.buf[..p.end], reverse = true, }; @@ -74,45 +74,45 @@ fn split_iter(it: *iterator) ([]u8, []u8) = { export fn iterrem(it: *iterator) str = strings::fromutf8_unsafe(it.cur); @test fn iter() void = { - const buf = init(local("/foo/bar/baz"))!; - let i = iter(&buf); + const p = init(local("/foo/bar/baz"))!; + let i = iter(&p); assert(nextiter(&i) as str == local("/")); assert(nextiter(&i) as str == "foo"); assert(nextiter(&i) as str == "bar"); assert(nextiter(&i) as str == "baz"); assert(nextiter(&i) is done); - i = riter(&buf); + i = riter(&p); assert(nextiter(&i) as str == "baz"); assert(nextiter(&i) as str == "bar"); assert(nextiter(&i) as str == "foo"); assert(nextiter(&i) as str == local("/")); assert(nextiter(&i) is done); - set(&buf, local("foo/bar/baz"))!; - i = iter(&buf); + set(&p, local("foo/bar/baz"))!; + i = iter(&p); assert(nextiter(&i) as str == "foo"); assert(nextiter(&i) as str == "bar"); assert(nextiter(&i) as str == "baz"); assert(nextiter(&i) is done); - i = riter(&buf); + i = riter(&p); assert(nextiter(&i) as str == "baz"); assert(nextiter(&i) as str == "bar"); assert(nextiter(&i) as str == "foo"); assert(nextiter(&i) is done); - set(&buf, "foo")!; - i = iter(&buf); + set(&p, "foo")!; + i = iter(&p); assert(nextiter(&i) as str == "foo"); assert(nextiter(&i) is done); - i = riter(&buf); + i = riter(&p); assert(nextiter(&i) as str == "foo"); assert(nextiter(&i) is done); - set(&buf, local("/"))!; - i = iter(&buf); + set(&p, local("/"))!; + i = iter(&p); assert(nextiter(&i) as str == local("/")); assert(nextiter(&i) is done); - i = riter(&buf); + i = riter(&p); assert(nextiter(&i) as str == local("/")); assert(nextiter(&i) is done); }; diff --git a/path/path.ha b/path/path.ha @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MPL-2.0 +// (c) Hare authors <https://harelang.org> + +use bytes; +use strings; + +export type path = struct { + buf: [MAX]u8, + end: size, +}; + +// Initializes a new [[path]] +export fn init(items: str...) (path | error) = { + let p = path { ... }; + push(&p, items...)?; + return p; +}; + +// Sets the value of a [[path]] to a list of components, overwriting any +// previous value. Returns the new string value of the path. +export fn set(p: *path, items: str...) (str | error) = { + p.end = 0; + return push(p, items...); +}; + +// Returns the current path stored in this [[path]]'s buffer +// The return value is borrowed from the [[path]]. +// Use [[strings::dup]] to extend the lifetime of the string. +export fn string(p: *path) str = { + if (p.end == 0) return "."; + return strings::fromutf8_unsafe(p.buf[..p.end]); +}; + +// Checks if a [[path]] is an absolute path. +export fn abs(p: (*path | str)) bool = match (p) { +case let s: str => return strings::hasprefix(s, sepstr); +case let p: *path => return 0 < p.end && p.buf[0] == SEP; +}; + +// Checks if a [[path]] is the root directory. +export fn isroot(p: (*path | str)) bool = match (p) { +case let s: str => return s == sepstr; +case let p: *path => return p.end == 1 && p.buf[0] == SEP; +}; + +// Replaces all instances of '/' in a string with [[SEP]]. The result is +// statically-allocated. +export fn local(path: str) str = { + static let buf: [MAX]u8 = [0...]; + return _local(path, &buf); +}; + +fn _local(path: str, buf: *[MAX]u8) str = { + let buf = buf[..0]; + const path = strings::toutf8(path); + for (let i = 0z; i < len(path); i += 1) { + if (path[i] == '/') { + static append(buf, SEP)!; + } else { + static append(buf, path[i])!; + }; + }; + return strings::fromutf8(buf)!; +}; diff --git a/path/prefix.ha b/path/prefix.ha @@ -4,47 +4,47 @@ use bytes; use strings; -// Add a prefix to a buffer. The buffer will be modified, and it will -// remain normalized, so any ".." components in the original buffer may be +// Add a prefix to a [[path]]. The [[path]] will be modified, and it will +// remain normalized, so any ".." components in the original [[path]] may be // collapsed. -export fn prepend(buf: *buffer, prefix: str...) (str | error) = { - static let tmp = buffer { ... }; - tmp = *buf; - set(buf, prefix...)?; - return push(buf, string(&tmp)); +export fn prepend(p: *path, prefix: str...) (str | error) = { + static let tmp = path { ... }; + tmp = *p; + set(p, prefix...)?; + return push(p, string(&tmp)); }; -// Returns a buffer without a prefix. The prefix is normalized before +// Returns a [[path]] without a prefix. The prefix is normalized before // processing, and this function will return [[too_long]] if the prefix is // longer than [[MAX]]. If the prefix is not present, returns [[not_prefix]]. // The resulting path will always be relative. // -// This function does not modify the buffer. See [[popprefix]]. -export fn trimprefix(buf: *buffer, prefix: str) (str | error) = { - const start = splitprefix(buf, prefix)?; - if (start == buf.end) return "."; - return strings::fromutf8_unsafe(buf.buf[start..buf.end]); +// This function does not modify the [[path]]. See [[popprefix]]. +export fn trimprefix(p: *path, prefix: str) (str | error) = { + const start = splitprefix(p, prefix)?; + if (start == p.end) return "."; + return strings::fromutf8_unsafe(p.buf[start..p.end]); }; -// Equivalent to [[trimprefix]], but modifies the buffer in the process. -export fn popprefix(buf: *buffer, prefix: str) (str | error) = { - const start = splitprefix(buf, prefix)?; - static delete(buf.buf[..][..start]); - buf.end -= start; - return string(buf); +// Equivalent to [[trimprefix]], but modifies the [[path]] in the process. +export fn popprefix(p: *path, prefix: str) (str | error) = { + const start = splitprefix(p, prefix)?; + static delete(p.buf[..][..start]); + p.end -= start; + return string(p); }; // helper function for trimprefix and popprefix, returns the new -// start of the buffer, or an error. -fn splitprefix(buf: *buffer, prefix: str) (size | error) = { +// start of the [[path]], or an error. +fn splitprefix(p: *path, prefix: str) (size | error) = { let pref = init(prefix)?; if (pref.end == 0) { - if (abs(buf)) return not_prefix; - } else if (pref.end < buf.end && pref.buf[pref.end-1] != SEP) { + if (abs(p)) return not_prefix; + } else if (pref.end < p.end && pref.buf[pref.end-1] != SEP) { pref.buf[pref.end] = SEP; pref.end += 1; }; - if (bytes::hasprefix(buf.buf[..buf.end], pref.buf[..pref.end])) { + if (bytes::hasprefix(p.buf[..p.end], pref.buf[..pref.end])) { return pref.end; } else { return not_prefix; @@ -52,23 +52,23 @@ fn splitprefix(buf: *buffer, prefix: str) (size | error) = { }; @test fn prepend() void = { - const buf = init("a")!; + const p = init("a")!; // relative - assert(prepend(&buf, "apple")! == local("apple/a")); - assert(popprefix(&buf, "b") is error); - assert(popprefix(&buf, "appl") is error); - assert(popprefix(&buf, local("/")) is error); - assert(popprefix(&buf, ".")! == local("apple/a")); - assert(popprefix(&buf, "apple")! == "a"); - assert(popprefix(&buf, "a")! == "."); + assert(prepend(&p, "apple")! == local("apple/a")); + assert(popprefix(&p, "b") is error); + assert(popprefix(&p, "appl") is error); + assert(popprefix(&p, local("/")) is error); + assert(popprefix(&p, ".")! == local("apple/a")); + assert(popprefix(&p, "apple")! == "a"); + assert(popprefix(&p, "a")! == "."); // absolute - assert(prepend(&buf, local("/apple/a"))! == local("/apple/a")); - assert(popprefix(&buf, local("/b")) is error); - assert(popprefix(&buf, local("/appl")) is error); - assert(popprefix(&buf, ".") is error); - assert(popprefix(&buf, local("/"))! == local("apple/a")); - assert(prepend(&buf, local("/"))! == local("/apple/a")); - assert(popprefix(&buf, local("/apple/a"))! == "."); + assert(prepend(&p, local("/apple/a"))! == local("/apple/a")); + assert(popprefix(&p, local("/b")) is error); + assert(popprefix(&p, local("/appl")) is error); + assert(popprefix(&p, ".") is error); + assert(popprefix(&p, local("/"))! == local("apple/a")); + assert(prepend(&p, local("/"))! == local("/apple/a")); + assert(popprefix(&p, local("/apple/a"))! == "."); }; diff --git a/path/stack.ha b/path/stack.ha @@ -4,35 +4,35 @@ use bytes; use strings; -// Appends path elements onto the end of a path buffer. +// Appends path elements onto the end of a [[path]] buffer. // Returns the new string value of the path. -export fn push(buf: *buffer, items: str...) (str | error) = { +export fn push(p: *path, items: str...) (str | error) = { for (let item .. items) { let elem = strings::toutf8(item); for (true) match (bytes::index(elem, SEP)) { case void => - buf.end = appendnorm(buf, elem)?; + p.end = appendnorm(p, elem)?; break; case let j: size => - if (j == 0 && buf.end == 0) { - buf.buf[0] = SEP; - buf.end = 1; + if (j == 0 && p.end == 0) { + p.buf[0] = SEP; + p.end = 1; } else { - buf.end = appendnorm(buf, elem[..j])?; + p.end = appendnorm(p, elem[..j])?; }; elem = elem[j+1..]; }; }; - return string(buf); + return string(p); }; const dot: []u8 = ['.']; const dotdot: []u8 = ['.', '.']; -// append a path segment to a buffer, preserving normalization. +// append a path segment to a [[path]], preserving normalization. // seg must not contain any [[SEP]]s. if you need to make the path absolute, you -// should do that manually. returns the new end of the buffer. +// should do that manually. returns the new end of the [[path]]. // x + => x // x + . => x // / + .. => / @@ -40,141 +40,141 @@ const dotdot: []u8 = ['.', '.']; // x/.. + .. => x/../.. // x/y + .. => x // x + y => x/y -fn appendnorm(buf: *buffer, seg: []u8) (size | error) = { - if (len(seg) == 0 || bytes::equal(dot, seg)) return buf.end; +fn appendnorm(p: *path, seg: []u8) (size | error) = { + if (len(seg) == 0 || bytes::equal(dot, seg)) return p.end; if (bytes::equal(dotdot, seg)) { - if (isroot(buf)) return buf.end; - const isep = match (bytes::rindex(buf.buf[..buf.end], SEP)) { + if (isroot(p)) return p.end; + const isep = match (bytes::rindex(p.buf[..p.end], SEP)) { case void => yield 0z; case let i: size => yield i + 1; }; - if (buf.end == 0 || bytes::equal(buf.buf[isep..buf.end], dotdot)) { - return appendlit(buf, dotdot)?; + if (p.end == 0 || bytes::equal(p.buf[isep..p.end], dotdot)) { + return appendlit(p, dotdot)?; } else { return if (isep <= 1) isep else isep - 1; }; } else { - return appendlit(buf, seg)?; + return appendlit(p, seg)?; }; }; -// append a segment to a buffer, *without* preserving normalization. -// returns the new end of the buffer -fn appendlit(buf: *buffer, bs: []u8) (size | error) = { - let newend = buf.end; - if (buf.end == 0 || isroot(buf)) { - if (MAX < buf.end + len(bs)) return too_long; +// append a segment to a [[path]], *without* preserving normalization. +// returns the new end of the [[path]] +fn appendlit(p: *path, bs: []u8) (size | error) = { + let newend = p.end; + if (p.end == 0 || isroot(p)) { + if (MAX < p.end + len(bs)) return too_long; } else { - if (MAX < buf.end + len(bs) + 1) return too_long; - buf.buf[buf.end] = SEP; + if (MAX < p.end + len(bs) + 1) return too_long; + p.buf[p.end] = SEP; newend += 1; }; - buf.buf[newend..newend+len(bs)] = bs; + p.buf[newend..newend+len(bs)] = bs; return newend + len(bs); }; @test fn push() void = { - let buf = init()!; - assert(string(&buf) == "."); + let p = init()!; + assert(string(&p) == "."); // current dir invariants - assert(push(&buf, "")! == "."); - assert(push(&buf, ".")! == "."); + assert(push(&p, "")! == "."); + assert(push(&p, ".")! == "."); // parent dir invariants - assert(push(&buf, "..")! == ".."); - assert(push(&buf, "")! == ".."); - assert(push(&buf, ".")! == ".."); - assert(push(&buf, local("/"))! == ".."); + assert(push(&p, "..")! == ".."); + assert(push(&p, "")! == ".."); + assert(push(&p, ".")! == ".."); + assert(push(&p, local("/"))! == ".."); - assert(set(&buf)! == "."); + assert(set(&p)! == "."); // root dir invariants - assert(push(&buf, local("/"))! == local("/")); - assert(push(&buf, "")! == local("/")); - assert(push(&buf, ".")! == local("/")); - assert(push(&buf, "..")! == local("/")); - assert(push(&buf, local("/"))! == local("/")); + assert(push(&p, local("/"))! == local("/")); + assert(push(&p, "")! == local("/")); + assert(push(&p, ".")! == local("/")); + assert(push(&p, "..")! == local("/")); + assert(push(&p, local("/"))! == local("/")); - assert(set(&buf)! == "."); + assert(set(&p)! == "."); // regular path and parent - assert(push(&buf, "foo")! == "foo"); - assert(push(&buf, ".")! == "foo"); - assert(push(&buf, local("/"))! == "foo"); - assert(push(&buf, "..")! == "."); + assert(push(&p, "foo")! == "foo"); + assert(push(&p, ".")! == "foo"); + assert(push(&p, local("/"))! == "foo"); + assert(push(&p, "..")! == "."); // multiple segments - assert(push(&buf, "a", "b")! == local("a/b")); - assert(push(&buf, "..", "c")! == local("a/c")); - assert(push(&buf, "..")! == "a"); - assert(push(&buf, local("/d"))! == local("a/d")); - assert(push(&buf, "..", "..")! == "."); + assert(push(&p, "a", "b")! == local("a/b")); + assert(push(&p, "..", "c")! == local("a/c")); + assert(push(&p, "..")! == "a"); + assert(push(&p, local("/d"))! == local("a/d")); + assert(push(&p, "..", "..")! == "."); // multiple segments, absolute - assert(push(&buf, local("/"), "a", "b")! == local("/a/b")); - assert(push(&buf, "..", "c")! == local("/a/c")); - assert(push(&buf, "..")! == local("/a")); - assert(push(&buf, local("/d"))! == local("/a/d")); - assert(push(&buf, "..", "..", "..")! == local("/")); + assert(push(&p, local("/"), "a", "b")! == local("/a/b")); + assert(push(&p, "..", "c")! == local("/a/c")); + assert(push(&p, "..")! == local("/a")); + assert(push(&p, local("/d"))! == local("/a/d")); + assert(push(&p, "..", "..", "..")! == local("/")); }; -// Examine the final path segment in a buffer. +// Examine the final path segment in a [[path]]. // Returns void if the path is empty or is the root dir. -export fn peek(buf: *const buffer) (str | void) = split(buf).1; +export fn peek(p: *const path) (str | void) = split(p).1; -// Remove and return the final path segment in a buffer. +// Remove and return the final path segment in a [[path]]. // Returns void if the path is empty or is the root dir. -export fn pop(buf: *buffer) (str | void) = { - const (end, res) = split(buf); - buf.end = end; +export fn pop(p: *path) (str | void) = { + const (end, res) = split(p); + p.end = end; return res; }; -// helper function for pop/peek, returns (new end of buffer, result) -fn split(buf: *buffer) (size, (str | void)) = { - if (buf.end == 0 || isroot(buf)) return (buf.end, void); - match (bytes::rindex(buf.buf[..buf.end], SEP)) { +// helper function for pop/peek, returns (new end of [[path]], result) +fn split(p: *path) (size, (str | void)) = { + if (p.end == 0 || isroot(p)) return (p.end, void); + match (bytes::rindex(p.buf[..p.end], SEP)) { case void => - return (0z, strings::fromutf8_unsafe(buf.buf[..buf.end])); + return (0z, strings::fromutf8_unsafe(p.buf[..p.end])); case let i: size => return ( if (i == 0) 1z else i, - strings::fromutf8_unsafe(buf.buf[i+1..buf.end]), + strings::fromutf8_unsafe(p.buf[i+1..p.end]), ); }; }; @test fn pop() void = { // empty - let buf = init()!; - assert(pop(&buf) is void); - assert(string(&buf) == "."); + let p = init()!; + assert(pop(&p) is void); + assert(string(&p) == "."); // root dir - buf.end = 0; - push(&buf, local("/"))!; - assert(pop(&buf) is void); - assert(string(&buf) == local("/")); + p.end = 0; + push(&p, local("/"))!; + assert(pop(&p) is void); + assert(string(&p) == local("/")); // relative file - buf.end = 0; - push(&buf, "foo")!; - assert(pop(&buf) as str == "foo"); - assert(string(&buf) == "."); + p.end = 0; + push(&p, "foo")!; + assert(pop(&p) as str == "foo"); + assert(string(&p) == "."); // abs file - buf.end = 0; - push(&buf, local("/foo"))!; - assert(pop(&buf) as str == "foo"); - assert(string(&buf) == local("/")); + p.end = 0; + push(&p, local("/foo"))!; + assert(pop(&p) as str == "foo"); + assert(string(&p) == local("/")); }; -// Returns the parent directory for a given path, without modifying the buffer. +// Returns the parent directory for a given path, without modifying the [[path]]. // If the path is the root directory, the root directory is returned. The value // is either borrowed from the input or statically allocated; use // [[strings::dup]] to extend its lifetime or modify it. -export fn parent(buf: *const buffer) (str | error) = { - const newend = appendnorm(buf, dotdot)?; +export fn parent(p: *const path) (str | error) = { + const newend = appendnorm(p, dotdot)?; if (newend == 0) return "."; - return strings::fromutf8_unsafe(buf.buf[..newend]); + return strings::fromutf8_unsafe(p.buf[..newend]); }; diff --git a/temp/+freebsd.ha b/temp/+freebsd.ha @@ -47,7 +47,7 @@ export fn named( oflags |= fs::flag::WRONLY; }; - static let pathbuf = path::buffer { ... }; + static let pathbuf = path::path { ... }; static let namebuf: [32]u8 = [0...]; for (true) { let id = 0u64; @@ -83,9 +83,9 @@ export fn dir() str = { io::write(&enc, buf)!; let name = memio::string(&sink)!; - static let buf = path::buffer { ... }; - path::set(&buf, get_tmpdir(), name)!; - const path = path::string(&buf); + static let p = path::path { ... }; + path::set(&p, get_tmpdir(), name)!; + const path = path::string(&p); match (os::mkdir(path,0o755)) { case let err: fs::error => abort("Could not create temp directory"); case void => void; diff --git a/temp/+linux.ha b/temp/+linux.ha @@ -57,7 +57,7 @@ export fn named( oflags |= fs::flag::WRONLY; }; - static let pathbuf = path::buffer { ... }; + static let pathbuf = path::path { ... }; static let namebuf: [32]u8 = [0...]; for (true) { let id = 0u64; @@ -93,9 +93,9 @@ export fn dir() str = { io::write(&enc, buf) as size; const name = memio::string(&sink)!; - static let buf = path::buffer { ... }; - path::set(&buf, get_tmpdir(), name)!; - const path = path::string(&buf); + static let p = path::path { ... }; + path::set(&p, get_tmpdir(), name)!; + const path = path::string(&p); match (os::mkdir(path, 0o755)) { case let err: fs::error => abort("Could not create temp directory"); case void => void; diff --git a/time/chrono/tzdb.ha b/time/chrono/tzdb.ha @@ -36,7 +36,7 @@ export fn tz(name: str) (locality | tzdberror) = { yield path::init(TZDB_PATH, name); }; const filepath = match (filepath) { - case let buf: path::buffer => + case let buf: path::path => yield buf; case let err: path::error => assert(err is path::too_long);