1 /* infokey.c -- read ~/.infokey
2
3 Copyright 1999-2019 Free Software Foundation, Inc.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 Originally written by Andrew Bettison. */
19
20 #include "info.h"
21 #include "doc.h"
22 #include "session.h"
23 #include "funs.h"
24 #include "getopt.h"
25 #include "variables.h"
26
27 extern char *program_name; /* in info.c */
28
29 enum sect_e
30 {
31 info = 0,
32 ea = 1,
33 var = 2
34 };
35
36 static void syntax_error (const char *filename, unsigned int linenum,
37 const char *fmt, ...) TEXINFO_PRINTFLIKE(3,4);
38
39 /* Compilation - the real work.
40
41 Source file syntax
42 ------------------
43 The source file is a line-based text file with the following
44 structure:
45
46 # comments
47 # more comments
48
49 #info
50 u prev-line
51 d next-line
52 ^a invalid # just beep
53 \ku prev-line
54 #stop
55 \kd next-line
56 q quit # of course!
57
58 #echo-area
59 ^a echo-area-beg-of-line
60 ^e echo-area-end-of-line
61 \kr echo-area-forward
62 \kl echo-area-backward
63 \kh echo-area-beg-of-line
64 \ke echo-area-end-of-line
65
66 #var
67 scroll-step=1
68 ISO-Latin=Off
69
70 Lines starting with '#' are comments, and are ignored. Blank
71 lines are ignored. Each section is introduced by one of the
72 following lines:
73
74 #info
75 #echo-area
76 #var
77
78 The sections may occur in any order. Each section may be
79 omitted completely. If the 'info' section is the first in the
80 file, its '#info' line may be omitted.
81
82 The 'info' and 'echo-area' sections
83 -----------------------------------
84 Each line in the 'info' or 'echo-area' sections has the
85 following syntax:
86
87 key-sequence SPACE action-name [ SPACE [ # comment ] ] \n
88
89 Where SPACE is one or more white space characters excluding
90 newline, "action-name" is the name of a GNU Info command,
91 "comment" is any sequence of characters excluding newline, and
92 "key-sequence" is a concatenation of one or more key definitions
93 using the following syntax:
94
95 1. A carat ^ followed by one character indicates a single
96 control character;
97
98 2. A backslash \ followed by one, two, or three octal
99 digits indicates a single character having that ASCII
100 code;
101
102 3. \n indicates a single NEWLINE;
103 \e indicates a single ESC;
104 \r indicates a single CR;
105 \t indicates a single TAB;
106 \b indicates a single BACKSPACE;
107
108 4. \ku indicates the Up Arrow key;
109 \kd indicates the Down Arrow key;
110 \kl indicates the Left Arrow key;
111 \kr indicates the Right Arrow key;
112 \kP indicates the Page Up (PRIOR) key;
113 \kN indicates the Page Down (NEXT) key;
114 \kh indicates the Home key;
115 \ke indicates the End key;
116 \kx indicates the DEL key;
117 \k followed by any other character indicates a single
118 control-K, and the following character is interpreted
119 as in rules 1, 2, 3, 5 and 6.
120
121 5. \m followed by any sequence defined in rules 1, 2, 3, 4
122 or 6 indicates the "Meta" modification of that key.
123
124 6. A backslash \ followed by any character not described
125 above indicates that character itself. In particular:
126 \\ indicates a single backslash \,
127 \ (backslash-space) indicates a single space,
128 \^ indicates a single caret ^,
129
130 If the following line:
131
132 #stop
133
134 occurs anywhere in an 'info' or 'echo-area' section, that
135 indicates to GNU Info to suppress all of its default key
136 bindings in that context.
137
138 The 'var' section
139 -----------------
140 Each line in the 'var' section has the following syntax:
141
142 variable-name = value \n
143
144 Where "variable-name" is the name of a GNU Info variable and
145 "value" is the value that GNU Info will assign to that variable
146 when commencing execution. There must be no white space in the
147 variable name, nor between the variable name and the '='. All
148 characters immediately following the '=', up to but not
149 including the terminating newline, are considered to be the
150 value that will be assigned. In other words, white space
151 following the '=' is not ignored.
152 */
153
154 static int lookup_action (const char *actname);
155
156 /* Read the init file. Return true if no error was encountered. Set
157 SUPPRESS_INFO or SUPPRESS_EA to true if the init file specified to ignore
158 default key bindings. */
159 int
compile(FILE * fp,const char * filename,int * suppress_info,int * suppress_ea)160 compile (FILE *fp, const char *filename, int *suppress_info, int *suppress_ea)
161 {
162 int error = 0; /* Set if there was a fatal error in reading init file. */
163 char rescan = 0; /* Whether to reuse the same character when moving onto the
164 next state. */
165 unsigned int lnum = 0;
166 int c = 0;
167
168 /* This parser is a true state machine, with no sneaky fetching
169 of input characters inside the main loop. In other words, all
170 state is fully represented by the following variables:
171 */
172 enum
173 {
174 start_of_line,
175 start_of_comment,
176 in_line_comment,
177 in_trailing_comment,
178 get_keyseq,
179 got_keyseq,
180 get_action,
181 got_action,
182 get_varname,
183 got_varname,
184 get_equals,
185 got_equals,
186 get_value
187 }
188 state = start_of_line;
189 enum sect_e section = info;
190 enum
191 {
192 normal,
193 slosh,
194 control,
195 octal,
196 special_key
197 }
198 seqstate = normal; /* used if state == get_keyseq */
199 char meta = 0;
200 char ocnt = 0; /* used if state == get_keyseq && seqstate == octal */
201
202 /* Data is accumulated in the following variables. The code
203 avoids overflowing these strings, and throws an error
204 where appropriate if a string limit is exceeded. These string
205 lengths are arbitrary (and should be large enough) and their
206 lengths are not hard-coded anywhere else, so increasing them
207 here will not break anything. */
208 char oval = 0;
209 char comment[10];
210 unsigned int clen = 0;
211 int seq[20];
212 unsigned int slen = 0;
213 char act[80];
214 unsigned int alen = 0;
215 char varn[80];
216 unsigned int varlen = 0;
217 char val[80];
218 unsigned int vallen = 0;
219
220 #define To_seq(c) \
221 do { \
222 if (slen < sizeof seq/sizeof(int)) \
223 seq[slen++] = meta ? KEYMAP_META(c) : (c); \
224 else \
225 { \
226 syntax_error(filename, lnum, \
227 _("key sequence too long")); \
228 error = 1; \
229 } \
230 meta = 0; \
231 } while (0)
232
233 while (!error && (rescan || (c = fgetc (fp)) != EOF))
234 {
235 rescan = 0;
236 switch (state)
237 {
238 case start_of_line:
239 lnum++;
240 if (c == '#')
241 state = start_of_comment;
242 else if (c != '\n')
243 {
244 switch (section)
245 {
246 case info:
247 case ea:
248 state = get_keyseq;
249 seqstate = normal;
250 slen = 0;
251 break;
252 case var:
253 state = get_varname;
254 varlen = 0;
255 break;
256 }
257 rescan = 1;
258 }
259 break;
260
261 case start_of_comment:
262 clen = 0;
263 state = in_line_comment;
264 /* fall through */
265 case in_line_comment:
266 if (c == '\n')
267 {
268 state = start_of_line;
269 comment[clen] = '\0';
270 if (strcmp (comment, "info") == 0)
271 section = info;
272 else if (strcmp (comment, "echo-area") == 0)
273 section = ea;
274 else if (strcmp (comment, "var") == 0)
275 section = var;
276 else if (strcmp (comment, "stop") == 0
277 && (section == info || section == ea))
278 {
279 if (section == info)
280 *suppress_info = 1;
281 else
282 *suppress_ea = 1;
283 }
284 }
285 else if (clen < sizeof comment - 1)
286 comment[clen++] = c;
287 break;
288
289 case in_trailing_comment:
290 if (c == '\n')
291 state = start_of_line;
292 break;
293
294 case get_keyseq:
295 switch (seqstate)
296 {
297 case normal:
298 if (c == '\n' || isspace (c))
299 {
300 state = got_keyseq;
301 rescan = 1;
302 if (slen == 0)
303 {
304 syntax_error (filename, lnum, _("missing key sequence"));
305 error = 1;
306 }
307 }
308 else if (c == '\\')
309 seqstate = slosh;
310 else if (c == '^')
311 seqstate = control;
312 else
313 To_seq (c);
314 break;
315
316 case slosh:
317 switch (c)
318 {
319 case '0': case '1': case '2': case '3':
320 case '4': case '5': case '6': case '7':
321 seqstate = octal;
322 oval = c - '0';
323 ocnt = 1;
324 break;
325 case 'b':
326 To_seq ('\b');
327 seqstate = normal;
328 break;
329 case 'e':
330 To_seq ('\033');
331 seqstate = normal;
332 break;
333 case 'n':
334 To_seq ('\n');
335 seqstate = normal;
336 break;
337 case 'r':
338 To_seq ('\r');
339 seqstate = normal;
340 break;
341 case 't':
342 To_seq ('\t');
343 seqstate = normal;
344 break;
345 case 'm':
346 meta = 1;
347 seqstate = normal;
348 break;
349 case 'k':
350 seqstate = special_key;
351 break;
352 default:
353 /* Backslash followed by any other char
354 just means that char. */
355 To_seq (c);
356 seqstate = normal;
357 break;
358 }
359 break;
360
361 case octal:
362 switch (c)
363 {
364 case '0': case '1': case '2': case '3':
365 case '4': case '5': case '6': case '7':
366 if (++ocnt <= 3)
367 oval = oval * 8 + c - '0';
368 if (ocnt == 3)
369 seqstate = normal;
370 break;
371 default:
372 ocnt = 4;
373 seqstate = normal;
374 rescan = 1;
375 break;
376 }
377 if (seqstate != octal)
378 {
379 if (oval)
380 To_seq (oval);
381 else
382 {
383 syntax_error (filename, lnum,
384 _("NUL character (\\000) not permitted"));
385 error = 1;
386 }
387 }
388 break;
389
390 case special_key:
391 switch (c)
392 {
393 case 'u': To_seq (KEY_UP_ARROW); break;
394 case 'd': To_seq (KEY_DOWN_ARROW); break;
395 case 'r': To_seq (KEY_RIGHT_ARROW); break;
396 case 'l': To_seq (KEY_LEFT_ARROW); break;
397 case 'U': To_seq (KEY_PAGE_UP); break;
398 case 'D': To_seq (KEY_PAGE_DOWN); break;
399 case 'h': To_seq (KEY_HOME); break;
400 case 'e': To_seq (KEY_END); break;
401 case 'x': To_seq (KEY_DELETE); break;
402 default: To_seq (c); rescan = 1; break;
403 }
404 seqstate = normal;
405 break;
406
407 case control:
408 if (CONTROL (c))
409 To_seq (CONTROL (c));
410 else
411 {
412 syntax_error (filename, lnum,
413 _("NUL character (^%c) not permitted"), c);
414 error = 1;
415 }
416 seqstate = normal;
417 break;
418 }
419 break;
420
421 case got_keyseq:
422 if (isspace (c) && c != '\n')
423 break;
424 state = get_action;
425 alen = 0;
426 /* fall through */
427 case get_action:
428 if (c == '\n' || isspace (c))
429 {
430 int a;
431
432 state = got_action;
433 rescan = 1;
434 if (alen == 0)
435 {
436 syntax_error (filename, lnum, _("missing action name"));
437 error = 1;
438 }
439 else
440 {
441 int keymap_bind_keyseq (Keymap, int *, KEYMAP_ENTRY *);
442
443 act[alen] = '\0';
444 a = lookup_action (act);
445 if (a == A_info_menu_digit)
446 {
447 /* Only allow "1 menu-digit". (This is useful if
448 this default binding is disabled with "#stop".)
449 E.g. do not allow "b menu-digit". */
450 if (seq[0] != '1' || seq[1] != '\0'
451 || section != info)
452 {
453 syntax_error (filename, lnum,
454 _("cannot bind key sequence to menu-digit"));
455 }
456 else
457 {
458 /* Bind each key from '1' to '9' to 'menu-digit'. */
459 KEYMAP_ENTRY ke;
460 int i;
461
462 ke.type = ISFUNC;
463 ke.value.function = &function_doc_array[a];
464
465 for (i = '1'; i <= '9'; i++)
466 {
467 seq[0] = i;
468 keymap_bind_keyseq (info_keymap, seq, &ke);
469 }
470 }
471 }
472 else if (a == -1)
473 {
474 /* Print an error message, but keep going (don't set
475 error = 1) for compatibility with infokey files aimed
476 at future versions which may have different
477 actions. */
478 syntax_error (filename, lnum, _("unknown action `%s'"),
479 act);
480 }
481 else
482 {
483 KEYMAP_ENTRY ke;
484 static InfoCommand invalid_function = { 0 };
485
486 ke.type = ISFUNC;
487 ke.value.function = a != A_INVALID
488 ? &function_doc_array[a]
489 : &invalid_function;
490 To_seq (0);
491
492 if (section == info)
493 keymap_bind_keyseq (info_keymap, seq, &ke);
494 else /* section == ea */
495 keymap_bind_keyseq (echo_area_keymap, seq, &ke);
496 }
497 }
498 }
499 else if (alen < sizeof act - 1)
500 act[alen++] = c;
501 else
502 {
503 syntax_error (filename, lnum, _("action name too long"));
504 error = 1;
505 }
506 break;
507
508 case got_action:
509 if (c == '#')
510 state = in_trailing_comment;
511 else if (c == '\n')
512 state = start_of_line;
513 else if (!isspace (c))
514 {
515 syntax_error (filename, lnum,
516 _("extra characters following action `%s'"),
517 act);
518 error = 1;
519 }
520 break;
521
522 case get_varname:
523 if (c == '=')
524 {
525 if (varlen == 0)
526 {
527 syntax_error (filename, lnum, _("missing variable name"));
528 error = 1;
529 }
530 state = get_value;
531 vallen = 0;
532 }
533 else if (c == '\n' || isspace (c))
534 {
535 syntax_error (filename, lnum,
536 _("missing `=' immediately after variable name"));
537 error = 1;
538 }
539 else if (varlen < sizeof varn - 1)
540 varn[varlen++] = c;
541 else
542 {
543 syntax_error (filename, lnum, _("variable name too long"));
544 error = 1;
545 }
546 break;
547
548 case get_value:
549 if (c == '\n')
550 {
551 VARIABLE_ALIST *v;
552
553 state = start_of_line;
554 varn[varlen] = '\0';
555 val[vallen] = '\0';
556 v = variable_by_name (varn);
557 if (!v)
558 info_error (_("%s: no such variable"), varn);
559 else if (!set_variable_to_value (v, val, SET_IN_CONFIG_FILE))
560 info_error (_("value %s is not valid for variable %s"),
561 val, varn);
562 }
563 else if (vallen < sizeof val - 1)
564 val[vallen++] = c;
565 else
566 {
567 syntax_error (filename, lnum, _("value too long"));
568 error = 1;
569 }
570 break;
571
572 case get_equals:
573 case got_equals:
574 case got_varname:
575 break;
576 }
577 }
578
579 #undef To_seq
580
581 return !error;
582 }
583
584 /* Return the numeric code of an Info command given its name. If not found,
585 return -1. This uses the auto-generated array in doc.c. */
586 static int
lookup_action(const char * name)587 lookup_action (const char *name)
588 {
589 int i;
590
591 if (!strcmp (name, "invalid"))
592 return A_INVALID;
593 for (i = 0; function_doc_array[i].func_name; i++)
594 if (!strcmp (function_doc_array[i].func_name, name))
595 return i;
596 return -1;
597 }
598
599
600
601 /* Error handling. */
602
603 /* Give the user a generic error message in the form
604 progname: message
605 */
606 static void
syntax_error(const char * filename,unsigned int linenum,const char * fmt,...)607 syntax_error (const char *filename,
608 unsigned int linenum, const char *fmt, ...)
609 {
610 va_list ap;
611
612 fprintf (stderr, "%s: ", program_name);
613 fprintf (stderr, _("\"%s\", line %u: "), filename, linenum);
614 va_start(ap, fmt);
615 vfprintf (stderr, fmt, ap);
616 va_end(ap);
617 fprintf (stderr, "\n");
618 }
619
620
621