make leak-safe (hopefully)

This commit is contained in:
2026-03-05 15:54:00 +01:00
parent 03a2d9c70e
commit e4707d5264

109
cfg.h
View File

@@ -89,7 +89,7 @@ void string_append(string* a, string* b) {
string string_slice(string* s, size_t front, size_t back) {
assert(front < back);
assert(front < s->len);
assert(back < s->len);
assert(back <= s->len);
size_t len = back - front;
string res = new_string_sized(len);
res.len = len;
@@ -387,10 +387,15 @@ string config_value_str(config_value* c, size_t indent) {
string_cappend(&res, "\n");
sub = config_str(c->section, indent+2);
string_append(&res, &sub);
free_string(sub);
return res;
case config_value_string:
string_cappend(&res, "\"");
string_append(&res, &c->s);
for (i = 0; i < c->s.len; i++) {
if (c->s.str[i] == '"' || c->s.str[i] == '\\') string_cappend(&res, "\\");
char ch[2] = {c->s.str[i], '\0'};
string_cappend(&res, ch);
}
string_cappend(&res, "\"");
break;
case config_value_number:
@@ -467,6 +472,7 @@ string config_str(config* c, size_t indent) {
string_cappend(&res, "\n");
}
free(keys.data);
return res;
}
@@ -509,9 +515,10 @@ typedef struct parsed_config {
bool config_parse_line(string* s, size_t* line, size_t* col) {
bool seen = false;
while (*s->str == '\n') {
while (s->len > 0 && *s->str == '\n') {
seen = true;
s->str++;
s->len--;
(*line)++;
*col = 1;
}
@@ -520,8 +527,9 @@ bool config_parse_line(string* s, size_t* line, size_t* col) {
ssize_t config_parse_indent(string* s, size_t* col) {
ssize_t count = 0;
while (*s->str == ' ') {
while (s->len > 0 && *s->str == ' ') {
s->str++;
s->len--;
count++;
(*col)++;
}
@@ -542,43 +550,51 @@ parsed_config config_parse_internal(string*, size_t*, size_t*, size_t);
parsed_value config_parse_list(string* s, size_t* line, size_t* col, size_t indent, size_t consumed) {
parsed_value elem;
parsed_value err;
size_t ind;
list_config_value* l = malloc(sizeof(list_config_value));
*l =new_list_config_value();
while(true) {
if (s->str[0] != ' ' && s->str[0] != '\n') return config_value_parse_error("expected space to start list element", *line, *col);
if (s->str[0] == ' ') s->str++;
if (s->len == 0) { err = config_value_parse_error("unexpected EOF in list", *line, *col); goto fail; }
if (s->str[0] != ' ' && s->str[0] != '\n') { err = config_value_parse_error("expected space to start list element", *line, *col); goto fail; }
if (s->str[0] == ' ') { s->str++; s->len--; }
consumed += 1;
elem = config_parse_value(s, line, col, indent);
if (!elem.ok) return elem;
if (!elem.ok) { err = elem; goto fail; }
list_push_config_value(l, elem.v);
consumed += elem.consumed;
s->str += elem.consumed;
if (s->str[0] == '\0') break;
if (!config_parse_line(s, line, col)) return config_value_parse_error("expected newline", *line, *col);
if (s->len == 0 || s->str[0] == '\0') break;
if (!config_parse_line(s, line, col)) { err = config_value_parse_error("expected newline", *line, *col); goto fail; }
ind = config_parse_indent(s, col);
if(ind < indent) { s->str -= ind; col -= ind; break; }
if (ind > indent) return config_value_parse_error("unexpected indent", *line, *col);
if(ind < indent) { s->str -= ind + 1; s->len += ind + 1; *col -= ind; (*line)--; break; }
if (ind > indent) { err = config_value_parse_error("unexpected indent", *line, *col); goto fail; }
consumed += ind;
if (s->str[0] != '-') return config_value_parse_error("expected hyphen to start list element", *line, *col);
if (s->len == 0 || s->str[0] != '-') { err = config_value_parse_error("expected hyphen to start list element", *line, *col); goto fail; }
s->str++;
s->len--;
consumed += 1;
}
return (parsed_value){.ok=true, .consumed=consumed, .v=config_list(l)};
fail:
free_list_config_value(*l);
free(l);
return err;
}
parsed_value config_parse_section(string* s, size_t* line, size_t* col, size_t indent, size_t consumed) {
config* res;
*col -= consumed;
s->str -= consumed;
s->len += consumed;
parsed_config c = config_parse_internal(s, line, col, indent);
if (!c.ok) return (parsed_value){.ok=false, .consumed=c.consumed, .err=c.err};
@@ -597,26 +613,38 @@ parsed_value config_parse_list_or_section(string* s, size_t* line, size_t* col,
if (ind < indent) return config_value_parse_error("expected greater indent", *line, *col);
if (ind > indent) return config_value_parse_error("unexpected indent", *line, *col);
if (s->str[0] == '-') { s->str++; return config_parse_list(s, line, col, indent, ind+1); }
if (s->len == 0) return config_value_parse_error("unexpected EOF", *line, *col);
if (s->str[0] == '-') { s->str++; s->len--; return config_parse_list(s, line, col, indent, ind+1); }
return config_parse_section(s, line, col, indent, ind);
}
parsed_value config_parse_string(string* s, size_t* line, size_t* col) {
size_t i;
string val = new_string();
for (i = 1; i < s->len; i++) {
(*col)++;
if (s->str[i] == '\\') {
if (i +1 == s->len) return config_value_parse_error("Unexpected EOF, expected end of string", *line, *col);
if (i +1 == s->len) { free_string(val); return config_value_parse_error("Unexpected EOF, expected end of string", *line, *col); }
i++;
char ch[2] = {s->str[i], '\0'};
string_cappend(&val, ch);
} else if (s->str[i] == '"') {
return (parsed_value){.ok=true, .consumed=i+1, .v=config_string(string_slice(s, 1, i))};
size_t consumed = i+1;
s->str += consumed;
s->len -= consumed;
return (parsed_value){.ok=true, .consumed=consumed, .v=config_string(val)};
} else if (s->str[i] == '\0') {
break;
} else if (s->str[i] == '\n') {
(*line)++;
*col = 0;
string_cappend(&val, "\n");
} else {
char ch[2] = {s->str[i], '\0'};
string_cappend(&val, ch);
}
}
free_string(val);
return config_value_parse_error("Unterminated string", *line, *col);
}
@@ -624,7 +652,7 @@ parsed_value config_parse_number(string* s, size_t* line, size_t* col) {
int i;
ssize_t end = string_find_whitespace(s);
bool dot = false;
string to_parse = string_slice(s, 0, end == -1 ? s->len-1 : end);
string to_parse = string_slice(s, 0, end == -1 ? s->len : end);
for (i = 0; i < to_parse.len; i++) {
(*col)++;
@@ -635,18 +663,29 @@ parsed_value config_parse_number(string* s, size_t* line, size_t* col) {
} else if (to_parse.str[i] == '\0') {
break;
} else {
free_string(to_parse);
return config_value_parse_error("Expected number, got unparseable", *line, *col);
}
}
char* to_parse_cstr = string_cstr(&to_parse);
double result = strtod(to_parse_cstr, NULL);
free(to_parse_cstr);
size_t to_parse_len = to_parse.len;
free_string(to_parse);
s->str += to_parse_len;
s->len -= to_parse_len;
return (parsed_value){
.ok=true,
.consumed=to_parse.len,
.v=config_number(strtod(string_cstr(&to_parse), NULL))
.consumed=to_parse_len,
.v=config_number(result)
};
}
parsed_value config_parse_value(string* s, size_t* line, size_t* col, size_t indent) {
if (s->len == 0) return config_value_parse_error("unexpected EOF, expected value", *line, *col);
if (s->str[0] == '"') return config_parse_string(s, line, col);
if (s->str[0] == '\n') return config_parse_list_or_section(s, line, col, indent+2);
return config_parse_number(s, line, col);
@@ -662,9 +701,10 @@ parsed_config config_parse_error(char* msg, size_t line, size_t col) {
}
void config_parse_trim(string* s, size_t* line, size_t* col, size_t* consumed) {
while (*s->str == ' ' || *s->str == '\t') {
while (s->len > 0 && (*s->str == ' ' || *s->str == '\t')) {
(*col)++;
s->str++;
s->len--;
(*consumed)++;
}
}
@@ -678,34 +718,43 @@ parsed_config config_parse_internal(string* s, size_t* line, size_t* col, size_t
size_t consumed = 0;
map_string_config_value values = new_map_string_config_value(1024);
while(s->str[0] != '\0') {
while(s->len > 0 && s->str[0] != '\0') {
if (s->str[0] == '\n') {
config_parse_line(s, line, col);
continue;
}
ind = config_parse_indent(s, col);
if (ind > indent) return config_parse_error("unexpected indent", *line, *col);
if (ind < indent) break;
if (ind > indent) { res = config_parse_error("unexpected indent", *line, *col); goto fail; }
if (ind < indent) { s->str -= ind + 1; s->len += ind + 1; *col -= ind; (*line)--; break; }
consumed += ind;
whitespace = string_find_whitespace(s);
if (whitespace == -1) return config_parse_error("expected whitespace, got EOF", *line, (*col)+s->len);
if (whitespace == -1) { res = config_parse_error("expected whitespace, got EOF", *line, (*col)+s->len); goto fail; }
if (whitespace == 0) {
if (s->len == 0 || s->str[0] != '\n') { res = config_parse_error("unexpected whitespace", *line, *col); goto fail; }
config_parse_line(s, line, col); continue;
}
label = string_slice(s, 0, whitespace);
s->str += whitespace;
s->len -= whitespace;
*col += whitespace;
consumed += consumed;
consumed += whitespace;
config_parse_trim(s, line, col, &consumed);
val = config_parse_value(s, line, col, indent);
if (!val.ok) return (parsed_config){.ok=false, .err=val.err};
if (!val.ok) { free_string(label); res = (parsed_config){.ok=false, .err=val.err}; goto fail; }
s->str += val.consumed;
consumed += val.consumed;
map_put_string_config_value(&values, label, val.v);
if (!config_parse_line(s, line, col) && s->str[0] != '\0') return config_parse_error("expected newline", *line, *col);
if (!config_parse_line(s, line, col) && s->len > 0 && s->str[0] != '\0') { res = config_parse_error("expected newline", *line, *col); goto fail; }
consumed++;
}
@@ -713,6 +762,10 @@ parsed_config config_parse_internal(string* s, size_t* line, size_t* col, size_t
res.ok = true;
res.consumed = consumed;
return res;
fail:
free_map_string_config_value(values);
return res;
}
parsed_config config_parse(string* s) {