diff --git a/cfg.h b/cfg.h index d30be09..79045a1 100644 --- a/cfg.h +++ b/cfg.h @@ -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) {