hare

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

commit d0da286289e8d17e89ebeebbd5bdc1cd3df60076
parent 34fdb97585fee2ce307b1190d8e5dd0a8ccf8936
Author: Alexey Yerin <yyp@disroot.org>
Date:   Sat, 25 Sep 2021 10:48:49 +0300

format::ini: new module

Signed-off-by: Alexey Yerin <yyp@disroot.org>

Diffstat:
Aformat/ini/+test.ha | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aformat/ini/scan.ha | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aformat/ini/types.ha | 21+++++++++++++++++++++
Mscripts/gen-stdlib | 18++++++++++++++++++
Mstdlib.mk | 31+++++++++++++++++++++++++++++++
5 files changed, 237 insertions(+), 0 deletions(-)

diff --git a/format/ini/+test.ha b/format/ini/+test.ha @@ -0,0 +1,75 @@ +use bufio; +use io; +use strings; + +@test fn simple() void = { + const buf = bufio::fixed(strings::toutf8( +"# This is a comment +[sourcehut.org] +name=Sourcehut +description=The hacker's forge +[harelang.org] +name=Hare +description=The Hare programming language"), io::mode::READ); + defer io::close(buf); + const sc = scan(buf); + defer finish(&sc); + + // [sourcehut.org] + ini_test(&sc, "sourcehut.org", "name", "Sourcehut"); + ini_test(&sc, "sourcehut.org", "description", "The hacker's forge"); + // [harelang.org] + ini_test(&sc, "harelang.org", "name", "Hare"); + ini_test(&sc, "harelang.org", "description", + "The Hare programming language"); + assert(next(&sc) is io::EOF); +}; + +@test fn extended() void = { + // TODO: expand? + const buf = bufio::fixed(strings::toutf8( +"# Equal sign in the value +exec=env VARIABLE=value binary + +# Unicode +trademark=™ +"), io::mode::READ); + defer io::close(buf); + const sc = scan(buf); + defer finish(&sc); + + ini_test(&sc, "", "exec", "env VARIABLE=value binary"); + ini_test(&sc, "", "trademark", "™"); + assert(next(&sc) is io::EOF); +}; + + +@test fn invalid() void = { + // Missing equal sign + const buf = bufio::fixed(strings::toutf8("novalue\n"), io::mode::READ); + defer io::close(buf); + const sc = scan(buf); + defer finish(&sc); + + assert(next(&sc) as error is syntaxerr); // TODO: test line numbering? + + // Unterminated section header + const buf = bufio::fixed(strings::toutf8("[dangling\n"), io::mode::READ); + defer io::close(buf); + const sc = scan(buf); + defer finish(&sc); + + assert(next(&sc) as error is syntaxerr); +}; + +fn ini_test( + sc: *scanner, + section: const str, + key: const str, + value: const str, +) void = { + const ent = next(sc)! as entry; + assert(ent.0 == section); + assert(ent.1 == key); + assert(ent.2 == value); +}; diff --git a/format/ini/scan.ha b/format/ini/scan.ha @@ -0,0 +1,92 @@ +use bufio; +use encoding::utf8; +use fmt; +use io; +use strings; + +export type scanner = struct { + in: *io::stream, + line: str, + lineno: size, + section: str, +}; + +// Creates an INI file scanner. Use [[next]] to read entries. The caller must +// call [[finish]] once they're done with this object. +export fn scan(in: *io::stream) scanner = scanner { + in = in, + lineno = 1, + ... +}; + +// Frees resources associated with a [[scanner]]. +export fn finish(sc: *scanner) void = { + free(sc.line); + free(sc.section); +}; + +// An entry in an INI file: (section, key, value). +export type entry = (const str, const str, const str); + +// Duplicates an [[entry]]. Use [[entry_finish]] to get rid of it. +export fn entry_dup(ent: entry) entry = ( + strings::dup(ent.0), + strings::dup(ent.1), + strings::dup(ent.2), +); + +// Frees an [[entry]] previously duplicated with [[entry_dup]]. +export fn entry_finish(ent: entry) void = { + free(ent.0); + free(ent.1); + free(ent.2); +}; + +// Returns the next entry from an INI file. The return value is overwritten on +// subsequent calls, use [[entry_dup]] or [[strings::dup]] to extend the +// lifetime of the entry or its fields respectively. +export fn next(sc: *scanner) (entry | io::EOF | error) = { + for (true) { + const line = match (bufio::scanline(sc.in)?) { + case b: []u8 => + yield strings::try_fromutf8(b)?; + case io::EOF => + return io::EOF; + }; + sc.lineno += 1; + + free(sc.line); + sc.line = line; + + const line = strings::trim(sc.line); + + if (len(line) == 0 || strings::has_prefix(line, "#")) { + continue; + }; + + if (strings::has_prefix(line, "[")) { + const end = match (strings::index(line, ']')) { + case idx: size => + yield idx; + case void => + return (sc.in.name, sc.lineno): syntaxerr; + }; + free(sc.section); + sc.section = strings::dup(strings::sub(line, 1, end)); + continue; + }; + + const eq = match (strings::index(line, '=')) { + case idx: size => + yield idx; + case void => + return (sc.in.name, sc.lineno): syntaxerr; + }; + return ( + sc.section, + strings::sub(line, 0, eq), + strings::sub(line, eq + 1, strings::end), + ); + }; + abort(); // Unreachable +}; diff --git a/format/ini/types.ha b/format/ini/types.ha @@ -0,0 +1,21 @@ +use encoding::utf8; +use fmt; +use io; + +// A syntax error occured during parsing. +export type syntaxerr = !(str, size); + +// Any error that may occur during parsing. +export type error = !(io::error | utf8::invalid | syntaxerr); + +// Returns a user-friendly representation of [[error]]. +export fn strerror(err: error) const str = match (err) { +case err: io::error => + return io::strerror(err); +case utf8::invalid => + return "File is invalid UTF-8"; +case s: syntaxerr => + // XXX: tuple unpacking could improve this + static let buf: [1024]u8 = [0...]; + yield fmt::bsprintf(buf, "{}:{}: Invalid syntax", s.0, s.1); +}; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -308,6 +308,23 @@ format_elf() { gen_ssa format::elf } +gensrcs_format_ini() { + gen_srcs format::ini \ + scan.ha \ + types.ha \ + $* +} + +format_ini() { + if [ $testing -eq 0 ] + then + gensrcs_format_ini + else + gensrcs_format_ini +test.ha + fi + gen_ssa format::ini bufio encoding::utf8 fmt io strings +} + gensrcs_format_xml() { gen_srcs format::xml \ types.ha \ @@ -874,6 +891,7 @@ errors fmt fnmatch format::elf +format::ini format::xml fs fs::mem diff --git a/stdlib.mk b/stdlib.mk @@ -162,6 +162,10 @@ hare_stdlib_deps+=$(stdlib_fnmatch) stdlib_format_elf=$(HARECACHE)/format/elf/format_elf.o hare_stdlib_deps+=$(stdlib_format_elf) +# gen_lib format::ini +stdlib_format_ini=$(HARECACHE)/format/ini/format_ini.o +hare_stdlib_deps+=$(stdlib_format_ini) + # gen_lib format::xml stdlib_format_xml=$(HARECACHE)/format/xml/format_xml.o hare_stdlib_deps+=$(stdlib_format_xml) @@ -589,6 +593,17 @@ $(HARECACHE)/format/elf/format_elf.ssa: $(stdlib_format_elf_srcs) $(stdlib_rt) @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nformat::elf \ -t$(HARECACHE)/format/elf/format_elf.td $(stdlib_format_elf_srcs) +# format::ini +stdlib_format_ini_srcs= \ + $(STDLIB)/format/ini/scan.ha \ + $(STDLIB)/format/ini/types.ha + +$(HARECACHE)/format/ini/format_ini.ssa: $(stdlib_format_ini_srcs) $(stdlib_rt) $(stdlib_bufio) $(stdlib_encoding_utf8) $(stdlib_fmt) $(stdlib_io) $(stdlib_strings) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/format/ini + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nformat::ini \ + -t$(HARECACHE)/format/ini/format_ini.td $(stdlib_format_ini_srcs) + # format::xml stdlib_format_xml_srcs= \ $(STDLIB)/format/xml/types.ha \ @@ -1375,6 +1390,10 @@ hare_testlib_deps+=$(testlib_fnmatch) testlib_format_elf=$(TESTCACHE)/format/elf/format_elf.o hare_testlib_deps+=$(testlib_format_elf) +# gen_lib format::ini +testlib_format_ini=$(TESTCACHE)/format/ini/format_ini.o +hare_testlib_deps+=$(testlib_format_ini) + # gen_lib format::xml testlib_format_xml=$(TESTCACHE)/format/xml/format_xml.o hare_testlib_deps+=$(testlib_format_xml) @@ -1810,6 +1829,18 @@ $(TESTCACHE)/format/elf/format_elf.ssa: $(testlib_format_elf_srcs) $(testlib_rt) @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nformat::elf \ -t$(TESTCACHE)/format/elf/format_elf.td $(testlib_format_elf_srcs) +# format::ini +testlib_format_ini_srcs= \ + $(STDLIB)/format/ini/scan.ha \ + $(STDLIB)/format/ini/types.ha \ + $(STDLIB)/format/ini/+test.ha + +$(TESTCACHE)/format/ini/format_ini.ssa: $(testlib_format_ini_srcs) $(testlib_rt) $(testlib_bufio) $(testlib_encoding_utf8) $(testlib_fmt) $(testlib_io) $(testlib_strings) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/format/ini + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nformat::ini \ + -t$(TESTCACHE)/format/ini/format_ini.td $(testlib_format_ini_srcs) + # format::xml testlib_format_xml_srcs= \ $(STDLIB)/format/xml/types.ha \