hare

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

commit ba1f76e8a971de8879a59dac59f40488eba16abc
parent 90078264a1d894e6023b52127651c6a9e78ff734
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun,  2 Jun 2024 18:54:58 +0200

unix::hosts: rewrite with bufio::scanner

This is a breaking change. I also took the opportunity here to clean up
the API design, and iter_lookup is now private.

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Aunix/hosts/errors.ha | 27+++++++++++++++++++++++++++
Munix/hosts/hosts.ha | 153++++++++++++++++++++++++++++++++++++-------------------------------------------
Munix/hosts/test+test.ha | 39+++++++++++++++++++--------------------
3 files changed, 115 insertions(+), 104 deletions(-)

diff --git a/unix/hosts/errors.ha b/unix/hosts/errors.ha @@ -0,0 +1,27 @@ +use encoding::utf8; +use fs; +use io; +use net::ip; + +// Returned when an invalid host line was found. +export type invalid = !void; + +// All possible errors returned from this module. +export type error = !(io::error | invalid | utf8::invalid | ip::invalid + | fs::error); + +// Converts an [[error]] to a human-friendly representation. +export fn strerror(err: error) const str = { + match (err) { + case invalid => + return "Host file format is invalid"; + case utf8::invalid => + return "File is invalid UTF-8"; + case ip::invalid => + return "IP address is invalid"; + case let err: io::error => + return io::strerror(err); + case let err: fs::error => + return fs::strerror(err); + }; +}; diff --git a/unix/hosts/hosts.ha b/unix/hosts/hosts.ha @@ -2,10 +2,10 @@ // (c) Hare authors <https://harelang.org> use bufio; +use errors; use encoding::utf8; use fs; use io; -use memio; use net::ip; use os; use strings; @@ -17,112 +17,97 @@ export type host = struct { names: []str, }; -// Iterator through the host lines in a host file. -export type iterator = struct { - handle: io::handle, +export type reader = struct { + scan: bufio::scanner, + names: []str, }; -// Returned when an invalid host line was found. -export type invalid = !void; - -// All possible errors returned from this module. -export type error = !(io::error | invalid | utf8::invalid | ip::invalid - | fs::error); - -// Converts an [[error]] to a human-friendly representation. -export fn strerror(err: error) const str = match (err) { -case invalid => - return "Host file format is invalid"; -case utf8::invalid => - return "File is invalid UTF-8"; -case ip::invalid => - return "IP address is invalid"; -case let err: io::error => - return io::strerror(err); -case let err: fs::error => - return fs::strerror(err); +// Read from an /etc/hosts-formatted file. Call [[next]] to enumerate entries +// and [[finish]] to free state associated with the [[reader]]. +export fn read(in: io::handle) reader = { + return reader { + scan = bufio::newscanner(in), + names = [], + }; }; -// Creates an [[iterator]] for a provided [[io::handle]] pointing to -// the /etc/hosts file. The user should call [[next]] to iterate through -// host lines. -export fn iter(in: io::handle) iterator = iterator { - handle = in, +// Frees resources associated with a [[reader]]. +export fn finish(rd: *reader) void = { + bufio::finish(&rd.scan); + free(rd.names); }; -// Returns the next host line as a [[host]] type. -export fn next(it: *iterator) (host | void | error) = for (true) { - const line = match (bufio::read_line(it.handle)) { - case io::EOF => - return void; - case let line: []u8 => - yield line; - }; - defer free(line); - if (len(line) == 0 || line[0] == '#') { - continue; - }; +// Returns the next host line as a [[host]] type. The host value is borrowed +// from the [[reader]]; see [[host_dup]] to extend its lifetime. +export fn next(rd: *reader) (host | done | error) = { + for (const line => bufio::scan_line(&rd.scan)?) { + if (len(line) == 0 || strings::hasprefix(line, "#")) { + continue; + }; - const scanner = memio::fixed(line); - const tok = match (bufio::read_tok(&scanner, ' ', '\t')?) { - case io::EOF => - return void; - case let tok: []u8 => - yield tok; - }; - defer free(tok); - const addr = ip::parse(strings::fromutf8(tok)?)?; - - let names: []str = []; - for (true) { - const tok = match (bufio::read_tok(&scanner, ' ', '\t')?) { - case io::EOF => - break; - case let tok: []u8 => - yield tok; + const tok = strings::tokenize(line, " \t"); + const addr = strings::next_token(&tok) as str; + const addr = ip::parse(addr)?; + + rd.names = rd.names[..0]; + + for (const tok => strings::next_token(&tok)) { + if (len(tok) == 0) { + continue; + }; + append(rd.names, tok); }; - if (len(tok) == 0) continue; - append(names, strings::fromutf8(tok)?); - }; - if (len(names) == 0) { - return invalid; - }; + if (len(rd.names) == 0) { + return invalid; + }; - return host { - addr = addr, - names = names, + return host { + addr = addr, + names = rd.names, + }; }; + + return done; }; -// Looks up a slice of addresses from /etc/hosts. +// Looks up a slice of addresses from /etc/hosts. The caller must free the +// return value. export fn lookup(name: const str) ([]ip::addr | error) = { const file = os::open(PATH)?; defer io::close(file)!; - let it = iter(file); - return iter_lookup(&it, name); + + const rd = read(file); + defer finish(&rd); + return _lookup(&rd, name); }; -// Looks up a slice of addresses given an [[iterator]] to /etc/hosts. -export fn iter_lookup(it: *iterator, name: const str) ([]ip::addr | error) = { +fn _lookup(rd: *reader, name: const str) ([]ip::addr | error) = { let addrs: []ip::addr = []; - for (true) match(next(it)?) { - case void => break; - case let h: host => - defer finish(h); - for (let i = 0z; i < len(h.names); i += 1) { - if (h.names[i] == name) { - append(addrs, h.addr); + for (const host => next(rd)?) { + for (const cand .. host.names) { + if (cand == name) { + append(addrs, host.addr); }; }; }; - return addrs; + + if (len(addrs) != 0) { + return addrs; + }; + + return []; }; -// Frees resources associated with a [[host]]. -export fn finish(host: host) void = { - for (let i = 0z; i < len(host.names); i += 1) { - free(host.names[i]); +// Duplicates a [[host]] value. +export fn host_dup(src: *host) host = { + return host { + addr = src.addr, + names = strings::dupall(src.names), }; - free(host.names); +}; + +// Frees resources associated with a [[host]]. +export fn host_finish(host: *host) void = { + strings::freeall(host.names); }; diff --git a/unix/hosts/test+test.ha b/unix/hosts/test+test.ha @@ -17,55 +17,54 @@ def HOSTS_FILE = ` `; @test fn next() void = { - let buf = memio::fixed(strings::toutf8(HOSTS_FILE)); - let it = iter(&buf); + const buf = memio::fixed(strings::toutf8(HOSTS_FILE)); + const rd = read(&buf); + defer finish(&rd); - const h = next(&it) as host; - defer finish(h); + const h = next(&rd) as host; assert(ip::equal(h.addr, ip::LOCAL_V4)); assert(len(h.names) == 1); assert(h.names[0] == "localhost"); - const h = next(&it) as host; - defer finish(h); + const h = next(&rd) as host; assert(ip::equal(h.addr, ip::LOCAL_V6)); assert(len(h.names) == 2); assert(h.names[0] == "ip6-localhost"); assert(h.names[1] == "ip6-loopback"); - const h = next(&it) as host; - defer finish(h); + const h = next(&rd) as host; assert(ip::equal(h.addr, [10, 10, 10, 10]: ip::addr4)); assert(len(h.names) == 1); assert(h.names[0] == "other.localdomain"); - const h = next(&it) as host; - defer finish(h); + const h = next(&rd) as host; assert(ip::equal(h.addr, [10, 10, 20, 20]: ip::addr4)); assert(len(h.names) == 1); assert(h.names[0] == "other.localdomain"); - const h = next(&it); - assert(h is void); - const h = next(&it); - assert(h is void); + const h = next(&rd); + assert(h is done); + const h = next(&rd); + assert(h is done); }; @test fn errors() void = { const s = "127"; - assert(next(&iter(&memio::fixed(strings::toutf8(s)))) + assert(next(&read(&memio::fixed(strings::toutf8(s)))) is ip::invalid); const s = "127.0.0.1"; - assert(next(&iter(&memio::fixed(strings::toutf8(s)))) + assert(next(&read(&memio::fixed(strings::toutf8(s)))) is invalid); }; @test fn lookup() void = { - let buf = memio::fixed(strings::toutf8(HOSTS_FILE)); - let it = iter(&buf); - const addrs = iter_lookup(&it, "other.localdomain") as []ip::addr; + const buf = memio::fixed(strings::toutf8(HOSTS_FILE)); + const rd = read(&buf); + defer finish(&rd); + + const addrs = _lookup(&rd, "other.localdomain") as []ip::addr; + defer free(addrs); assert(len(addrs) == 2); assert(ip::equal(addrs[0], [10, 10, 10, 10]: ip::addr4)); assert(ip::equal(addrs[1], [10, 10, 20, 20]: ip::addr4)); - free(addrs); };