#include #include #include #include #include "format.h" #include "log.h" #include "util.h" #define format_grow(need) \ do { \ if (n + 1 >= length) { \ length += (need); \ parts = realloc(parts, length * sizeof(format_pair_t)); \ assert(parts != NULL); \ } \ } while (0) bool format_init(format_t *format, const char *string, char delim) { format_pair_t *parts = NULL; size_t length = 0, n = 0; size_t start = 0, end = 0; while (string[end] != '\0') { if (string[end] == delim) { // TODO: Escape formatting if (string[end + 1] == '{') { format_grow(2); if (start != end) { parts[n].string = strslice(string, start, end); parts[n].mark = 0; n++; } start = end += 2; while (string[end] != '}') { if (string[end] == '\0' || (string[end] == '$' && string[end + 1] == '{')) { format->parts = parts, format->length = length; format_free(format); return false; } end++; } size_t l = end - start; size_t next = end--; while (start <= end && isspace(string[start])) start++, l--; while (start <= end && isspace(string[end])) end--, l--; parts[n].string = strslice(string, start, start + l); parts[n].mark = 1; n++; start = end = ++next; goto next; } } end++; next: } if (start != end || string[end] != '\0') { while (string[end] != '\0') end++; format_grow(1); parts[n].string = strslice(string, start, end); parts[n].mark = 0; n++; } format->parts = parts; format->length = n; return true; } static size_t format_option_cmp(const char *a, const char *b) { size_t i = 0; while (b[i] != '\0') { if (a[i] != b[i]) return 0; i++; } return i; } static bool format_option_match(format_pair_t *part, const format_option_t *option) { format_mark_t mark = 0; size_t off = 0, tmp = 0; for (const format_pair_t *ps = option->prefixes; ps != NULL && ps->string != NULL; ps++) { tmp = format_option_cmp(part->string, ps->string); if (tmp != 0) { off += tmp; mark |= ps->mark; break; } } tmp = format_option_cmp(part->string + off, option->option.string); if (tmp == 0) return false; off += tmp; mark |= option->option.mark; for (const format_pair_t *ss = option->suffixes; ss != NULL && ss->string != NULL; ss++) { tmp = format_option_cmp(part->string + off, ss->string); if (tmp != 0) { off += tmp; mark |= ss->mark; break; } } if (part->string[off] != '\0') return false; part->mark = mark; return true; } int format_remark(format_t *format, const char *label, const format_option_t *options) { int errors = 0, marked = 0; for (size_t i = 0; i < format->length; i++) { // Skip regular strings if (!format->parts[i].mark) continue; for (size_t j = 0; !iszero(&options[j], sizeof(format_option_t)); j++) { if (format_option_match(&format->parts[i], &options[j])) { marked++; goto next; } } errors++; if (label != NULL) log_error("Unknown format option '%s' for block '%s'", format->parts[i].string, label); next: } return errors > 0 ? -errors : marked; } void format_free(format_t *format) { for (size_t i = 0; i < format->length; i++) free(format->parts[i].string); free(format->parts); }