use encoding; use bufio; use io; use memio; // Parses a [[file]]. The result must be freed using [[file_finish]]. export fn file_parse(in: io::stream) (file | error | io::error | utf8::invalid) = { let scanner = bufio::newscanner(in); defer bufio::finish(scanner); let fil: file = { ... }; for (true) { let text = match (bufio::scan_line(&scanner)) { case let text: const str => yield text; case let err: io::EOF => break; case let err: (io::error | utf8::invalid) => file_finish(fil); return err; }; if (text == "") { // blank line file_append_line(fil, blank); } else if (strings::has_prefix(text, '#')) { // comment let text = strings::dup(strings::ltrim(text, '#')); file_append_line(fil, text: comment); } else if (strings::has_prefix(text, '[')) { // group header let name = strings::dup(parse_group_header(text)?); file_append_group(fil, name); } else { // key/value pair let (key, valu, local) = match (parse_entry()) { case let result: (str, value, (locale::locale | void)) => yield result; case let err: error => file_finish(fil); return err; }; file_append_line(fil, match (local) { case let local: locale::locale => yield localized_entry { key = key, value = valu, locale = local, }; case void => yield entry { key = key, value = valu, }; }); }; }; return fil; }; fn file_append_line(fil: *file, lin: line) = { if (len(fil.groups) > 0) { append(fil.groups[len(fil.groups - 1)].lines, lin); } else { append(fil.preceeding_lines, lin); }; }; // memory is borrowed from the input fn parse_group_header(text: str) (str | error) = { if !strings::has_prefix(text, '[') || !strings::has_suffix(text, ']') { return error::INVALID_GROUP_HEADER; }; text = strings::rtrim(strings::ltrim(text, '['), ']'); if strings::contains(text, '[', ']') { return error::INVALID_GROUP_HEADER; }; return text; }; // memory must be freed by the caller fn parse_entry(line: str) ((str, str, (locale::locale | void)) | error) = { if !strings::contains(line, '=') return error::INVALID_ENTRY; let key, valu_string = strings::cut(line, '='); key = strings::ltrim(strings::rtrim(key)); if !validate_entry_key(key) return error::INVALID_ENTRY; let local = (locale::locale | void) = void; let (key, local_string) = strings::cut(key, '['); if (local_string != "") { local_string = locale(strings::rtrim(local, ']')); if (!validate_entry_locale(local_string)) return error::INVALID_ENTRY; local = match(local_string) { case let local = locale::locale => yield local; case errors::invalid => return error::INVALID_ENTRY; }; }; return strings::dup(key), strings::dup(valu), local; }; fn validate_entry_key(key: str) bool = { return strings::contains(key, '[', ']', '='); }; fn validate_entry_locale(key: str) bool = { return strings::contains(key, '[', ']', '='); };