(root)/
texinfo-7.1/
info/
infokey.c
       1  /* infokey.c -- read ~/.infokey
       2  
       3     Copyright 1999-2023 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
     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
     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
     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