Add parsing stub
This commit is contained in:
parent
baea275b66
commit
427d30c255
@ -17,7 +17,11 @@ export type group = struct {
|
|||||||
|
|
||||||
// A line in the file, which can be a comment (or a blank line), an entry, or
|
// A line in the file, which can be a comment (or a blank line), an entry, or
|
||||||
// a localized entry.
|
// a localized entry.
|
||||||
export type line = (comment | entry | localized_entry);
|
export type line = (blank | comment | entry | localized_entry);
|
||||||
|
|
||||||
|
// A blank line.
|
||||||
|
// Specification: §3.1
|
||||||
|
export type blank = void;
|
||||||
|
|
||||||
// A comment.
|
// A comment.
|
||||||
// Specification: §3.1
|
// Specification: §3.1
|
||||||
@ -38,13 +42,8 @@ export type localized_entry = struct {
|
|||||||
locale: locale::locale,
|
locale: locale::locale,
|
||||||
};
|
};
|
||||||
|
|
||||||
// An entry value. Values that reference memory (such as [[str]]) are borrowed
|
// Gets a non-localized [[value]] from a [[file]]. If the group does not exist,
|
||||||
// from the [[file]]. These may be free'd or overwritten when other functions in
|
// or the group exists but the key isn't in it, it returns void.
|
||||||
// this module are called, so [[value_dup]] is required to extend its lifetime.
|
|
||||||
export type value = (str | bool | f32);
|
|
||||||
|
|
||||||
// Gets a non-localized value from a [[file]]. If the group does not exist, or
|
|
||||||
// the group exists but the key isn't in it, it returns void.
|
|
||||||
export fn file_get(fil: *file, group_name: str, key: str) (value | void) = {
|
export fn file_get(fil: *file, group_name: str, key: str) (value | void) = {
|
||||||
let grou = match (file_find_group(fil, group_name)) {
|
let grou = match (file_find_group(fil, group_name)) {
|
||||||
case let index: size => yield &fil.groups[index];
|
case let index: size => yield &fil.groups[index];
|
||||||
@ -91,6 +90,8 @@ export fn file_get_localized(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
//export fn file_add
|
//export fn file_add
|
||||||
|
|
||||||
//export fn file_add_localized
|
//export fn file_add_localized
|
||||||
@ -101,7 +102,17 @@ export fn file_get_localized(
|
|||||||
|
|
||||||
//export fn file_encode
|
//export fn file_encode
|
||||||
|
|
||||||
//export fn file_finish
|
// Frees memory associated with a [[file]].
|
||||||
|
export fn file_finish(fil *file) void = {
|
||||||
|
for (let lin .. file.lines) {
|
||||||
|
line_finish(lin);
|
||||||
|
};
|
||||||
|
free(file.lines);
|
||||||
|
for (let grou .. file.groups) {
|
||||||
|
group_finish(grou);
|
||||||
|
};
|
||||||
|
free(file.groups);
|
||||||
|
};
|
||||||
|
|
||||||
fn file_find_group(fil: *file, group_name: str) (size | void) = {
|
fn file_find_group(fil: *file, group_name: str) (size | void) = {
|
||||||
let index = 0z;
|
let index = 0z;
|
||||||
@ -173,7 +184,7 @@ fn group_find_localized_entry(grou: *group, key: str, local: locale::locale) (si
|
|||||||
return void;
|
return void;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn group_find_localized_entry_exact(grou: *group, key: str, local: locale::locale) (size | void) = {
|
fn group_find_localized_entry_exact(grou: group, key: str, local: locale::locale) (size | void) = {
|
||||||
let index = 0z;
|
let index = 0z;
|
||||||
for (let lin .. grou.lines) {
|
for (let lin .. grou.lines) {
|
||||||
match (lin) {
|
match (lin) {
|
||||||
@ -186,15 +197,3 @@ fn group_find_localized_entry_exact(grou: *group, key: str, local: locale::local
|
|||||||
index += 1;
|
index += 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Duplicates a [[value]]. Use [[value_finish]] to get rid of it.
|
|
||||||
export fn value_dup(valu: value) value = match (valu) {
|
|
||||||
case let valu: str => yield strings::dup(valu);
|
|
||||||
case => yield valu;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Frees an [[entry]] previously duplicated with [[entry_dup]].
|
|
||||||
export fn value_finish(valu: value) void = match (valu) {
|
|
||||||
case let valu: str => free(valu);
|
|
||||||
case => void;
|
|
||||||
};
|
|
||||||
|
28
format/desktop_entry/error.ha
Normal file
28
format/desktop_entry/error.ha
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Any error which may be returned from a function in this module.
|
||||||
|
export type error = !enum {
|
||||||
|
INVALID_GROUP_HEADER,
|
||||||
|
INVALID_ENTRY,
|
||||||
|
// unused for now
|
||||||
|
DUPLICATE_GROUP,
|
||||||
|
DUPLICATE_ENTRY,
|
||||||
|
DUPLICATE_LOCALIZATION,
|
||||||
|
NO_DEFAULT_VALUE,
|
||||||
|
ENTRY_OUTSIDE_GROUP,
|
||||||
|
// --------------
|
||||||
|
UNSUPPORTED_ESCAPE,
|
||||||
|
STRING_NOT_ASCII,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Converts a desktop entry [[error]] into a user-friendly string.
|
||||||
|
export fn strerror(err: error) str = switch {
|
||||||
|
case INVALID_GROUP_HEADER => yield "invalid group header";
|
||||||
|
case INVALID_ENTRY => yield "invalid entry";
|
||||||
|
case DUPLICATE_GROUP => yield "duplicate group";
|
||||||
|
case DUPLICATE_ENTRY => yield "duplicate entry";
|
||||||
|
case DUPLICATE_LOCALIZATION => yield "duplicate localization";
|
||||||
|
case NO_DEFAULT_VALUE => yield "no default value";
|
||||||
|
case ENTRY_OUTSIDE_GROUP => yield "entry outside group";
|
||||||
|
case UNSUPPORTED_ESCAPE => yield "unsupported escape";
|
||||||
|
case STRING_NOT_ASCII => yield "string not ascii";
|
||||||
|
case => yield "unknown";
|
||||||
|
};
|
115
format/desktop_entry/parse.ha
Normal file
115
format/desktop_entry/parse.ha
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
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, '[', ']', '=');
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user