From ecbbccbaa68e28f36675bfcfb184e2a25e044c66 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 21 Oct 2024 12:48:21 -0400 Subject: [PATCH] Add untested value parsing --- format/desktop_entry/value.ha | 178 ++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 format/desktop_entry/value.ha diff --git a/format/desktop_entry/value.ha b/format/desktop_entry/value.ha new file mode 100644 index 0000000..ed3434e --- /dev/null +++ b/format/desktop_entry/value.ha @@ -0,0 +1,178 @@ +use ascii; +use strconv; +use strings; + +// Parses a string value. It may contain all ASCII characters except for control +// characters. The memory must be freed by the caller. +// Specification: §4 +export fn parse_string(in: str) (str | error) = { + for (let char .. strings::toutf8(in)) { + if (!ascii::valid(char: rune)) return invalid_ascii; + }; + return parse_localestring(in); +}; + +// Parses a localestring value. It is user displayable, and is encoded in UTF-8. +// The memory must be freed by the caller. +// Specification: §4 +export fn parse_localestring(in: str) (str | error) = { + let escaper = escape_string(in); + let string = match(string_escaper_next(&escaper)?) { + case let string: str => yield string; + case done => return ""; + }; + if (len(escaper) > 0) { + free(string); + return expected_single; + }; + return string; +}; + +// Parses an iconstring value. It is the name of an icon; it may be an absolute +// path, or a symbolic name for an icon located using the algorithm described in +// the Icon Theme Specification. Such values are not user-displayable, and are +// encoded in UTF-8. The memory must be freed by the caller. +// Specification: §4 +export fn parse_iconstring(in: str) (str | error) = parse_localestring(in); + +// Parses a boolean value. It must either be the string "true" or "false". +// Specification: §4 +export fn parse_boolean(in: str) (bool | error) = { + if (in == "true" ) return true; + if (in == "false") return false; + return invalid_boolean; +}; + +// Parses a numeric value. It must be a valid floating point number as +// recognized by the %f specifier for scanf in the C locale. +// Specification: §4 +export fn parse_numeric(in: str) (f64 | error) = match (strconv::stof64(in)) { +case let float: f64 => yield float; +case => yield invalid_numeric; +}; + +// Parses multiple string values. See [[parse_string]] for more information. The +// memory must be freed by the caller. +// Specification: §4 +export fn parse_strings(in: str) ([]str | error) = { + for (let char .. strings::toutf8(in)) { + if (!ascii::valid(char: rune)) return invalid_ascii; + }; + return parse_localestrings(in); +}; + +// Parses multiple localestring values. See [[parse_localestring]] for more +// information. The memory must be freed by the caller. +// Specification: §4 +export fn parse_localestrings(in: str) ([]str | error) = { + let escaper = escape_string(in); + let result: []str = alloc([], 0); + for (let in => string_escaper_next(&escaper)) { + match (in) { + case let string: str => + append(result, string); + case let err: error => + strings::freeall(result); + return err; + }; + }; + return result; +}; + +// Parses multiple iconstring values. See [[parse_iconstring]] for more +// information. The memory must be freed by the caller. +// Specification: §4 +export fn parse_iconstrings(in: str) ([]str | error) = parse_localestrings(in); + +// Parses multiple boolean values. See [[parse_boolean]] for more information. +// The memory must be freed by the caller. +// Specification: §4 +export fn parse_booleans(in: str) ([]bool | error) = { + let splitte = split(in); + let result: []bool = alloc([], 0); + for (let in => splitter_next(&splitte)) { + match (parse_boolean(in)) { + case let boolean: bool => + append(result, boolean); + case let err: error => + free(result); + return err; + }; + }; + return result; +}; + +// Parses multiple numeric values. See [[parse_numeric]] for more information. +// The memory must be freed by the caller. +// Specification: §4 +export fn parse_numerics(in: str) ([]f64 | error) = { + let splitte = split(in); + let result: []f64 = alloc([], 0); + for (let in => splitter_next(&splitte)) { + match (parse_numeric(in)) { + case let number: f64 => + append(result, number); + case let err: error => + free(result); + return err; + }; + }; + return result; +}; + +type string_escaper = []u8; + +fn escape_string(in: str) string_escaper = strings::toutf8(in): string_escaper; + +fn string_escaper_next(this: *string_escaper) (str | done | error) = { + if (len(*this) < 1) return done; + let result: []u8 = alloc([], 0); + let end = 0z; + let saw_escape = false; + for (let byte .. *this) { + end += 1; + if (saw_escape) { + switch (byte) { + case ';' => append(result, ';'); + case 's' => append(result, ' '); + case 'n' => append(result, '\n'); + case 't' => append(result, '\t'); + case 'r' => append(result, '\r'); + case '\\' => append(result, '\\'); + case => + free(result); + return invalid_escape; + }; + saw_escape = false; + } else { + switch (byte) { + case ';' => break; + case '\\' => saw_escape = true; + case => append(result, byte); + }; + }; + }; + *this = (*this)[end..]; + return strings::fromutf8_unsafe(result); +}; + +type splitter = []u8; + +fn split(in: str) splitter = strings::toutf8(in): splitter; + +fn splitter_next(this: *splitter) (str | done) = { + if (len(*this) < 1) return done; + let end = 0z; + let knife = 0z; + for (let byte .. *this) { + knife += 1; + if (byte == ';') { + break; + } else { + end += 1; + }; + }; + let result = this[..end]; + *this = (*this)[knife..]; + return strings::fromutf8_unsafe(result); +};