URI: 
       configparser.c - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ledit.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       configparser.c (63996B)
       ---
            1 #ifdef LEDIT_DEBUG
            2 #include <time.h>
            3 #include "macros.h"
            4 #endif
            5 #include <stdio.h>
            6 #include <ctype.h>
            7 #include <errno.h>
            8 #include <string.h>
            9 #include <stdint.h>
           10 #include <stdlib.h>
           11 #include <limits.h>
           12 
           13 #include "util.h"
           14 #include "memory.h"
           15 #include "assert.h"
           16 #include "configparser.h"
           17 #include "theme_config.h"
           18 #include "keys_config.h"
           19 
           20 /* FIXME: Replace this entire parser with something sensible.
           21    The current handwritten parser is mainly for the lulz. */
           22 
           23 /* FIXME: standardize error messages */
           24 /* FIXME: it isn't entirely correct to give size_t as length for
           25    string in print_fmt (supposed to be int) */
           26 
           27 struct config {
           28         ledit_theme *theme;
           29         basic_key_array *basic_keys;
           30         command_key_array *command_keys;
           31         command_array *cmds;
           32         char **langs;
           33         size_t num_langs;
           34         size_t alloc_langs;
           35 } config = {NULL, NULL, NULL, NULL, NULL, 0, 0};
           36 
           37 enum toktype {
           38         STRING,
           39         LBRACE,
           40         RBRACE,
           41         EQUALS,
           42         NEWLINE,
           43         ERROR,
           44         END
           45 };
           46 
           47 static const char *
           48 toktype_str(enum toktype type) {
           49         switch (type) {
           50         case STRING:
           51                 return "string";
           52                 break;
           53         case LBRACE:
           54                 return "left brace";
           55                 break;
           56         case RBRACE:
           57                 return "right brace";
           58                 break;
           59         case EQUALS:
           60                 return "equals";
           61                 break;
           62         case NEWLINE:
           63                 return "newline";
           64                 break;
           65         case ERROR:
           66                 return "error";
           67                 break;
           68         case END:
           69                 return "end of file";
           70                 break;
           71         default:
           72                 return "unknown";
           73         }
           74 }
           75 
           76 struct token {
           77         char *text;
           78         size_t len;
           79         enum toktype type;
           80         size_t line;        /* line in original input */
           81         size_t line_offset; /* offset from start of line */
           82 };
           83 
           84 struct lexstate {
           85         char *text;
           86         size_t len;        /* length of text */
           87         size_t cur;        /* current byte position */
           88         size_t cur_line;   /* current line */
           89         size_t line_start; /* byte offset of start of current line */
           90 };
           91 
           92 static struct token
           93 next_token(struct lexstate *s) {
           94         char c;
           95         struct token tok;
           96         while (1) {
           97                 if (s->cur >= s->len)
           98                         return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1};
           99                 while (isspace(c = s->text[s->cur])) {
          100                         s->cur++;
          101                         if (c == '\n') {
          102                                 struct token tok = (struct token){s->text + s->cur - 1, 1, NEWLINE, s->cur_line, s->cur - s->line_start};
          103                                 s->cur_line++;
          104                                 s->line_start = s->cur;
          105                                 return tok;
          106                         }
          107                         if (s->cur >= s->len)
          108                                 return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1};
          109                 }
          110 
          111                 switch (s->text[s->cur]) {
          112                 case '#':
          113                         s->cur++;
          114                         while (s->cur < s->len && s->text[s->cur] != '\n')
          115                                 s->cur++;
          116                         continue;
          117                 case '{':
          118                         tok = (struct token){s->text + s->cur, 1, LBRACE, s->cur_line, s->cur - s->line_start + 1};
          119                         s->cur++;
          120                         break;
          121                 case '}':
          122                         tok = (struct token){s->text + s->cur, 1, RBRACE, s->cur_line, s->cur - s->line_start + 1};
          123                         s->cur++;
          124                         break;
          125                 case '=':
          126                         tok = (struct token){s->text + s->cur, 1, EQUALS, s->cur_line, s->cur - s->line_start + 1};
          127                         s->cur++;
          128                         break;
          129                 case '"':
          130                         /* FIXME: error if next char is not whitespace or end */
          131                         s->cur++;
          132                         tok = (struct token){s->text + s->cur, 0, STRING, s->cur_line, s->cur - s->line_start + 1};
          133                         size_t shift = 0, bs = 0;
          134                         int finished = 0;
          135                         while (s->cur < s->len) {
          136                                 char c = s->text[s->cur];
          137                                 if (c == '\n') {
          138                                         break;
          139                                 } else if (c == '\\') {
          140                                         shift += bs;
          141                                         tok.len += bs;
          142                                         bs = (bs + 1) % 2;
          143                                 } else if (c == '"') {
          144                                         if (bs) {
          145                                                 shift++;
          146                                                 tok.len++;
          147                                                 bs = 0;
          148                                         } else {
          149                                                 s->cur++;
          150                                                 finished = 1;
          151                                                 break;
          152                                         }
          153                                 } else {
          154                                         tok.len++;
          155                                 }
          156                                 s->text[s->cur - shift] = s->text[s->cur];
          157                                 s->cur++;
          158                         }
          159                         if (!finished) {
          160                                 tok.text = "Unfinished string";
          161                                 tok.len = strlen("Unfinished string");
          162                                 tok.type = ERROR;
          163                         }
          164                         break;
          165                 default:
          166                         tok = (struct token){s->text + s->cur, 1, STRING, s->cur_line, s->cur - s->line_start + 1};
          167                         s->cur++;
          168                         while (s->cur < s->len) {
          169                                 char c = s->text[s->cur];
          170                                 if (isspace(c) || c == '{' || c == '}' || c == '=') {
          171                                         break;
          172                                 } else if (c == '"') {
          173                                         tok.text = "Unexpected start of string";
          174                                         tok.len = strlen("Unexpected start of string");
          175                                         tok.type = ERROR;
          176                                         tok.line_offset = s->cur - s->line_start + 1;
          177                                 }
          178                                 tok.len++;
          179                                 s->cur++;
          180                         }
          181                 }
          182                 return tok;
          183         }
          184 }
          185 
          186 typedef struct ast_obj ast_obj;
          187 
          188 typedef struct {
          189         ast_obj *objs;
          190         size_t len, cap;
          191 } ast_list;
          192 
          193 typedef struct {
          194         struct token tok;
          195 } ast_string;
          196 
          197 typedef struct {
          198         struct token tok;
          199         ast_obj *value;
          200 } ast_assignment;
          201 
          202 typedef struct {
          203         struct token func_tok;
          204         struct token *args;
          205         size_t len, cap;
          206 } ast_statement;
          207 
          208 enum objtype {
          209         OBJ_LIST,
          210         OBJ_STRING,
          211         OBJ_ASSIGNMENT,
          212         OBJ_STATEMENT
          213 };
          214 
          215 struct ast_obj {
          216         struct token tok;
          217         union {
          218                 ast_list list;
          219                 ast_string str;
          220                 ast_assignment assignment;
          221                 ast_statement statement;
          222         } obj;
          223         enum objtype type;
          224 };
          225 
          226 /* Note: These functions only free everything inside the object
          227    so they can be used with stack variables (or array elements)! */
          228 
          229 static void destroy_obj(ast_obj *obj);
          230 
          231 static void
          232 destroy_list(ast_list *list) {
          233         if (!list)
          234                 return;
          235         for (size_t i = 0; i < list->len; i++) {
          236                 destroy_obj(&list->objs[i]);
          237         }
          238         free(list->objs);
          239         list->objs = NULL;
          240         list->len = list->cap = 0;
          241 }
          242 
          243 static void
          244 destroy_obj(ast_obj *obj) {
          245         if (!obj)
          246                 return;
          247         switch (obj->type) {
          248                 case OBJ_LIST:
          249                         destroy_list(&obj->obj.list);
          250                         break;
          251                 case OBJ_ASSIGNMENT:
          252                         destroy_obj(obj->obj.assignment.value);
          253                         free(obj->obj.assignment.value);
          254                         obj->obj.assignment.value = NULL;
          255                         break;
          256                 case OBJ_STATEMENT:
          257                         free(obj->obj.statement.args);
          258                         obj->obj.statement.args = NULL;
          259                         obj->obj.statement.len = obj->obj.statement.cap = 0;
          260                         break;
          261                 default:
          262                         break;
          263         }
          264 }
          265 
          266 /* FIXME: overflow */
          267 static void
          268 list_append(ast_list *list, ast_obj o) {
          269         list->cap = ideal_array_size(list->cap, add_sz(list->len, 1));
          270         list->objs = ledit_reallocarray(list->objs, list->cap, sizeof(ast_obj));
          271         list->objs[list->len++] = o;
          272 }
          273 
          274 static void
          275 statement_append(ast_statement *statement, struct token tok) {
          276         statement->cap = ideal_array_size(statement->cap, add_sz(statement->len, 1));
          277         statement->args = ledit_reallocarray(statement->args, statement->cap, sizeof(struct token));
          278         statement->args[statement->len++] = tok;
          279 }
          280 
          281 /* FIXME: make this a bit nicer */
          282 /* Note: A lot of the ugliness is because of the
          283    (failed) attempt to somewhat optimize everything */
          284 
          285 static int
          286 parse_list(struct lexstate *s, ast_list *ret, int implicit_end, char *filename, char **errstr) {
          287         *ret = (ast_list){NULL, 0, 0};
          288         struct token tok = next_token(s);
          289         struct token tok2;
          290         while (1) {
          291                 switch (tok.type) {
          292                 case STRING:
          293                         tok2 = next_token(s);
          294                         if (tok2.type == STRING) {
          295                                 ast_statement statement = {tok, NULL, 0, 0};
          296                                 /* FIXME: maybe allow lists in statements? */
          297                                 while (tok2.type == STRING) {
          298                                         statement_append(&statement, tok2);
          299                                         tok2 = next_token(s);
          300                                 }
          301                                 list_append(ret, (ast_obj){.tok = tok, .obj = {.statement = statement}, .type = OBJ_STATEMENT});
          302                                 tok = tok2;
          303                         } else if (tok2.type == EQUALS) {
          304                                 ast_assignment assignment = {tok, NULL};
          305                                 assignment.value = ledit_malloc(sizeof(ast_obj));
          306                                 tok2 = next_token(s);
          307                                 assignment.value->tok = tok2;
          308                                 struct token orig_tok = tok;
          309                                 if (tok2.type == STRING) {
          310                                         assignment.value->obj.str = (ast_string){tok2};
          311                                         assignment.value->type = OBJ_STRING;
          312                                         tok = next_token(s);
          313                                         if (tok.type == STRING) {
          314                                                 *errstr = print_fmt(
          315                                                     "%s: Invalid assignment at line %zu, offset %zu",
          316                                                     filename, tok.line, tok.line_offset
          317                                                 );
          318                                                 free(assignment.value);
          319                                                 goto error;
          320                                         }
          321                                 } else if (tok2.type == LBRACE) {
          322                                         assignment.value->type = OBJ_LIST;
          323                                         /* just in case */
          324                                         assignment.value->obj.list = (ast_list){NULL, 0, 0};
          325                                         if (parse_list(s, &assignment.value->obj.list, 0, filename, errstr)) {
          326                                                 free(assignment.value);
          327                                                 goto error;
          328                                         }
          329                                         tok = next_token(s);
          330                                         if (tok.type == STRING) {
          331                                                 *errstr = print_fmt(
          332                                                     "%s: Invalid assignment at line %zu, offset %zu",
          333                                                     filename, tok.line, tok.line_offset
          334                                                 );
          335                                                 destroy_list(&assignment.value->obj.list);
          336                                                 free(assignment.value);
          337                                                 goto error;
          338                                         }
          339                                 } else {
          340                                         *errstr = print_fmt(
          341                                             "%s: Invalid assignment at line %zu, offset %zu",
          342                                             filename, tok2.line, tok2.line_offset
          343                                         );
          344                                         free(assignment.value);
          345                                         goto error;
          346                                 }
          347                                 list_append(ret, (ast_obj){.tok = orig_tok, .obj = {.assignment = assignment}, .type = OBJ_ASSIGNMENT});
          348                         } else {
          349                                 *errstr = print_fmt(
          350                                     "%s: Invalid token '%s' at line %zu, offset %zu",
          351                                     filename, toktype_str(tok2.type), tok2.line, tok2.line_offset
          352                                 );
          353                                 goto error;
          354                         }
          355                         break;
          356                 case NEWLINE:
          357                         tok = next_token(s);
          358                         break;
          359                 case RBRACE:
          360                         if (implicit_end) {
          361                                 *errstr = print_fmt(
          362                                     "%s: Unexpected right brace at line %zu, offset %zu",
          363                                     filename, tok.line, tok.line_offset
          364                                 );
          365                                 goto error;
          366                         } else {
          367                                 return 0;
          368                         }
          369                 case END:
          370                         if (!implicit_end) {
          371                                 *errstr = print_fmt(
          372                                     "%s: Unexpected end of file at line %zu, offset %zu",
          373                                     filename, tok.line, tok.line_offset
          374                                 );
          375                                 goto error;
          376                         } else {
          377                                 return 0;
          378                         }
          379                 case LBRACE:
          380                 case EQUALS:
          381                 case ERROR:
          382                 default:
          383                         *errstr = print_fmt(
          384                             "%s: Unexpected token '%s' at line %zu, offset %zu",
          385                             filename, toktype_str(tok.type), tok.line, tok.line_offset
          386                         );
          387                         goto error;
          388                 }
          389         }
          390         return 0;
          391 error:
          392         destroy_list(ret);
          393         return 1;
          394 }
          395 
          396 static char *
          397 load_file(char *filename, size_t *len_ret, char **errstr) {
          398         long len;
          399         char *file_contents;
          400         FILE *file;
          401 
          402         /* FIXME: https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file */
          403         file = fopen(filename, "r");
          404         if (!file) goto error;
          405         if (fseek(file, 0, SEEK_END)) goto errorclose;
          406         len = ftell(file);
          407         if (len < 0) goto errorclose;
          408         if (fseek(file, 0, SEEK_SET)) goto errorclose;
          409         file_contents = ledit_malloc(add_sz((size_t)len, 1));
          410         clearerr(file);
          411         fread(file_contents, 1, (size_t)len, file);
          412         if (ferror(file)) goto errorclose;
          413         file_contents[len] = '\0';
          414         if (fclose(file)) goto error;
          415         *len_ret = (size_t)len;
          416         return file_contents;
          417 error:
          418         if (errstr)
          419                 *errstr = strerror(errno);
          420         return NULL;
          421 errorclose:
          422         if (errstr)
          423                 *errstr = strerror(errno);
          424         fclose(file);
          425         return NULL;
          426 }
          427 
          428 /* FIXME: max recursion depth in parser */
          429 
          430 static int
          431 parse_theme_color(
          432     ledit_common *common,
          433     void *obj, const char *val, size_t val_len, char *key,
          434     char *filename, size_t line, size_t line_offset, char **errstr) {
          435         XftColor *dst = (XftColor *)obj;
          436         char col[8]; /* 7 for '#' and 6 hex values + 1 for '\0' */
          437         if (val_len == 7 && val[0] == '#') {
          438                 strncpy(col, val, val_len);
          439                 col[val_len] = '\0';
          440         } else if (val_len == 6) {
          441                 col[0] = '#';
          442                 strncpy(col + 1, val, val_len);
          443                 col[val_len + 1] = '\0';
          444         } else {
          445                 goto error;
          446         }
          447         /* FIXME: XftColorAllocValue */
          448         if (!XftColorAllocName(common->dpy, common->vis, common->cm, col, dst))
          449                 goto error;
          450         return 0;
          451 error:
          452         *errstr = print_fmt(
          453             "%s: Unable to parse color specification "
          454             "'%.*s' for '%s' at line %zu, position %zu",
          455             filename, val_len, val, key, line, line_offset
          456         );
          457         return 1;
          458 }
          459 
          460 static void
          461 destroy_theme_color(ledit_common *common, void *obj) {
          462         XftColor *color = (XftColor *)obj;
          463         XftColorFree(common->dpy, common->vis, common->cm, color);
          464 }
          465 
          466 /* based partially on OpenBSD's strtonum */
          467 static int
          468 parse_theme_number(
          469     ledit_common *common,
          470     void *obj, const char *val, size_t val_len, char *key,
          471     char *filename, size_t line, size_t line_offset, char **errstr) {
          472         (void)common;
          473         int *num = (int *)obj;
          474         /* the string needs to be nul-terminated
          475            if it contains more than 9 digits, it's illegal anyways */
          476         if (val_len > 9)
          477                 goto error;
          478         char str[10];
          479         strncpy(str, val, val_len);
          480         str[val_len] = '\0';
          481         char *end;
          482         long l = strtol(str, &end, 10);
          483         if (str == end || *end != '\0' ||
          484             l < 0 || l > INT_MAX || ((l == LONG_MIN ||
          485             l == LONG_MAX) && errno == ERANGE)) {
          486                 goto error;
          487         }
          488         *num = (int)l;
          489         return 0;
          490 error:
          491         *errstr = print_fmt(
          492             "%s: Invalid number '%.*s' "
          493             "for '%s' at line %zu, position %zu",
          494             filename, val_len, val, key, line, line_offset
          495         );
          496         return 1;
          497 }
          498 
          499 static void
          500 destroy_theme_number(ledit_common *common, void *obj) {
          501         (void)common;
          502         (void)obj;
          503 }
          504 
          505 static int
          506 parse_theme_string(
          507     ledit_common *common,
          508     void *obj, const char *val, size_t val_len, char *key,
          509     char *filename, size_t line, size_t line_offset, char **errstr) {
          510         (void)common; (void)key;
          511         (void)filename; (void)line; (void)line_offset; (void)errstr;
          512 
          513         char **obj_str = (char **)obj;
          514         *obj_str = ledit_strndup(val, val_len);
          515         return 0;
          516 }
          517 
          518 static void
          519 destroy_theme_string(ledit_common *common, void *obj) {
          520         (void)common;
          521         char **obj_str = (char **)obj;
          522         free(*obj_str);
          523 }
          524 
          525 static int
          526 parse_theme_bool(
          527     ledit_common *common,
          528     void *obj, const char *val, size_t val_len, char *key,
          529     char *filename, size_t line, size_t line_offset, char **errstr) {
          530         (void)common;
          531         int *num = (int *)obj;
          532         if (str_array_equal("true", val, val_len)) {
          533                 *num = 1;
          534                 return 0;
          535         } else if (str_array_equal("false", val, val_len)) {
          536                 *num = 0;
          537                 return 0;
          538         }
          539         *errstr = print_fmt(
          540             "%s: Invalid boolean '%.*s' "
          541             "for '%s' at line %zu, position %zu",
          542             filename, val_len, val, key, line, line_offset
          543         );
          544         return 1;
          545 }
          546 
          547 static void
          548 destroy_theme_bool(ledit_common *common, void *obj) {
          549         (void)common;
          550         (void)obj;
          551 }
          552 
          553 /* FIXME: This interface is absolutely horrible - it's mainly this way to reuse the
          554    theme array for the destroy function */
          555 /* If theme is NULL, a new theme is loaded, else it is destroyed */
          556 static ledit_theme *
          557 load_destroy_theme(ledit_common *common, ast_list *theme_list, ledit_theme *theme, char *filename, char **errstr) {
          558         *errstr = NULL;
          559         int default_init = theme ? 1 : 0;
          560         if (!theme)
          561                 theme = ledit_malloc(sizeof(ledit_theme));
          562 
          563         struct {
          564                 char *key;
          565                 void *obj;
          566                 int (*parse_func)(
          567                     ledit_common *common,
          568                     void *obj, const char *val, size_t val_len, char *key,
          569                     char *filename, size_t line, size_t line_offset, char **errstr
          570                 );
          571                 void (*destroy_func)(ledit_common *common, void *obj);
          572                 const char *default_value;
          573                 int initialized;
          574         } settings[] = {
          575                 {"text-font", &theme->text_font, &parse_theme_string, &destroy_theme_string, TEXT_FONT, default_init},
          576                 {"text-size", &theme->text_size, &parse_theme_number, &destroy_theme_number, TEXT_SIZE, default_init},
          577                 {"scrollbar-width", &theme->scrollbar_width, &parse_theme_number, &destroy_theme_number, SCROLLBAR_WIDTH, default_init},
          578                 {"scrollbar-step", &theme->scrollbar_step, &parse_theme_number, &destroy_theme_number, SCROLLBAR_STEP, default_init},
          579                 {"extra-line-spacing", &theme->extra_line_spacing, &parse_theme_number, &destroy_theme_number, EXTRA_LINE_SPACING, default_init},
          580                 {"text-fg", &theme->text_fg, &parse_theme_color, &destroy_theme_color, TEXT_FG, default_init},
          581                 {"text-bg", &theme->text_bg, &parse_theme_color, &destroy_theme_color, TEXT_BG, default_init},
          582                 {"cursor-fg", &theme->cursor_fg, &parse_theme_color, &destroy_theme_color, CURSOR_FG, default_init},
          583                 {"cursor-bg", &theme->cursor_bg, &parse_theme_color, &destroy_theme_color, CURSOR_BG, default_init},
          584                 {"selection-fg", &theme->selection_fg, &parse_theme_color, &destroy_theme_color, SELECTION_FG, default_init},
          585                 {"selection-bg", &theme->selection_bg, &parse_theme_color, &destroy_theme_color, SELECTION_BG, default_init},
          586                 {"bar-fg", &theme->bar_fg, &parse_theme_color, &destroy_theme_color, BAR_FG, default_init},
          587                 {"bar-bg", &theme->bar_bg, &parse_theme_color, &destroy_theme_color, BAR_BG, default_init},
          588                 {"bar-cursor", &theme->bar_cursor, &parse_theme_color, &destroy_theme_color, BAR_CURSOR, default_init},
          589                 {"bar-fmt", &theme->bar_fmt, &parse_theme_string, &destroy_theme_string, BAR_FMT, default_init},
          590                 {"scrollbar-fg", &theme->scrollbar_fg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_FG, default_init},
          591                 {"scrollbar-bg", &theme->scrollbar_bg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_BG, default_init},
          592                 {"highlight-search", &theme->highlight_search, &parse_theme_bool, &destroy_theme_bool, HIGHLIGHT_SEARCH, default_init},
          593         };
          594 
          595         if (default_init)
          596                 goto cleanup;
          597 
          598         if (theme_list) {
          599                 for (size_t i = 0; i < theme_list->len; i++) {
          600                         size_t line = theme_list->objs[i].tok.line;
          601                         size_t line_offset = theme_list->objs[i].tok.line_offset;
          602                         if (theme_list->objs[i].type != OBJ_ASSIGNMENT) {
          603                                 *errstr = print_fmt(
          604                                     "%s: Invalid statement in theme configuration "
          605                                     "at line %zu, offset %zu", filename, line, line_offset
          606                                 );
          607                                 goto cleanup;
          608                         } else if (theme_list->objs[i].obj.assignment.value->type != OBJ_STRING) {
          609                                 *errstr = print_fmt(
          610                                     "%s: Invalid assignment in theme configuration "
          611                                     "at line %zu, offset %zu", filename, line, line_offset
          612                                 );
          613                                 goto cleanup;
          614                         }
          615 
          616                         char *key = theme_list->objs[i].obj.assignment.tok.text;
          617                         size_t key_len = theme_list->objs[i].obj.assignment.tok.len;
          618                         char *val = theme_list->objs[i].obj.assignment.value->obj.str.tok.text;
          619                         size_t val_len = theme_list->objs[i].obj.assignment.value->obj.str.tok.len;
          620 
          621                         int found = 0;
          622                         /* FIXME: use binary search maybe */
          623                         for (size_t j = 0; j < LENGTH(settings); j++) {
          624                                 if (str_array_equal(settings[j].key, key, key_len)) {
          625                                         /* FIXME: maybe just make this a warning? */
          626                                         if (settings[j].initialized) {
          627                                                 *errstr = print_fmt(
          628                                                     "%s: Duplicate definition of "
          629                                                     "'%.*s' at line %zu, position %zu",
          630                                                     filename, key_len, key, line, line_offset
          631                                                 );
          632                                                 goto cleanup;
          633                                         }
          634                                         if (settings[j].parse_func(
          635                                             common, settings[j].obj, val, val_len,
          636                                             settings[j].key, filename, line, line_offset, errstr)) {
          637                                                 goto cleanup;
          638                                         }
          639                                         settings[j].initialized = 1;
          640                                         found = 1;
          641                                         break;
          642                                 }
          643                         }
          644                         if (!found) {
          645                                 *errstr = print_fmt(
          646                                     "%s: Invalid theme setting "
          647                                     "'%.*s' at line %zu, position %zu",
          648                                     filename, key_len, key, line, line_offset
          649                                 );
          650                                 goto cleanup;
          651                         }
          652                 }
          653         }
          654 
          655         for (size_t i = 0; i < LENGTH(settings); i++) {
          656                 if (!settings[i].initialized) {
          657                         /* FIXME: kind of inefficient to calculate strlen at runtime */
          658                         /* FIXME: line number doesn't make sense */
          659                         if (settings[i].parse_func(
          660                             common, settings[i].obj, settings[i].default_value,
          661                             strlen(settings[i].default_value), settings[i].key,
          662                             "default config", 0, 0, errstr)) {
          663                                 goto cleanup;
          664                         }
          665                 }
          666         }
          667 
          668         /* FIXME: make this check part of the generic handling above (also, < 0 is already checked anyways) */
          669         /* FIXME: 100 is completely arbitrary */
          670         if (theme->extra_line_spacing < 0 || theme->extra_line_spacing > 100) {
          671                 *errstr = print_fmt(
          672                     "%s: Invalid value '%d' for theme setting 'extra-line-spacing' "
          673                     "(allowed values are 0-100)",
          674                     filename, theme->extra_line_spacing
          675                 );
          676                 goto cleanup;
          677         }
          678 
          679         return theme;
          680 cleanup:
          681         for (size_t i = 0; i < LENGTH(settings); i++) {
          682                 if (settings[i].initialized) {
          683                         settings[i].destroy_func(common, settings[i].obj);
          684                 }
          685         }
          686         free(theme);
          687         return NULL;
          688 }
          689 
          690 static ledit_theme *
          691 load_theme(ledit_common *common, ast_list *theme_list, char *filename, char **errstr) {
          692         return load_destroy_theme(common, theme_list, NULL, filename, errstr);
          693 }
          694 
          695 static void
          696 destroy_theme(ledit_common *common, ledit_theme *theme) {
          697         char *errstr = NULL;
          698         if (!theme)
          699                 return;
          700         (void)load_destroy_theme(common, NULL, theme, NULL, &errstr);
          701         /* shouldn't happen... */
          702         if (errstr)
          703                 free(errstr);
          704 }
          705 
          706 /* This only destroys the members inside 'cfg' since the config
          707  * struct itself is usually not on the heap. */
          708 static void
          709 config_destroy(ledit_common *common, struct config *cfg) {
          710         if (cfg->theme)
          711                 destroy_theme(common, cfg->theme);
          712         cfg->theme = NULL;
          713         for (size_t i = 0; i < cfg->num_langs; i++) {
          714                 for (size_t j = 0; j < cfg->basic_keys[i].num_keys; j++) {
          715                         free(cfg->basic_keys[i].keys[j].text);
          716                 }
          717                 free(cfg->basic_keys[i].keys);
          718                 for (size_t j = 0; j < cfg->command_keys[i].num_keys; j++) {
          719                         free(cfg->command_keys[i].keys[j].text);
          720                 }
          721                 free(cfg->command_keys[i].keys);
          722                 for (size_t j = 0; j < cfg->cmds[i].num_cmds; j++) {
          723                         free(cfg->cmds[i].cmds[j].text);
          724                 }
          725                 free(cfg->cmds[i].cmds);
          726                 free(cfg->langs[i]);
          727         }
          728         free(cfg->basic_keys);
          729         free(cfg->command_keys);
          730         free(cfg->cmds);
          731         free(cfg->langs);
          732         cfg->basic_keys = NULL;
          733         cfg->command_keys = NULL;
          734         cfg->cmds = NULL;
          735         cfg->langs = NULL;
          736         cfg->num_langs = cfg->alloc_langs = 0;
          737 }
          738 
          739 void
          740 config_cleanup(ledit_common *common) {
          741         config_destroy(common, &config);
          742 }
          743 
          744 /* FIXME: which additional ones are needed here? */
          745 static struct keysym_mapping {
          746         char *name;
          747         KeySym keysym;
          748 } keysym_map[] = {
          749         {"backspace", XK_BackSpace},
          750         {"begin", XK_Begin},
          751         {"break", XK_Break},
          752         {"cancel", XK_Cancel},
          753         {"clear", XK_Clear},
          754         {"delete", XK_Delete},
          755         {"down", XK_Down},
          756         {"end", XK_End},
          757         {"escape", XK_Escape},
          758         {"execute", XK_Execute},
          759 
          760         {"f1", XK_F1},
          761         {"f10", XK_F10},
          762         {"f11", XK_F11},
          763         {"f12", XK_F12},
          764         {"f13", XK_F13},
          765         {"f14", XK_F14},
          766         {"f15", XK_F15},
          767         {"f16", XK_F16},
          768         {"f17", XK_F17},
          769         {"f18", XK_F18},
          770         {"f19", XK_F19},
          771         {"f2", XK_F2},
          772         {"f20", XK_F20},
          773         {"f21", XK_F21},
          774         {"f22", XK_F22},
          775         {"f23", XK_F23},
          776         {"f24", XK_F24},
          777         {"f25", XK_F25},
          778         {"f26", XK_F26},
          779         {"f27", XK_F27},
          780         {"f28", XK_F28},
          781         {"f29", XK_F29},
          782         {"f3", XK_F3},
          783         {"f30", XK_F30},
          784         {"f31", XK_F31},
          785         {"f32", XK_F32},
          786         {"f33", XK_F33},
          787         {"f34", XK_F34},
          788         {"f35", XK_F35},
          789         {"f4", XK_F4},
          790         {"f5", XK_F5},
          791         {"f6", XK_F6},
          792         {"f7", XK_F7},
          793         {"f8", XK_F8},
          794         {"f9", XK_F9},
          795 
          796         {"find", XK_Find},
          797         {"help", XK_Help},
          798         {"home", XK_Home},
          799         {"insert", XK_Insert},
          800 
          801         {"kp-0", XK_KP_0},
          802         {"kp-1", XK_KP_1},
          803         {"kp-2", XK_KP_2},
          804         {"kp-3", XK_KP_3},
          805         {"kp-4", XK_KP_4},
          806         {"kp-5", XK_KP_5},
          807         {"kp-6", XK_KP_6},
          808         {"kp-7", XK_KP_7},
          809         {"kp-8", XK_KP_8},
          810         {"kp-9", XK_KP_9},
          811         {"kp-add", XK_KP_Add},
          812         {"kp-begin", XK_KP_Begin},
          813         {"kp-decimal", XK_KP_Decimal},
          814         {"kp-delete", XK_KP_Delete},
          815         {"kp-divide", XK_KP_Divide},
          816         {"kp-down", XK_KP_Down},
          817         {"kp-end", XK_KP_End},
          818         {"kp-enter", XK_KP_Enter},
          819         {"kp-equal", XK_KP_Equal},
          820         {"kp-f1", XK_KP_F1},
          821         {"kp-f2", XK_KP_F2},
          822         {"kp-f3", XK_KP_F3},
          823         {"kp-f4", XK_KP_F4},
          824         {"kp-home", XK_KP_Home},
          825         {"kp-insert", XK_KP_Insert},
          826         {"kp-left", XK_KP_Left},
          827         {"kp-multiply", XK_KP_Multiply},
          828         {"kp-next", XK_KP_Next},
          829         {"kp-page-down", XK_KP_Page_Down},
          830         {"kp-page-up", XK_KP_Page_Up},
          831         {"kp-prior", XK_KP_Prior},
          832         {"kp-right", XK_KP_Right},
          833         {"kp-separator", XK_KP_Separator},
          834         {"kp-space", XK_KP_Space},
          835         {"kp-subtract", XK_KP_Subtract},
          836         {"kp-tab", XK_KP_Tab},
          837         {"kp-up", XK_KP_Up},
          838 
          839         {"l1", XK_L1},
          840         {"l10", XK_L10},
          841         {"l2", XK_L2},
          842         {"l3", XK_L3},
          843         {"l4", XK_L4},
          844         {"l5", XK_L5},
          845         {"l6", XK_L6},
          846         {"l7", XK_L7},
          847         {"l8", XK_L8},
          848         {"l9", XK_L9},
          849 
          850         {"left", XK_Left},
          851         {"linefeed", XK_Linefeed},
          852         {"menu", XK_Menu},
          853         {"mode-switch", XK_Mode_switch},
          854         {"next", XK_Next},
          855         {"num-lock", XK_Num_Lock},
          856         {"page-down", XK_Page_Down},
          857         {"page-up", XK_Page_Up},
          858         {"pause", XK_Pause},
          859         {"print", XK_Print},
          860         {"prior", XK_Prior},
          861 
          862         {"r1", XK_R1},
          863         {"r10", XK_R10},
          864         {"r11", XK_R11},
          865         {"r12", XK_R12},
          866         {"r13", XK_R13},
          867         {"r14", XK_R14},
          868         {"r15", XK_R15},
          869         {"r2", XK_R2},
          870         {"r3", XK_R3},
          871         {"r4", XK_R4},
          872         {"r5", XK_R5},
          873         {"r6", XK_R6},
          874         {"r7", XK_R7},
          875         {"r8", XK_R8},
          876         {"r9", XK_R9},
          877 
          878         {"redo", XK_Redo},
          879         {"return", XK_Return},
          880         {"right", XK_Right},
          881         {"script-switch", XK_script_switch},
          882         {"scroll-lock", XK_Scroll_Lock},
          883         {"select", XK_Select},
          884         {"space", XK_space},
          885         {"sysreq", XK_Sys_Req},
          886         {"tab", XK_Tab},
          887         {"up", XK_Up},
          888         {"undo", XK_Undo},
          889 };
          890 
          891 GEN_CB_MAP_HELPERS(keysym_map, struct keysym_mapping, name)
          892 
          893 static int
          894 parse_keysym(char *keysym_str, size_t len, KeySym *sym) {
          895         struct keysym_mapping *km = keysym_map_get_entry(keysym_str, len);
          896         if (!km)
          897                 return 1;
          898         *sym = km->keysym;
          899         return 0;
          900 }
          901 
          902 static int
          903 parse_modemask(char *modemask_str, size_t len, ledit_mode *mode_ret) {
          904         size_t cur = 0;
          905         *mode_ret = 0;
          906         while (cur < len) {
          907                 if (str_array_equal("normal", modemask_str + cur, LEDIT_MIN(6, len - cur))) {
          908                         cur += 6;
          909                         *mode_ret |= NORMAL;
          910                 } else if (str_array_equal("visual", modemask_str + cur, LEDIT_MIN(6, len - cur))) {
          911                         cur += 6;
          912                         *mode_ret |= VISUAL;
          913                 } else if (str_array_equal("insert", modemask_str + cur, LEDIT_MIN(6, len - cur))) {
          914                         cur += 6;
          915                         *mode_ret |= INSERT;
          916                 } else {
          917                         return 1;
          918                 }
          919                 if (cur < len && modemask_str[cur] != '|')
          920                         return 1;
          921                 else
          922                         cur++;
          923         }
          924         return 0;
          925 }
          926 
          927 static int
          928 parse_modmask(char *modmask_str, size_t len, unsigned int *mask_ret) {
          929         size_t cur = 0;
          930         *mask_ret = 0;
          931         while (cur < len) {
          932                 if (str_array_equal("shift", modmask_str + cur, LEDIT_MIN(5, len - cur))) {
          933                         cur += 5;
          934                         *mask_ret |= ShiftMask;
          935                 /*
          936                 } else if (str_array_equal("lock", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
          937                         cur += 4;
          938                         *mask_ret |= LockMask;
          939                 */
          940                 } else if (str_array_equal("control", modmask_str + cur, LEDIT_MIN(7, len - cur))) {
          941                         cur += 7;
          942                         *mask_ret |= ControlMask;
          943                 } else if (str_array_equal("mod1", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
          944                         cur += 4;
          945                         *mask_ret |= Mod1Mask;
          946                 /*
          947                 } else if (str_array_equal("mod2", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
          948                         cur += 4;
          949                         *mask_ret |= Mod2Mask;
          950                 */
          951                 } else if (str_array_equal("mod3", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
          952                         cur += 4;
          953                         *mask_ret |= Mod3Mask;
          954                 } else if (str_array_equal("mod4", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
          955                         cur += 4;
          956                         *mask_ret |= Mod4Mask;
          957                 } else if (str_array_equal("mod5", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
          958                         cur += 4;
          959                         *mask_ret |= Mod5Mask;
          960                 } else if (str_array_equal("any", modmask_str + cur, LEDIT_MIN(3, len - cur))) {
          961                         cur += 3;
          962                         *mask_ret = UINT_MAX;
          963                 } else {
          964                         return 1;
          965                 }
          966                 if (cur < len && modmask_str[cur] != '|')
          967                         return 1;
          968                 else
          969                         cur++;
          970         }
          971         return 0;
          972 }
          973 
          974 /* FIXME: it would probably be safer to not write the string lengths by hand... */
          975 static int
          976 parse_command_modemask(char *mode_str, size_t len, command_mode *mode_ret) {
          977         size_t cur = 0;
          978         *mode_ret = 0;
          979         /* IMPORTANT: these need to be sorted appropriately so e.g. edit doesn't mess with edit-search */
          980         while (cur < len) {
          981                 if (str_array_equal("substitute", mode_str + cur, LEDIT_MIN(10, len - cur))) {
          982                         cur += 10;
          983                         *mode_ret |= CMD_SUBSTITUTE;
          984                 } else if (str_array_equal("edit-search-backwards", mode_str + cur, LEDIT_MIN(21, len - cur))) {
          985                         cur += 21;
          986                         *mode_ret |= CMD_EDITSEARCHB;
          987                 } else if (str_array_equal("edit-search", mode_str + cur, LEDIT_MIN(11, len - cur))) {
          988                         cur += 11;
          989                         *mode_ret |= CMD_EDITSEARCH;
          990                 } else if (str_array_equal("edit", mode_str + cur, LEDIT_MIN(4, len - cur))) {
          991                         cur += 4;
          992                         *mode_ret |= CMD_EDIT;
          993                 } else {
          994                         return 1;
          995                 }
          996                 if (cur < len && mode_str[cur] != '|') {
          997                         return 1;
          998                 } else {
          999                         cur++;
         1000                 }
         1001         }
         1002         return 0;
         1003 }
         1004 
         1005 /* FIXME: generic dynamic array */
         1006 
         1007 static void
         1008 push_lang(struct config *cfg) {
         1009         if (cfg->num_langs == cfg->alloc_langs) {
         1010                 cfg->alloc_langs = ideal_array_size(cfg->alloc_langs, add_sz(cfg->num_langs, 1));
         1011                 cfg->basic_keys = ledit_reallocarray(cfg->basic_keys, cfg->alloc_langs, sizeof(basic_key_array));
         1012                 cfg->command_keys = ledit_reallocarray(cfg->command_keys, cfg->alloc_langs, sizeof(command_key_array));
         1013                 cfg->cmds = ledit_reallocarray(cfg->cmds, cfg->alloc_langs, sizeof(command_array));
         1014                 cfg->langs = ledit_reallocarray(cfg->langs, cfg->alloc_langs, sizeof(char *));
         1015         }
         1016         basic_key_array *arr1 = &cfg->basic_keys[cfg->num_langs];
         1017         arr1->keys = NULL;
         1018         arr1->num_keys = arr1->alloc_keys = 0;
         1019         command_key_array *arr2 = &cfg->command_keys[cfg->num_langs];
         1020         arr2->keys = NULL;
         1021         arr2->num_keys = arr2->alloc_keys = 0;
         1022         command_array *arr3 = &cfg->cmds[cfg->num_langs];
         1023         arr3->cmds = NULL;
         1024         arr3->num_cmds = arr3->alloc_cmds = 0;
         1025         cfg->langs[cfg->num_langs] = NULL;
         1026         cfg->num_langs++;
         1027 }
         1028 
         1029 #define GEN_PARSE_STATEMENT(name, cb_type, mapping_type, mode_parse_func)                             \
         1030 static int                                                                                            \
         1031 name(ast_statement *st, mapping_type *m, char *filename, char **errstr) {                             \
         1032         size_t line = st->func_tok.line;                                                              \
         1033         size_t line_offset = st->func_tok.line_offset;                                                \
         1034         m->cb = NULL;                                                                                 \
         1035         m->text = NULL;                                                                               \
         1036         m->mods = 0;                                                                                  \
         1037         m->modes = 0;                                                                                 \
         1038         m->keysym = 0;                                                                                \
         1039         char *msg = NULL;                                                                             \
         1040         if (!str_array_equal("bind", st->func_tok.text, st->func_tok.len) || st->len < 1) {           \
         1041                 msg = "Invalid statement";                                                            \
         1042                 goto error;                                                                           \
         1043         }                                                                                             \
         1044         m->cb = cb_type##_map_get_entry(st->args[0].text, st->args[0].len);                           \
         1045         if (!m->cb) {                                                                                 \
         1046                 msg = "Invalid function specification";                                               \
         1047                 goto error;                                                                           \
         1048         }                                                                                             \
         1049         int text_init = 0, keysym_init = 0, modes_init = 0, mods_init = 0;                            \
         1050         for (size_t i = 1; i < st->len; i++) {                                                        \
         1051                 line = st->args[i].line;                                                              \
         1052                 line_offset = st->args[i].line_offset;                                                \
         1053                 if (str_array_equal("mods", st->args[i].text, st->args[i].len)) {                     \
         1054                         if (mods_init) {                                                              \
         1055                                 msg = "Duplicate mods specification";                                 \
         1056                                 goto error;                                                           \
         1057                         } else if (i == st->len - 1) {                                                \
         1058                                 msg = "Unfinished statement";                                         \
         1059                                 goto error;                                                           \
         1060                         }                                                                             \
         1061                         i++;                                                                          \
         1062                         if (parse_modmask(st->args[i].text, st->args[i].len, &m->mods)) {             \
         1063                                 msg = "Invalid mods specification";                                   \
         1064                                 goto error;                                                           \
         1065                         }                                                                             \
         1066                         mods_init = 1;                                                                \
         1067                 } else if (str_array_equal("modes", st->args[i].text, st->args[i].len)) {             \
         1068                         if (modes_init) {                                                             \
         1069                                 msg = "Duplicate modes specification";                                \
         1070                                 goto error;                                                           \
         1071                         } else if (i == st->len - 1) {                                                \
         1072                                 msg = "Unfinished statement";                                         \
         1073                                 goto error;                                                           \
         1074                         }                                                                             \
         1075                         i++;                                                                          \
         1076                         if (mode_parse_func(st->args[i].text, st->args[i].len, &m->modes)) {          \
         1077                                 msg = "Invalid modes specification";                                  \
         1078                                 goto error;                                                           \
         1079                         } else if (!cb_type##_modemask_is_valid(m->cb, m->modes)) {                   \
         1080                                 msg = "Function not defined for all given modes";                     \
         1081                                 goto error;                                                           \
         1082                         }                                                                             \
         1083                         modes_init = 1;                                                               \
         1084                 } else if (str_array_equal("keysym", st->args[i].text, st->args[i].len)) {            \
         1085                         if (text_init) {                                                              \
         1086                                 msg = "Text already specified";                                       \
         1087                                 goto error;                                                           \
         1088                         } else if (keysym_init) {                                                     \
         1089                                 msg = "Duplicate keysym specification";                               \
         1090                                 goto error;                                                           \
         1091                         } else if (i == st->len - 1) {                                                \
         1092                                 msg = "Unfinished statement";                                         \
         1093                                 goto error;                                                           \
         1094                         }                                                                             \
         1095                         i++;                                                                          \
         1096                         if (parse_keysym(st->args[i].text, st->args[i].len, &m->keysym)) {            \
         1097                                 msg = "Invalid keysym specification";                                 \
         1098                                 goto error;                                                           \
         1099                         }                                                                             \
         1100                         keysym_init = 1;                                                              \
         1101                 } else if (str_array_equal("text", st->args[i].text, st->args[i].len)) {              \
         1102                         if (keysym_init) {                                                            \
         1103                                 msg = "Keysym already specified";                                     \
         1104                                 goto error;                                                           \
         1105                         } else if (text_init) {                                                       \
         1106                                 msg = "Duplicate text specification";                                 \
         1107                                 goto error;                                                           \
         1108                         } else if (i == st->len - 1) {                                                \
         1109                                 msg = "Unfinished statement";                                         \
         1110                                 goto error;                                                           \
         1111                         }                                                                             \
         1112                         i++;                                                                          \
         1113                         m->text = ledit_strndup(st->args[i].text, st->args[i].len);                   \
         1114                         text_init = 1;                                                                \
         1115                 } else if (str_array_equal("catchall", st->args[i].text, st->args[i].len)) {          \
         1116                         if (keysym_init) {                                                            \
         1117                                 msg = "Keysym already specified";                                     \
         1118                                 goto error;                                                           \
         1119                         } else if (text_init) {                                                       \
         1120                                 msg = "Duplicate text specification";                                 \
         1121                                 goto error;                                                           \
         1122                         }                                                                             \
         1123                         m->text = ledit_strdup("");                                                   \
         1124                         text_init = 1;                                                                \
         1125                 } else {                                                                              \
         1126                         msg = "Invalid statement";                                                    \
         1127                         goto error;                                                                   \
         1128                 }                                                                                     \
         1129         }                                                                                             \
         1130         if (!text_init && !keysym_init) {                                                             \
         1131                 msg = "No text or keysym specified";                                                  \
         1132                 goto error;                                                                           \
         1133         }                                                                                             \
         1134         if (!modes_init) {                                                                            \
         1135                 msg = "No modes specified";                                                           \
         1136                 goto error;                                                                           \
         1137         }                                                                                             \
         1138         return 0;                                                                                     \
         1139 error:                                                                                                \
         1140         if (msg) {                                                                                    \
         1141                 *errstr = print_fmt(                                                                  \
         1142                     "%s, line %zu, offset %zu: %s", filename, line, line_offset, msg                  \
         1143                  );                                                                                   \
         1144         }                                                                                             \
         1145         if (m->text)                                                                                  \
         1146                 free(m->text);                                                                        \
         1147         return 1;                                                                                     \
         1148 }
         1149 
         1150 GEN_PARSE_STATEMENT(parse_basic_key_statement, basic_key_cb, basic_key_mapping, parse_modemask)
         1151 GEN_PARSE_STATEMENT(parse_command_key_statement, command_key_cb, command_key_mapping, parse_command_modemask)
         1152 
         1153 static int
         1154 parse_command_statement(ast_statement *st, command_mapping *m, char *filename, char **errstr) {
         1155         size_t line = st->func_tok.line;
         1156         size_t line_offset = st->func_tok.line_offset;
         1157         m->cb = NULL;
         1158         m->text = NULL;
         1159         char *msg = NULL;
         1160         if (!str_array_equal("bind", st->func_tok.text, st->func_tok.len) || st->len != 2) {
         1161                 msg = "Invalid statement";
         1162                 goto error;
         1163         }
         1164         m->cb = command_cb_map_get_entry(st->args[0].text, st->args[0].len);
         1165         if (!m->cb) {
         1166                 msg = "Invalid function specification";
         1167                 goto error;
         1168         }
         1169         m->text = ledit_strndup(st->args[1].text, st->args[1].len);
         1170         return 0;
         1171 error:
         1172         if (msg) {
         1173                 *errstr = print_fmt(
         1174                     "%s, line %zu, offset %zu: %s", filename, line, line_offset, msg
         1175                 );
         1176         }
         1177         /* I guess this is unnecessary */
         1178         if (m->text)
         1179                 free(m->text);
         1180         return 1;
         1181 }
         1182 
         1183 static void
         1184 push_basic_key_mapping(basic_key_array *arr, basic_key_mapping m) {
         1185         if (arr->num_keys == arr->alloc_keys) {
         1186                 arr->alloc_keys = ideal_array_size(arr->alloc_keys, add_sz(arr->num_keys, 1));
         1187                 arr->keys = ledit_reallocarray(arr->keys, arr->alloc_keys, sizeof(basic_key_mapping));
         1188         }
         1189         arr->keys[arr->num_keys] = m;
         1190         arr->num_keys++;
         1191 }
         1192 
         1193 static void
         1194 push_command_key_mapping(command_key_array *arr, command_key_mapping m) {
         1195         if (arr->num_keys == arr->alloc_keys) {
         1196                 arr->alloc_keys = ideal_array_size(arr->alloc_keys, add_sz(arr->num_keys, 1));
         1197                 arr->keys = ledit_reallocarray(arr->keys, arr->alloc_keys, sizeof(command_key_mapping));
         1198         }
         1199         arr->keys[arr->num_keys] = m;
         1200         arr->num_keys++;
         1201 }
         1202 
         1203 static void
         1204 push_command_mapping(command_array *arr, command_mapping m) {
         1205         if (arr->num_cmds == arr->alloc_cmds) {
         1206                 arr->alloc_cmds = ideal_array_size(arr->alloc_cmds, add_sz(arr->num_cmds, 1));
         1207                 arr->cmds = ledit_reallocarray(arr->cmds, arr->alloc_cmds, sizeof(command_mapping));
         1208         }
         1209         arr->cmds[arr->num_cmds] = m;
         1210         arr->num_cmds++;
         1211 }
         1212 
         1213 /* FIXME: This could be made a lot nicer and less repetitive */
         1214 static int
         1215 load_bindings(struct config *cfg, ast_list *list, char *filename, char **errstr) {
         1216         int basic_keys_init = 0, command_keys_init = 0, commands_init = 0;
         1217         size_t cur_lang = cfg->num_langs - 1; /* FIXME: ensure no underflow */
         1218         for (size_t i = 0; i < list->len; i++) {
         1219                 size_t line = list->objs[i].tok.line;
         1220                 size_t line_offset = list->objs[i].tok.line_offset;
         1221                 if (list->objs[i].type != OBJ_ASSIGNMENT) {
         1222                         *errstr = print_fmt(
         1223                              "%s: Invalid statement in bindings configuration "
         1224                              "at list %zu, offset %zu", filename, line, line_offset
         1225                          );
         1226                          goto error;
         1227                 }
         1228                 char *key = list->objs[i].obj.assignment.tok.text;
         1229                 size_t key_len = list->objs[i].obj.assignment.tok.len;
         1230                 if (str_array_equal("language", key, key_len)) {
         1231                         if (list->objs[i].obj.assignment.value->type != OBJ_STRING) {
         1232                                 *errstr = print_fmt(
         1233                                     "%s: Invalid language setting in bindings configuration "
         1234                                     "at line %zu, offset %zu", filename, line, line_offset
         1235                                 );
         1236                                 goto error;
         1237                         } else if (cfg->langs[cur_lang]) {
         1238                                 *errstr = print_fmt(
         1239                                     "%s: Duplicate language setting in bindings configuration "
         1240                                     "at line %zu, offset %zu", filename, line, line_offset
         1241                                 );
         1242                                 goto error;
         1243                         }
         1244                         char *val = list->objs[i].obj.assignment.value->obj.str.tok.text;
         1245                         size_t val_len = list->objs[i].obj.assignment.value->obj.str.tok.len;
         1246                         cfg->langs[cur_lang] = ledit_strndup(val, val_len);
         1247                 } else if (str_array_equal("basic-keys", key, key_len)) {
         1248                         if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
         1249                                 *errstr = print_fmt(
         1250                                     "%s: Invalid basic-keys setting in bindings configuration "
         1251                                     "at line %zu, offset %zu", filename, line, line_offset
         1252                                 );
         1253                                 goto error;
         1254                         } else if (basic_keys_init) {
         1255                                 *errstr = print_fmt(
         1256                                     "%s: Duplicate basic-keys setting in bindings configuration "
         1257                                     "at line %zu, offset %zu", filename, line, line_offset
         1258                                 );
         1259                                 goto error;
         1260                         }
         1261                         ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
         1262                         for (size_t j = 0; j < slist->len; j++) {
         1263                                 line = slist->objs[j].tok.line;
         1264                                 line_offset = slist->objs[j].tok.line_offset;
         1265                                 if (slist->objs[j].type != OBJ_STATEMENT) {
         1266                                         *errstr = print_fmt(
         1267                                             "%s: Invalid basic-keys setting in bindings configuration "
         1268                                             "at line %zu, offset %zu", filename, line, line_offset
         1269                                         );
         1270                                         goto error;
         1271                                 }
         1272                                 basic_key_mapping m;
         1273                                 if (parse_basic_key_statement(&slist->objs[j].obj.statement, &m, filename, errstr))
         1274                                         goto error;
         1275                                 push_basic_key_mapping(&cfg->basic_keys[0], m);
         1276                         }
         1277                         basic_keys_init = 1;
         1278                 } else if (str_array_equal("command-keys", key, key_len)) {
         1279                         if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
         1280                                 *errstr = print_fmt(
         1281                                     "%s: Invalid command-keys setting in bindings configuration "
         1282                                     "at line %zu, offset %zu", filename, line, line_offset
         1283                                 );
         1284                                 goto error;
         1285                         } else if (command_keys_init) {
         1286                                 *errstr = print_fmt(
         1287                                     "%s: Duplicate command-keys setting in bindings configuration "
         1288                                     "at line %zu, offset %zu", filename, line, line_offset
         1289                                 );
         1290                                 goto error;
         1291                         }
         1292                         ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
         1293                         for (size_t j = 0; j < slist->len; j++) {
         1294                                 line = slist->objs[j].tok.line;
         1295                                 line_offset = slist->objs[j].tok.line_offset;
         1296                                 if (slist->objs[j].type != OBJ_STATEMENT) {
         1297                                         *errstr = print_fmt(
         1298                                             "%s: Invalid command-keys setting in bindings configuration "
         1299                                             "at line %zu, offset %zu", filename, line, line_offset
         1300                                         );
         1301                                         goto error;
         1302                                 }
         1303                                 command_key_mapping m;
         1304                                 if (parse_command_key_statement(&slist->objs[j].obj.statement, &m, filename, errstr))
         1305                                         goto error;
         1306                                 push_command_key_mapping(&cfg->command_keys[0], m);
         1307                         }
         1308                         command_keys_init = 1;
         1309                 } else if (str_array_equal("commands", key, key_len)) {
         1310                         if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
         1311                                 *errstr = print_fmt(
         1312                                     "%s: Invalid commands setting in bindings configuration "
         1313                                     "at line %zu, offset %zu", filename, line, line_offset
         1314                                 );
         1315                                 goto error;
         1316                         } else if (commands_init) {
         1317                                 *errstr = print_fmt(
         1318                                     "%s: Duplicate commands setting in bindings configuration "
         1319                                     "at line %zu, offset %zu", filename, line, line_offset
         1320                                 );
         1321                                 goto error;
         1322                         }
         1323                         ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
         1324                         for (size_t j = 0; j < slist->len; j++) {
         1325                                 line = slist->objs[j].tok.line;
         1326                                 line_offset = slist->objs[j].tok.line_offset;
         1327                                 if (slist->objs[j].type != OBJ_STATEMENT) {
         1328                                         *errstr = print_fmt(
         1329                                             "%s: Invalid commands setting in bindings configuration "
         1330                                             "at line %zu, offset %zu", filename, line, line_offset
         1331                                         );
         1332                                         goto error;
         1333                                 }
         1334                                 command_mapping m;
         1335                                 if (parse_command_statement(&slist->objs[j].obj.statement, &m, filename, errstr))
         1336                                         goto error;
         1337                                 push_command_mapping(&cfg->cmds[0], m);
         1338                         }
         1339                         commands_init = 1;
         1340                 }
         1341         }
         1342 
         1343         /* FIXME: the behavior here is a bit weird - if there is nothing other than a language
         1344            setting in the bindings configuration, all actual bindings are default, but the
         1345            associated language is different */
         1346         if (!cfg->langs[cur_lang]) {
         1347                 cfg->langs[cur_lang] = ledit_strdup(language_default);
         1348         }
         1349         /* FIXME: avoid calling strlen */
         1350         if (!basic_keys_init) {
         1351                 ledit_debug("No basic keys configured in bindings; loading defaults\n");
         1352                 basic_key_mapping m;
         1353                 for (size_t i = 0; i < LENGTH(basic_keys_default); i++) {
         1354                         m.cb = basic_key_cb_map_get_entry(basic_keys_default[i].func_name, strlen(basic_keys_default[i].func_name));
         1355                         if (!m.cb) {
         1356                                 *errstr = print_fmt("default config: Invalid basic key function name '%s'", basic_keys_default[i].func_name);
         1357                                 goto error;
         1358                         } else if (!basic_key_cb_modemask_is_valid(m.cb, basic_keys_default[i].modes)) {
         1359                                 *errstr = print_fmt("default config: Function '%s' not defined for all given modes", basic_keys_default[i].func_name);
         1360                                 goto error;
         1361                         }
         1362                         m.text = basic_keys_default[i].text ? ledit_strdup(basic_keys_default[i].text) : NULL;
         1363                         m.mods = basic_keys_default[i].mods;
         1364                         m.modes = basic_keys_default[i].modes;
         1365                         m.keysym = basic_keys_default[i].keysym;
         1366                         push_basic_key_mapping(&cfg->basic_keys[0], m);
         1367                 }
         1368         }
         1369         if (!command_keys_init) {
         1370                 ledit_debug("No command keys configured in bindings; loading defaults\n");
         1371                 command_key_mapping m;
         1372                 for (size_t i = 0; i < LENGTH(command_keys_default); i++) {
         1373                         m.cb = command_key_cb_map_get_entry(command_keys_default[i].func_name, strlen(command_keys_default[i].func_name));
         1374                         if (!m.cb) {
         1375                                 *errstr = print_fmt("default config: Invalid command key function name '%s'", command_keys_default[i].func_name);
         1376                                 goto error;
         1377                         } else if (!command_key_cb_modemask_is_valid(m.cb, command_keys_default[i].modes)) {
         1378                                 *errstr = print_fmt("default config: Function '%s' not defined for all given modes", command_keys_default[i].func_name);
         1379                                 goto error;
         1380                         }
         1381                         m.text = command_keys_default[i].text ? ledit_strdup(command_keys_default[i].text) : NULL;
         1382                         m.mods = command_keys_default[i].mods;
         1383                         m.modes = command_keys_default[i].modes;
         1384                         m.keysym = command_keys_default[i].keysym;
         1385                         push_command_key_mapping(&cfg->command_keys[0], m);
         1386                 }
         1387         }
         1388         /* FIXME: guard against NULL text in default config! */
         1389         if (!commands_init) {
         1390                 ledit_debug("No commands configured in bindings; loading defaults\n");
         1391                 command_mapping m;
         1392                 for (size_t i = 0; i < LENGTH(commands_default); i++) {
         1393                         m.cb = command_cb_map_get_entry(commands_default[i].func_name, strlen(commands_default[i].func_name));
         1394                         if (!m.cb) {
         1395                                 *errstr = print_fmt("default config: Invalid command function name '%s'", commands_default[i].func_name);
         1396                                 goto error;
         1397                         }
         1398                         m.text = ledit_strdup(commands_default[i].text);
         1399                         push_command_mapping(&cfg->cmds[0], m);
         1400                 }
         1401         }
         1402         return 0;
         1403 /* FIXME: simplify error handling by doing more here */
         1404 error:
         1405         return 1;
         1406 }
         1407 
         1408 static int
         1409 load_mapping(struct config *cfg, ast_list *list, char *filename, char **errstr) {
         1410         int key_mapping_init = 0, command_mapping_init = 0;
         1411         size_t cur_lang = cfg->num_langs - 1; /* FIXME: ensure no underflow */
         1412         for (size_t i = 0; i < list->len; i++) {
         1413                 size_t line = list->objs[i].tok.line;
         1414                 size_t line_offset = list->objs[i].tok.line_offset;
         1415                 if (list->objs[i].type != OBJ_ASSIGNMENT) {
         1416                         *errstr = print_fmt(
         1417                              "%s: Invalid statement in language mapping configuration "
         1418                              "at list %zu, offset %zu", filename, line, line_offset
         1419                          );
         1420                          goto error;
         1421                 }
         1422                 char *key = list->objs[i].obj.assignment.tok.text;
         1423                 size_t key_len = list->objs[i].obj.assignment.tok.len;
         1424                 basic_key_array *bkmap = &cfg->basic_keys[cur_lang];
         1425                 command_key_array *ckmap = &cfg->command_keys[cur_lang];
         1426                 command_array *cmap = &cfg->cmds[cur_lang];
         1427                 if (str_array_equal("language", key, key_len)) {
         1428                         if (list->objs[i].obj.assignment.value->type != OBJ_STRING) {
         1429                                 *errstr = print_fmt(
         1430                                     "%s: Invalid language setting in language mapping configuration "
         1431                                     "at line %zu, offset %zu", filename, line, line_offset
         1432                                 );
         1433                                 goto error;
         1434                         } else if (cfg->langs[cur_lang]) {
         1435                                 *errstr = print_fmt(
         1436                                     "%s: Duplicate language setting in language mapping configuration "
         1437                                     "at line %zu, offset %zu", filename, line, line_offset
         1438                                 );
         1439                                 goto error;
         1440                         }
         1441                         char *val = list->objs[i].obj.assignment.value->obj.str.tok.text;
         1442                         size_t val_len = list->objs[i].obj.assignment.value->obj.str.tok.len;
         1443                         cfg->langs[cur_lang] = ledit_strndup(val, val_len);
         1444                 } else if (str_array_equal("key-mapping", key, key_len)) {
         1445                         if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
         1446                                 *errstr = print_fmt(
         1447                                     "%s: Invalid key-mapping setting in language mapping configuration "
         1448                                     "at line %zu, offset %zu", filename, line, line_offset
         1449                                 );
         1450                                 goto error;
         1451                         } else if (key_mapping_init) {
         1452                                 *errstr = print_fmt(
         1453                                     "%s: Duplicate key-mapping setting in language mapping configuration "
         1454                                     "at line %zu, offset %zu", filename, line, line_offset
         1455                                 );
         1456                                 goto error;
         1457                         }
         1458                         ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
         1459                         for (size_t j = 0; j < slist->len; j++) {
         1460                                 line = slist->objs[j].tok.line;
         1461                                 line_offset = slist->objs[j].tok.line_offset;
         1462                                 if (slist->objs[j].type != OBJ_STATEMENT) {
         1463                                         *errstr = print_fmt(
         1464                                             "%s: Invalid key-mapping setting in language mapping configuration "
         1465                                             "at line %zu, offset %zu", filename, line, line_offset
         1466                                         );
         1467                                         goto error;
         1468                                 }
         1469                                 ast_statement *st = &slist->objs[j].obj.statement;
         1470                                 if (!str_array_equal("map", st->func_tok.text, st->func_tok.len) || st->len != 2) {
         1471                                         *errstr = print_fmt(
         1472                                             "%s: Invalid key-mapping statement in language mapping configuration "
         1473                                             "at line %zu, offset %zu", filename, line, line_offset
         1474                                         );
         1475                                         goto error;
         1476                                 }
         1477                                 /* FIXME: any way to speed this up? I guess once the keys can be binary-searched... */
         1478                                 for (size_t k = 0; k < bkmap->num_keys; k++) {
         1479                                         if (bkmap->keys[k].text && str_array_equal(bkmap->keys[k].text, st->args[1].text, st->args[1].len)) {
         1480                                                 free(bkmap->keys[k].text);
         1481                                                 bkmap->keys[k].text = ledit_strndup(st->args[0].text, st->args[0].len);
         1482                                         }
         1483                                 }
         1484                                 for (size_t k = 0; k < ckmap->num_keys; k++) {
         1485                                         if (ckmap->keys[k].text && str_array_equal(ckmap->keys[k].text, st->args[1].text, st->args[1].len)) {
         1486                                                 free(ckmap->keys[k].text);
         1487                                                 ckmap->keys[k].text = ledit_strndup(st->args[0].text, st->args[0].len);
         1488                                         }
         1489                                 }
         1490                         }
         1491                         key_mapping_init = 1;
         1492                 } else if (str_array_equal("command-mapping", key, key_len)) {
         1493                         if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
         1494                                 *errstr = print_fmt(
         1495                                     "%s: Invalid command-mapping setting in language mapping configuration "
         1496                                     "at line %zu, offset %zu", filename, line, line_offset
         1497                                 );
         1498                                 goto error;
         1499                         } else if (command_mapping_init) {
         1500                                 *errstr = print_fmt(
         1501                                     "%s: Duplicate command-mapping setting in language mapping configuration "
         1502                                     "at line %zu, offset %zu", filename, line, line_offset
         1503                                 );
         1504                                 goto error;
         1505                         }
         1506                         ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
         1507                         for (size_t j = 0; j < slist->len; j++) {
         1508                                 line = slist->objs[j].tok.line;
         1509                                 line_offset = slist->objs[j].tok.line_offset;
         1510                                 if (slist->objs[j].type != OBJ_STATEMENT) {
         1511                                         *errstr = print_fmt(
         1512                                             "%s: Invalid command-mapping setting in language mapping configuration "
         1513                                             "at line %zu, offset %zu", filename, line, line_offset
         1514                                         );
         1515                                         goto error;
         1516                                 }
         1517                                 ast_statement *st = &slist->objs[j].obj.statement;
         1518                                 if (!str_array_equal("map", st->func_tok.text, st->func_tok.len) || st->len != 2) {
         1519                                         *errstr = print_fmt(
         1520                                             "%s: Invalid command-mapping statement in language mapping configuration "
         1521                                             "at line %zu, offset %zu", filename, line, line_offset
         1522                                         );
         1523                                         goto error;
         1524                                 }
         1525                                 for (size_t k = 0; k < cmap->num_cmds; k++) {
         1526                                         if (str_array_equal(cmap->cmds[k].text, st->args[1].text, st->args[1].len)) {
         1527                                                 free(cmap->cmds[k].text);
         1528                                                 cmap->cmds[k].text = ledit_strndup(st->args[0].text, st->args[0].len);
         1529                                         }
         1530                                 }
         1531                         }
         1532                         command_mapping_init = 1;
         1533                 }
         1534         }
         1535         if (!cfg->langs[cur_lang]) {
         1536                 /* FIXME: pass actual beginning line and offset so this doesn't have to
         1537                    use the line and offset of the first list element */
         1538                 if (list->len > 0) {
         1539                         *errstr = print_fmt(
         1540                             "%s: Missing language setting in language mapping configuration "
         1541                             "at line %zu, offset %zu", filename, list->objs[0].tok.line, list->objs[0].tok.line_offset
         1542                         );
         1543                 } else {
         1544                         *errstr = print_fmt("%s: Missing language setting in language mapping configuration", filename);
         1545                 }
         1546                 goto error;
         1547         }
         1548         return 0;
         1549 error:
         1550         return 1;
         1551 }
         1552 
         1553 static void
         1554 append_mapping(struct config *cfg) {
         1555         push_lang(cfg);
         1556         ledit_assert(cfg->num_langs > 1);
         1557 
         1558         /* first duplicate original mappings before replacing the text */
         1559         /* FIXME: optimize this to avoid useless reallocations */
         1560         size_t cur_lang = cfg->num_langs - 1;
         1561         basic_key_array *arr1 = &cfg->basic_keys[cur_lang];
         1562         arr1->num_keys = arr1->alloc_keys = cfg->basic_keys[0].num_keys;
         1563         arr1->keys = ledit_reallocarray(NULL, arr1->num_keys, sizeof(basic_key_mapping));
         1564         memmove(arr1->keys, cfg->basic_keys[0].keys, arr1->num_keys * sizeof(basic_key_mapping));
         1565         for (size_t i = 0; i < arr1->num_keys; i++) {
         1566                 if (arr1->keys[i].text)
         1567                         arr1->keys[i].text = ledit_strdup(arr1->keys[i].text);
         1568         }
         1569 
         1570 
         1571         command_key_array *arr2 = &cfg->command_keys[cur_lang];
         1572         arr2->num_keys = arr2->alloc_keys = cfg->command_keys[0].num_keys;
         1573         arr2->keys = ledit_reallocarray(NULL, arr2->num_keys, sizeof(command_key_mapping));
         1574         memmove(arr2->keys, cfg->command_keys[0].keys, arr2->num_keys * sizeof(command_key_mapping));
         1575         for (size_t i = 0; i < arr2->num_keys; i++) {
         1576                 if (arr2->keys[i].text)
         1577                         arr2->keys[i].text = ledit_strdup(arr2->keys[i].text);
         1578         }
         1579 
         1580         command_array *arr3 = &cfg->cmds[cur_lang];
         1581         arr3->num_cmds = arr3->alloc_cmds = cfg->cmds[0].num_cmds;
         1582         arr3->cmds = ledit_reallocarray(NULL, arr3->num_cmds, sizeof(command_mapping));
         1583         memmove(arr3->cmds, cfg->cmds[0].cmds, arr3->num_cmds * sizeof(command_mapping));
         1584         for (size_t i = 0; i < arr3->num_cmds; i++) {
         1585                 arr3->cmds[i].text = ledit_strdup(arr3->cmds[i].text);
         1586         }
         1587 }
         1588 
         1589 #ifdef LEDIT_DEBUG
         1590 static void
         1591 debug_print_obj(ast_obj *obj, int shiftwidth) {
         1592         for (int i = 0; i < shiftwidth; i++) {
         1593                 fprintf(stderr, " ");
         1594         }
         1595         switch (obj->type) {
         1596         case OBJ_STRING:
         1597                 fprintf(stderr, "STRING: %.*s\n", (int)obj->obj.str.tok.len, obj->obj.str.tok.text);
         1598                 break;
         1599         case OBJ_STATEMENT:
         1600                 fprintf(stderr, "STATEMENT: %.*s ", (int)obj->obj.statement.func_tok.len, obj->obj.statement.func_tok.text);
         1601                 for (size_t i = 0; i < obj->obj.statement.len; i++) {
         1602                         fprintf(stderr, "%.*s ", (int)obj->obj.statement.args[i].len, obj->obj.statement.args[i].text);
         1603                 }
         1604                 fprintf(stderr, "\n");
         1605                 break;
         1606         case OBJ_ASSIGNMENT:
         1607                 fprintf(stderr, "ASSIGNMENT: %.*s =\n", (int)obj->obj.assignment.tok.len, obj->obj.assignment.tok.text);
         1608                 debug_print_obj(obj->obj.assignment.value, shiftwidth + 4);
         1609                 break;
         1610         case OBJ_LIST:
         1611                 fprintf(stderr, "LIST:\n");
         1612                 for (size_t i = 0; i < obj->obj.list.len; i++) {
         1613                         debug_print_obj(&obj->obj.list.objs[i], shiftwidth + 4);
         1614                 }
         1615                 break;
         1616         }
         1617 }
         1618 #endif
         1619 
         1620 /* WARNING: *errstr must be freed! */
         1621 int
         1622 config_loadfile(ledit_common *common, char *filename, char **errstr) {
         1623         #ifdef LEDIT_DEBUG
         1624         struct timespec now, elapsed, last;
         1625         clock_gettime(CLOCK_MONOTONIC, &last);
         1626         #endif
         1627         size_t len;
         1628         *errstr = NULL;
         1629         ast_list list = {.objs = NULL, .len = 0, .cap = 0};
         1630         char *file_contents = NULL;
         1631         if (filename) {
         1632                 file_contents = load_file(filename, &len, errstr);
         1633                 if (!file_contents) return 1;
         1634                 #ifdef LEDIT_DEBUG
         1635                 clock_gettime(CLOCK_MONOTONIC, &now);
         1636                 ledit_timespecsub(&now, &last, &elapsed);
         1637                 ledit_debug_fmt(
         1638                     "Time to load config file: %lld seconds, %ld nanoseconds\n",
         1639                     (long long)elapsed.tv_sec, elapsed.tv_nsec
         1640                 );
         1641                 last = now;
         1642                 #endif
         1643                 /* start at line 1 to make error messages more useful */
         1644                 struct lexstate s = {file_contents, len, 0, 1, 0};
         1645                 if (parse_list(&s, &list, 1, filename, errstr)) {
         1646                         free(file_contents);
         1647                         return 1;
         1648                 }
         1649                 #ifdef LEDIT_DEBUG
         1650                 clock_gettime(CLOCK_MONOTONIC, &now);
         1651                 ledit_timespecsub(&now, &last, &elapsed);
         1652                 ledit_debug_fmt(
         1653                     "Time to parse config file: %lld seconds, %ld nanoseconds\n",
         1654                     (long long)elapsed.tv_sec, elapsed.tv_nsec
         1655                 );
         1656                 #endif
         1657         }
         1658 
         1659         #ifdef LEDIT_DEBUG
         1660         clock_gettime(CLOCK_MONOTONIC, &last);
         1661         for (size_t i = 0; i < list.len; i++) {
         1662                 debug_print_obj(&list.objs[i], 0);
         1663         }
         1664         clock_gettime(CLOCK_MONOTONIC, &now);
         1665         ledit_timespecsub(&now, &last, &elapsed);
         1666         ledit_debug_fmt(
         1667             "Time to print useless information: %lld seconds, %ld nanoseconds\n",
         1668             (long long)elapsed.tv_sec, elapsed.tv_nsec
         1669         );
         1670         clock_gettime(CLOCK_MONOTONIC, &last);
         1671         #endif
         1672 
         1673         struct config cfg = {NULL, NULL, NULL, NULL, NULL, 0, 0};
         1674         int theme_init = 0, bindings_init = 0, mappings_init = 0;
         1675         ast_assignment *assignment;
         1676         for (size_t i = 0; i < list.len; i++) {
         1677                 switch (list.objs[i].type) {
         1678                 case OBJ_ASSIGNMENT:
         1679                         assignment = &list.objs[i].obj.assignment;
         1680                         if (str_array_equal("theme", assignment->tok.text, assignment->tok.len)) {
         1681                                 if (theme_init) {
         1682                                         *errstr = print_fmt(
         1683                                             "%s: Duplicate theme definition at line %zu, offset %zu",
         1684                                             filename, assignment->tok.line, assignment->tok.line_offset
         1685                                         );
         1686                                         goto error;
         1687                                 } else if (assignment->value->type != OBJ_LIST) {
         1688                                         *errstr = print_fmt(
         1689                                             "%s: Invalid theme definition at line %zu, offset %zu",
         1690                                             filename, assignment->tok.line, assignment->tok.line_offset
         1691                                         );
         1692                                         goto error;
         1693                                 }
         1694                                 cfg.theme = load_theme(common, &assignment->value->obj.list, filename, errstr);
         1695                                 if (!cfg.theme)
         1696                                         goto error;
         1697                                 theme_init = 1;
         1698                         } else if (str_array_equal("bindings", assignment->tok.text, assignment->tok.len)) {
         1699                                 if (bindings_init) {
         1700                                         *errstr = print_fmt(
         1701                                             "%s: Duplicate definition of bindings at line %zu, offset %zu",
         1702                                             filename, assignment->tok.line, assignment->tok.line_offset
         1703                                         );
         1704                                         goto error;
         1705                                 }
         1706                                 push_lang(&cfg);
         1707                                 if (assignment->value->type != OBJ_LIST) {
         1708                                         *errstr = print_fmt(
         1709                                             "%s: Invalid definition of bindings at line %zu, offset %zu",
         1710                                             filename, assignment->tok.line, assignment->tok.line_offset
         1711                                         );
         1712                                         goto error;
         1713                                 }
         1714                                 if (load_bindings(&cfg, &assignment->value->obj.list, filename, errstr))
         1715                                         goto error;
         1716                                 bindings_init = 1;
         1717                         } else if (str_array_equal("language-mapping", assignment->tok.text, assignment->tok.len)) {
         1718                                 if (cfg.num_langs == 0) {
         1719                                         ledit_debug("No key/command bindings configured; loading defaults\n");
         1720                                         push_lang(&cfg);
         1721                                         /* load default config */
         1722                                         ast_list empty_list = {.objs = NULL, .len = 0, .cap = 0};
         1723                                         /* shouldn't usually happen */
         1724                                         if (load_bindings(&cfg, &empty_list, filename, errstr))
         1725                                                 goto error;
         1726                                         bindings_init = 1;
         1727                                 } else if (assignment->value->type != OBJ_LIST) {
         1728                                         *errstr = print_fmt(
         1729                                             "%s: Invalid definition of language mapping at line %zu, offset %zu",
         1730                                             filename, assignment->tok.line, assignment->tok.line_offset
         1731                                         );
         1732                                         goto error;
         1733                                 }
         1734 
         1735                                 append_mapping(&cfg);
         1736 
         1737                                 if (load_mapping(&cfg, &assignment->value->obj.list, filename, errstr))
         1738                                         goto error;
         1739                                 mappings_init = 1;
         1740                         } else {
         1741                                 *errstr = print_fmt(
         1742                                     "%s: Invalid assignment at line %zu, offset %zu",
         1743                                     filename, assignment->tok.line, assignment->tok.line_offset
         1744                                 );
         1745                                 goto error;
         1746                         }
         1747                         break;
         1748                 default:
         1749                         *errstr = print_fmt(
         1750                             "%s: Invalid statement at line %zu, offset %zu",
         1751                             filename, list.objs[i].tok.line, list.objs[i].tok.line_offset
         1752                         );
         1753                         goto error;
         1754                 }
         1755         }
         1756         if (!theme_init) {
         1757                 ledit_debug("No theme configured; loading defaults\n");
         1758                 cfg.theme = load_theme(common, NULL, NULL, errstr);
         1759                 if (!cfg.theme)
         1760                         goto error;
         1761         }
         1762         if (!bindings_init) {
         1763                 ledit_debug("No key/command bindings configured; loading defaults\n");
         1764                 push_lang(&cfg);
         1765                 /* load default config */
         1766                 ast_list empty_list = {.objs = NULL, .len = 0, .cap = 0};
         1767                 /* shouldn't usually happen */
         1768                 if (load_bindings(&cfg, &empty_list, NULL, errstr))
         1769                         goto error;
         1770         }
         1771         if (!mappings_init) {
         1772                 ledit_debug("No key/command mappings configured; loading defaults\n");
         1773                 for (size_t i = 0; i < LENGTH(mappings_default); i++) {
         1774                         append_mapping(&cfg);
         1775                         size_t cur_lang = cfg.num_langs - 1;
         1776                         cfg.langs[cur_lang] = ledit_strdup(mappings_default[i].lang);
         1777                         basic_key_array *bkmap = &cfg.basic_keys[cur_lang];
         1778                         command_key_array *ckmap = &cfg.command_keys[cur_lang];
         1779                         command_array *cmap = &cfg.cmds[cur_lang];
         1780                         /* FIXME: any way to speed this up? I guess once the keys can be binary-searched... */
         1781                         /* FIXME: duplicated code from above */
         1782                         for (size_t j = 0; j < mappings_default[i].keys_len; j++) {
         1783                                 for (size_t k = 0; k < bkmap->num_keys; k++) {
         1784                                         if (bkmap->keys[k].text && !strcmp(bkmap->keys[k].text, mappings_default[i].keys[j].to)) {
         1785                                                 free(bkmap->keys[k].text);
         1786                                                 bkmap->keys[k].text = ledit_strdup(mappings_default[i].keys[j].from);
         1787                                         }
         1788                                 }
         1789                                 for (size_t k = 0; k < ckmap->num_keys; k++) {
         1790                                         if (ckmap->keys[k].text && !strcmp(ckmap->keys[k].text, mappings_default[i].keys[j].to)) {
         1791                                                 free(ckmap->keys[k].text);
         1792                                                 ckmap->keys[k].text = ledit_strdup(mappings_default[i].keys[j].from);
         1793                                         }
         1794                                 }
         1795                         }
         1796                         for (size_t j = 0; j < mappings_default[i].cmds_len; j++) {
         1797                                 for (size_t k = 0; k < cmap->num_cmds; k++) {
         1798                                         if (!strcmp(cmap->cmds[k].text, mappings_default[i].keys[j].to)) {
         1799                                                 free(cmap->cmds[k].text);
         1800                                                 cmap->cmds[k].text = ledit_strdup(mappings_default[i].keys[j].from);
         1801                                         }
         1802                                 }
         1803                         }
         1804                 }
         1805         }
         1806         destroy_list(&list);
         1807         free(file_contents);
         1808         config_destroy(common, &config);
         1809         config = cfg;
         1810         #ifdef LEDIT_DEBUG
         1811         clock_gettime(CLOCK_MONOTONIC, &now);
         1812         ledit_timespecsub(&now, &last, &elapsed);
         1813         ledit_debug_fmt(
         1814             "Time to interpret config file: %lld seconds, %ld nanoseconds\n",
         1815             (long long)elapsed.tv_sec, elapsed.tv_nsec
         1816         );
         1817         #endif
         1818         return 0;
         1819 error:
         1820         destroy_list(&list);
         1821         free(file_contents);
         1822         config_destroy(common, &cfg);
         1823         return 1;
         1824 }
         1825 
         1826 ledit_theme *
         1827 config_get_theme(void) {
         1828         ledit_assert(config.theme != NULL);
         1829         return config.theme;
         1830 }
         1831 
         1832 basic_key_array *
         1833 config_get_basic_keys(size_t lang_index) {
         1834         ledit_assert(lang_index < config.num_langs);
         1835         return &config.basic_keys[lang_index];
         1836 }
         1837 
         1838 command_key_array *
         1839 config_get_command_keys(size_t lang_index) {
         1840         ledit_assert(lang_index < config.num_langs);
         1841         return &config.command_keys[lang_index];
         1842 }
         1843 
         1844 command_array *
         1845 config_get_commands(size_t lang_index) {
         1846         ledit_assert(lang_index < config.num_langs);
         1847         return &config.cmds[lang_index];
         1848 }
         1849 
         1850 int
         1851 config_get_language_index(char *lang, size_t *idx_ret) {
         1852         for (size_t i = 0; i < config.num_langs; i++) {
         1853                 if (!strcmp(lang, config.langs[i])) {
         1854                         *idx_ret = i;
         1855                         return 0;
         1856                 }
         1857         }
         1858         return 1;
         1859 }
         1860 
         1861 char *
         1862 config_get_language_string(size_t lang_index) {
         1863         if (lang_index >= config.num_langs)
         1864                 return NULL;
         1865         return config.langs[lang_index];
         1866 }