116 lines
2.9 KiB
Hare
116 lines
2.9 KiB
Hare
|
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, '[', ']', '=');
|
||
|
};
|