/* Search forwards or backwards for anything matching the regexp in the text delimited by BINDING. The search is forwards if BINDING->start is greater than BINDING->end. If PRET is specified, it receives a copy of BINDING at the end of a succeded search. Its START and END fields contain bounds of the found string instance. */ long regexp_search (char *regexp, SEARCH_BINDING *binding, long length, SEARCH_BINDING *pret) { static char *previous_regexp = NULL; static char *previous_content = NULL; static int was_insensitive = 0; static regex_t preg; static regmatch_t *matches; static int match_alloc = 0; static int match_count = 0; regoff_t pos; if (previous_regexp == NULL || ((binding->flags & S_FoldCase) != was_insensitive) || (strcmp (previous_regexp, regexp) != 0)) { /* need to compile a new regexp */ int result; char *unescaped_regexp; char *p, *q; previous_content = NULL; if (previous_regexp != NULL) { free (previous_regexp); previous_regexp = NULL; regfree (&preg); } was_insensitive = binding->flags & S_FoldCase; /* expand the \n and \t in regexp */ unescaped_regexp = xmalloc (1 + strlen (regexp)); for (p = regexp, q = unescaped_regexp; *p != '\0'; p++, q++) { if (*p == '\\') switch(*++p) { case 'n': *q = '\n'; break; case 't': *q = '\t'; break; case '\0': *q = '\\'; p--; break; default: *q++ = '\\'; *q = *p; break; } else *q = *p; } *q = '\0'; result = regcomp (&preg, unescaped_regexp, REG_EXTENDED| REG_NEWLINE| (was_insensitive ? REG_ICASE : 0)); free (unescaped_regexp); if (result != 0) { int size = regerror (result, &preg, NULL, 0); char *buf = xmalloc (size); regerror (result, &preg, buf, size); info_error (_("regexp error: %s"), buf, NULL); return -1; } previous_regexp = xstrdup(regexp); } if (previous_content != binding->buffer) { /* new buffer to search in, let's scan it */ regoff_t start = 0; char saved_char; previous_content = binding->buffer; saved_char = previous_content[length-1]; previous_content[length-1] = '\0'; for (match_count = 0; start < length; ) { int result = 0; if (match_count >= match_alloc) { /* match list full. Initially allocate 256 entries, then double every time we fill it */ match_alloc = (match_alloc > 0 ? match_alloc * 2 : 256); matches = xrealloc (matches, match_alloc * sizeof(regmatch_t)); } result = regexec (&preg, &previous_content[start], 1, &matches[match_count], 0); if (result == 0) { if (matches[match_count].rm_eo == 0) { /* ignore empty matches */ start++; } else { matches[match_count].rm_so += start; matches[match_count].rm_eo += start; start = matches[match_count++].rm_eo; } } else { break; } } previous_content[length-1] = saved_char; } pos = binding->start; if (pos > binding->end) { /* searching backward */ int i; for (i = match_count - 1; i >= 0; i--) { if (matches[i].rm_so <= pos) { if (pret) { pret->buffer = binding->buffer; pret->flags = binding->flags; pret->start = matches[i].rm_so; pret->end = matches[i].rm_eo; } return matches[i].rm_so; } } } else { /* searching forward */ int i; for (i = 0; i < match_count; i++) { if (matches[i].rm_so >= pos) { if (pret) { pret->buffer = binding->buffer; pret->flags = binding->flags; pret->start = matches[i].rm_so; pret->end = matches[i].rm_eo; } if (binding->flags & S_SkipDest) return matches[i].rm_eo; else return matches[i].rm_so; } } } /* not found */ return -1; }
/* Read the init file. Return true if no error was encountered. Set SUPPRESS_INFO or SUPPRESS_EA to true if the init file specified to ignore default key bindings. */ int compile (FILE *fp, const char *filename, int *suppress_info, int *suppress_ea) { int error = 0; /* Set if there was a fatal error in reading init file. */ char rescan = 0; /* Whether to reuse the same character when moving onto the next state. */ unsigned int lnum = 0; int c = 0; /* This parser is a true state machine, with no sneaky fetching of input characters inside the main loop. In other words, all state is fully represented by the following variables: */ enum { start_of_line, start_of_comment, in_line_comment, in_trailing_comment, get_keyseq, got_keyseq, get_action, got_action, get_varname, got_varname, get_equals, got_equals, get_value } state = start_of_line; enum sect_e section = info; enum { normal, slosh, control, octal, special_key } seqstate = normal; /* used if state == get_keyseq */ char meta = 0; char ocnt = 0; /* used if state == get_keyseq && seqstate == octal */ /* Data is accumulated in the following variables. The code avoids overflowing these strings, and throws an error where appropriate if a string limit is exceeded. These string lengths are arbitrary (and should be large enough) and their lengths are not hard-coded anywhere else, so increasing them here will not break anything. */ char oval = 0; char comment[10]; unsigned int clen = 0; int seq[20]; unsigned int slen = 0; char act[80]; unsigned int alen = 0; char varn[80]; unsigned int varlen = 0; char val[80]; unsigned int vallen = 0; #define To_seq(c) \ do { \ if (slen < sizeof seq/sizeof(int)) \ seq[slen++] = meta ? KEYMAP_META(c) : (c); \ else \ { \ syntax_error(filename, lnum, \ _("key sequence too long")); \ error = 1; \ } \ meta = 0; \ } while (0) while (!error && (rescan || (c = fgetc (fp)) != EOF)) { rescan = 0; switch (state) { case start_of_line: lnum++; if (c == '#') state = start_of_comment; else if (c != '\n') { switch (section) { case info: case ea: state = get_keyseq; seqstate = normal; slen = 0; break; case var: state = get_varname; varlen = 0; break; } rescan = 1; } break; case start_of_comment: clen = 0; state = in_line_comment; /* fall through */ case in_line_comment: if (c == '\n') { state = start_of_line; comment[clen] = '\0'; if (strcmp (comment, "info") == 0) section = info; else if (strcmp (comment, "echo-area") == 0) section = ea; else if (strcmp (comment, "var") == 0) section = var; else if (strcmp (comment, "stop") == 0 && (section == info || section == ea)) { if (section == info) *suppress_info = 1; else *suppress_ea = 1; } } else if (clen < sizeof comment - 1) comment[clen++] = c; break; case in_trailing_comment: if (c == '\n') state = start_of_line; break; case get_keyseq: switch (seqstate) { case normal: if (c == '\n' || isspace (c)) { state = got_keyseq; rescan = 1; if (slen == 0) { syntax_error (filename, lnum, _("missing key sequence")); error = 1; } } else if (c == '\\') seqstate = slosh; else if (c == '^') seqstate = control; else To_seq (c); break; case slosh: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': seqstate = octal; oval = c - '0'; ocnt = 1; break; case 'b': To_seq ('\b'); seqstate = normal; break; case 'e': To_seq ('\033'); seqstate = normal; break; case 'n': To_seq ('\n'); seqstate = normal; break; case 'r': To_seq ('\r'); seqstate = normal; break; case 't': To_seq ('\t'); seqstate = normal; break; case 'm': meta = 1; seqstate = normal; break; case 'k': seqstate = special_key; break; default: /* Backslash followed by any other char just means that char. */ To_seq (c); seqstate = normal; break; } break; case octal: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': if (++ocnt <= 3) oval = oval * 8 + c - '0'; if (ocnt == 3) seqstate = normal; break; default: ocnt = 4; seqstate = normal; rescan = 1; break; } if (seqstate != octal) { if (oval) To_seq (oval); else { syntax_error (filename, lnum, _("NUL character (\\000) not permitted")); error = 1; } } break; case special_key: switch (c) { case 'u': To_seq (KEY_UP_ARROW); break; case 'd': To_seq (KEY_DOWN_ARROW); break; case 'r': To_seq (KEY_RIGHT_ARROW); break; case 'l': To_seq (KEY_LEFT_ARROW); break; case 'U': To_seq (KEY_PAGE_UP); break; case 'D': To_seq (KEY_PAGE_DOWN); break; case 'h': To_seq (KEY_HOME); break; case 'e': To_seq (KEY_END); break; case 'x': To_seq (KEY_DELETE); break; default: To_seq (c); rescan = 1; break; } seqstate = normal; break; case control: if (CONTROL (c)) To_seq (CONTROL (c)); else { syntax_error (filename, lnum, _("NUL character (^%c) not permitted"), c); error = 1; } seqstate = normal; break; } break; case got_keyseq: if (isspace (c) && c != '\n') break; state = get_action; alen = 0; /* fall through */ case get_action: if (c == '\n' || isspace (c)) { int a; state = got_action; rescan = 1; if (alen == 0) { syntax_error (filename, lnum, _("missing action name")); error = 1; } else { act[alen] = '\0'; a = lookup_action (act); if (a == A_info_menu_digit) { /* It does not make sense for menu-digit to be anything other than '0' .. '9'. */ syntax_error (filename, lnum, _("cannot bind key sequence to menu-digit")); } else if (a == -1) { /* Print an error message, but keep going (don't set error = 1) for compatibility with infokey files aimed at future versions which may have different actions. */ syntax_error (filename, lnum, _("unknown action `%s'"), act); } else { int keymap_bind_keyseq (Keymap, int *, KEYMAP_ENTRY *); KEYMAP_ENTRY ke; ke.type = ISFUNC; ke.value.function = &function_doc_array[a]; To_seq (0); if (section == info) keymap_bind_keyseq (info_keymap, seq, &ke); else /* section == ea */ keymap_bind_keyseq (echo_area_keymap, seq, &ke); } } } else if (alen < sizeof act - 1) act[alen++] = c; else { syntax_error (filename, lnum, _("action name too long")); error = 1; } break; case got_action: if (c == '#') state = in_trailing_comment; else if (c == '\n') state = start_of_line; else if (!isspace (c)) { syntax_error (filename, lnum, _("extra characters following action `%s'"), act); error = 1; } break; case get_varname: if (c == '=') { if (varlen == 0) { syntax_error (filename, lnum, _("missing variable name")); error = 1; } state = get_value; vallen = 0; } else if (c == '\n' || isspace (c)) { syntax_error (filename, lnum, _("missing `=' immediately after variable name")); error = 1; } else if (varlen < sizeof varn - 1) varn[varlen++] = c; else { syntax_error (filename, lnum, _("variable name too long")); error = 1; } break; case get_value: if (c == '\n') { VARIABLE_ALIST *v; state = start_of_line; varn[varlen] = '\0'; val[vallen] = '\0'; v = variable_by_name (varn); if (!v) info_error (_("%s: no such variable"), varn); else if (!set_variable_to_value (v, val, SET_IN_CONFIG_FILE)) info_error (_("value %s is not valid for variable %s"), val, varn); } else if (vallen < sizeof val - 1) val[vallen++] = c; else { syntax_error (filename, lnum, _("value too long")); error = 1; } break; case get_equals: case got_equals: case got_varname: break; } } #undef To_seq return !error; }