193139Sru/* infokey.c -- compile ~/.infokey to ~/.info.
2146515Sru   $Id: infokey.c,v 1.9 2004/12/14 00:15:36 karl Exp $
393139Sru
4146515Sru   Copyright (C) 1999, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
593139Sru
693139Sru   This program is free software; you can redistribute it and/or modify
793139Sru   it under the terms of the GNU General Public License as published by
893139Sru   the Free Software Foundation; either version 2, or (at your option)
993139Sru   any later version.
1093139Sru
1193139Sru   This program is distributed in the hope that it will be useful,
1293139Sru   but WITHOUT ANY WARRANTY; without even the implied warranty of
1393139Sru   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1493139Sru   GNU General Public License for more details.
1593139Sru
1693139Sru   You should have received a copy of the GNU General Public License
1793139Sru   along with this program; if not, write to the Free Software
1893139Sru   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
1993139Sru
2093139Sru   Written by Andrew Bettison <andrewb@zip.com.au>. */
2193139Sru
2293139Sru#include "info.h"
2393139Sru#include "infomap.h"
2493139Sru#include "infokey.h"
2593139Sru#include "key.h"
2693139Sru#include "getopt.h"
2793139Sru
2893139Srustatic char *program_name = "infokey";
2993139Sru
3093139Sru/* Non-zero means print version info only. */
3193139Srustatic int print_version_p = 0;
3293139Sru
3393139Sru/* Non-zero means print a short description of the options. */
3493139Srustatic int print_help_p = 0;
3593139Sru
3693139Sru/* String specifying the source file.  This is set by the user on the
3793139Sru   command line, or a default is used. */
3893139Srustatic char *input_filename = (char *) NULL;
3993139Sru
4093139Sru/* String specifying the name of the file to output to.  This is
4193139Sru   set by the user on the command line, or a default is used. */
4293139Srustatic char *output_filename = (char *) NULL;
4393139Sru
4493139Sru/* Structure describing the options that Infokey accepts.  We pass this
4593139Sru   structure to getopt_long ().  If you add or otherwise change this
4693139Sru   structure, you must also change the string which follows it. */
4793139Srustatic struct option long_options[] =
4893139Sru{
4993139Sru  {"output", 1, 0, 'o'},
5093139Sru  {"help", 0, &print_help_p, 1},
5193139Sru  {"version", 0, &print_version_p, 1},
5293139Sru  {NULL, 0, NULL, 0}
5393139Sru};
5493139Sru
5593139Sru/* String describing the shorthand versions of the long options found above. */
5693139Srustatic char *short_options = "o:";
5793139Sru
5893139Sru/* Structure for holding the compiled sections. */
5993139Sruenum sect_e
6093139Sru  {
6193139Sru    info = 0,
6293139Sru    ea = 1,
63100513Sru    var = 2
6493139Sru  };
6593139Srustruct sect
6693139Sru  {
6793139Sru    unsigned int cur;
6893139Sru    unsigned char data[INFOKEY_MAX_SECTIONLEN];
6993139Sru  };
7093139Sru
7193139Sru/* Some "forward" declarations. */
72146515Srustatic char *mkpath (const char *dir, const char *file);
73146515Srustatic int compile (FILE *fp, const char *filename, struct sect *sections);
74146515Srustatic int write_infokey_file (FILE *fp, struct sect *sections);
75146515Srustatic void syntax_error (const char *filename,
76146515Sru    unsigned int linenum, const char *fmt,
77146515Sru    const void *a1, const void *a2, const void *a3, const void *a4);
78146515Srustatic void error_message (int error_code, const char *fmt,
79146515Sru    const void *a1, const void *a2, const void *a3, const void *a4);
80146515Srustatic void suggest_help (void);
81146515Srustatic void short_help (void);
8293139Sru
8393139Sru
8493139Sru/* **************************************************************** */
8593139Sru/*                                                                  */
8693139Sru/*             Main Entry Point to the Infokey Program              */
8793139Sru/*                                                                  */
8893139Sru/* **************************************************************** */
8993139Sru
9093139Sruint
91146515Srumain (int argc, char **argv)
9293139Sru{
9393139Sru  int getopt_long_index;	/* Index returned by getopt_long (). */
9493139Sru
9593139Sru#ifdef HAVE_SETLOCALE
9693139Sru  /* Set locale via LC_ALL.  */
9793139Sru  setlocale (LC_ALL, "");
9893139Sru#endif
9993139Sru
100146515Sru#ifdef ENABLE_NLS
10193139Sru  /* Set the text message domain.  */
10293139Sru  bindtextdomain (PACKAGE, LOCALEDIR);
10393139Sru  textdomain (PACKAGE);
104146515Sru#endif
10593139Sru
10693139Sru  while (1)
10793139Sru    {
10893139Sru      int option_character;
10993139Sru
11093139Sru      option_character = getopt_long
11193139Sru	(argc, argv, short_options, long_options, &getopt_long_index);
11293139Sru
11393139Sru      /* getopt_long () returns EOF when there are no more long options. */
11493139Sru      if (option_character == EOF)
11593139Sru	break;
11693139Sru
11793139Sru      /* If this is a long option, then get the short version of it. */
11893139Sru      if (option_character == 0 && long_options[getopt_long_index].flag == 0)
11993139Sru	option_character = long_options[getopt_long_index].val;
12093139Sru
12193139Sru      /* Case on the option that we have received. */
12293139Sru      switch (option_character)
12393139Sru	{
12493139Sru	case 0:
12593139Sru	  break;
12693139Sru
12793139Sru	  /* User is specifying the name of a file to output to. */
12893139Sru	case 'o':
12993139Sru	  if (output_filename)
13093139Sru	    free (output_filename);
13193139Sru	  output_filename = xstrdup (optarg);
13293139Sru	  break;
13393139Sru
13493139Sru	default:
13593139Sru	  suggest_help ();
13693139Sru	  xexit (1);
13793139Sru	}
13893139Sru    }
13993139Sru
14093139Sru  /* If the user specified --version, then show the version and exit. */
14193139Sru  if (print_version_p)
14293139Sru    {
14393139Sru      printf ("%s (GNU %s) %s\n", program_name, PACKAGE, VERSION);
14493139Sru      puts ("");
14593139Sru      printf (_ ("Copyright (C) %s Free Software Foundation, Inc.\n\
14693139SruThere is NO warranty.  You may redistribute this software\n\
14793139Sruunder the terms of the GNU General Public License.\n\
14893139SruFor more information about these matters, see the files named COPYING.\n"),
149114472Sru	      "2003");
15093139Sru      xexit (0);
15193139Sru    }
15293139Sru
15393139Sru  /* If the `--help' option was present, show the help and exit. */
15493139Sru  if (print_help_p)
15593139Sru    {
15693139Sru      short_help ();
15793139Sru      xexit (0);
15893139Sru    }
15993139Sru
16093139Sru  /* If there is one argument remaining, it is the name of the input
16193139Sru     file. */
16293139Sru  if (optind == argc - 1)
16393139Sru    {
16493139Sru      if (input_filename)
16593139Sru	free (input_filename);
16693139Sru      input_filename = xstrdup (argv[optind]);
16793139Sru    }
16893139Sru  else if (optind != argc)
16993139Sru    {
170146515Sru      error_message (0, _("incorrect number of arguments"),
171146515Sru          NULL, NULL, NULL, NULL);
17293139Sru      suggest_help ();
17393139Sru      xexit (1);
17493139Sru    }
17593139Sru
17693139Sru  /* Use default filenames where none given. */
17793139Sru  {
17893139Sru    char *homedir;
17993139Sru
18093139Sru    homedir = getenv ("HOME");
18193139Sru#ifdef __MSDOS__
18293139Sru    if (!homedir)
18393139Sru      homedir = ".";
18493139Sru#endif
18593139Sru    if (!input_filename)
18693139Sru      input_filename = mkpath (homedir, INFOKEY_SRCFILE);
18793139Sru    if (!output_filename)
18893139Sru      output_filename = mkpath (homedir, INFOKEY_FILE);
18993139Sru  }
19093139Sru
19193139Sru  {
19293139Sru    FILE *inf;
19393139Sru    FILE *outf;
19493139Sru    int write_error;
19593139Sru    static struct sect sections[3];
19693139Sru
19793139Sru    /* Open the input file. */
19893139Sru    inf = fopen (input_filename, "r");
19993139Sru    if (!inf)
20093139Sru      {
201146515Sru	error_message (errno, _("cannot open input file `%s'"),
202146515Sru            input_filename, NULL, NULL, NULL);
20393139Sru	xexit (1);
20493139Sru      }
20593139Sru
20693139Sru    /* Compile the input file to its verious sections, then write the
20793139Sru       section data to the output file. */
20893139Sru
20993139Sru    if (compile (inf, input_filename, sections))
21093139Sru      {
21193139Sru	/* Open the output file. */
21293139Sru	outf = fopen (output_filename, FOPEN_WBIN);
21393139Sru	if (!outf)
21493139Sru	  {
215146515Sru	    error_message (errno, _("cannot create output file `%s'"),
216146515Sru                output_filename, NULL, NULL, NULL);
21793139Sru	    xexit (1);
21893139Sru	  }
21993139Sru
22093139Sru	/* Write the contents of the output file and close it.  If there is
22193139Sru	   an error writing to the file, delete it and exit with a failure
22293139Sru	   status.  */
22393139Sru	write_error = 0;
22493139Sru	if (!write_infokey_file (outf, sections))
22593139Sru	  {
226146515Sru	    error_message (errno, _("error writing to `%s'"),
227146515Sru                output_filename, NULL, NULL, NULL);
22893139Sru	    write_error = 1;
22993139Sru	  }
23093139Sru	if (fclose (outf) == EOF)
23193139Sru	  {
232146515Sru	    error_message (errno, _("error closing output file `%s'"),
233146515Sru                output_filename, NULL, NULL, NULL);
23493139Sru	    write_error = 1;
23593139Sru	  }
23693139Sru	if (write_error)
23793139Sru	  {
23893139Sru	    unlink (output_filename);
23993139Sru	    xexit (1);
24093139Sru	  }
24193139Sru      }
24293139Sru
24393139Sru    /* Close the input file. */
24493139Sru    fclose (inf);
24593139Sru  }
24693139Sru
247116525Sru  return 0;
24893139Sru}
24993139Sru
25093139Srustatic char *
251146515Srumkpath (const char *dir, const char *file)
25293139Sru{
25393139Sru  char *p;
25493139Sru
25593139Sru  p = xmalloc (strlen (dir) + 1 + strlen (file) + 2);
25693139Sru  strcpy (p, dir);
25793139Sru  strcat (p, "/");
25893139Sru  strcat (p, file);
25993139Sru  return p;
26093139Sru}
26193139Sru
26293139Sru
26393139Sru/* Compilation - the real work.
26493139Sru
26593139Sru	Source file syntax
26693139Sru	------------------
26793139Sru	The source file is a line-based text file with the following
26893139Sru	structure:
26993139Sru
27093139Sru		# comments
27193139Sru		# more comments
27293139Sru
27393139Sru		#info
27493139Sru		u	prev-line
27593139Sru		d	next-line
27693139Sru		^a	invalid		# just beep
27793139Sru		\ku	prev-line
27893139Sru		#stop
27993139Sru		\kd	next-line
28093139Sru		q	quit		# of course!
28193139Sru
28293139Sru		#echo-area
28393139Sru		^a	echo-area-beg-of-line
28493139Sru		^e	echo-area-end-of-line
28593139Sru		\kr	echo-area-forward
28693139Sru		\kl	echo-area-backward
28793139Sru		\kh	echo-area-beg-of-line
28893139Sru		\ke	echo-area-end-of-line
28993139Sru
29093139Sru		#var
29193139Sru		scroll-step=1
29293139Sru		ISO-Latin=Off
29393139Sru
29493139Sru	Lines starting with '#' are comments, and are ignored.  Blank
29593139Sru	lines are ignored.  Each section is introduced by one of the
29693139Sru	following lines:
29793139Sru
29893139Sru		#info
29993139Sru		#echo-area
30093139Sru		#var
301116525Sru
30293139Sru	The sections may occur in any order.  Each section may be
30393139Sru	omitted completely.  If the 'info' section is the first in the
30493139Sru	file, its '#info' line may be omitted.
305116525Sru
30693139Sru	The 'info' and 'echo-area' sections
30793139Sru	-----------------------------------
30893139Sru	Each line in the 'info' or 'echo-area' sections has the
30993139Sru	following syntax:
31093139Sru
31193139Sru		key-sequence SPACE action-name [ SPACE [ # comment ] ] \n
312116525Sru
31393139Sru	Where SPACE is one or more white space characters excluding
31493139Sru	newline, "action-name" is the name of a GNU Info command,
31593139Sru	"comment" is any sequence of characters excluding newline, and
31693139Sru	"key-sequence" is a concatenation of one or more key definitions
31793139Sru	using the following syntax:
31893139Sru
31993139Sru	   1.	A carat ^ followed by one character indicates a single
32093139Sru	   	control character;
32193139Sru
32293139Sru	   2.	A backslash \ followed by one, two, or three octal
32393139Sru		digits indicates a single character having that ASCII
32493139Sru		code;
32593139Sru
32693139Sru	   3.	\n indicates a single NEWLINE;
32793139Sru		\e indicates a single ESC;
32893139Sru		\r indicates a single CR;
32993139Sru		\t indicates a single TAB;
33093139Sru		\b indicates a single BACKSPACE;
331116525Sru
33293139Sru	   4.	\ku indicates the Up Arrow key;
33393139Sru	   	\kd indicates the Down Arrow key;
33493139Sru	   	\kl indicates the Left Arrow key;
33593139Sru	   	\kr indicates the Right Arrow key;
33693139Sru	   	\kP indicates the Page Up (PRIOR) key;
33793139Sru	   	\kN indicates the Page Down (NEXT) key;
33893139Sru	   	\kh indicates the Home key;
33993139Sru	   	\ke indicates the End key;
34093139Sru	   	\kx indicates the DEL key;
34193139Sru		\k followed by any other character indicates a single
34293139Sru		control-K, and the following character is interpreted
34393139Sru		as in rules 1, 2, 3, 5 and 6.
34493139Sru
34593139Sru	   5.	\m followed by any sequence defined in rules 1, 2, 3, 4
34693139Sru		or 6 indicates the "Meta" modification of that key.
34793139Sru
34893139Sru	   6.	A backslash \ followed by any character not described
34993139Sru	   	above indicates that character itself.  In particular:
35093139Sru		\\ indicates a single backslash \,
35193139Sru		\  (backslash-space) indicates a single space,
35293139Sru		\^ indicates a single caret ^,
35393139Sru
35493139Sru	If the following line:
35593139Sru
35693139Sru		#stop
357116525Sru
35893139Sru	occurs anywhere in an 'info' or 'echo-area' section, that
35993139Sru	indicates to GNU Info to suppress all of its default key
36093139Sru	bindings in that context.
361116525Sru
36293139Sru	The 'var' section
36393139Sru	-----------------
36493139Sru	Each line in the 'var' section has the following syntax:
36593139Sru
36693139Sru		variable-name = value \n
367116525Sru
36893139Sru	Where "variable-name" is the name of a GNU Info variable and
36993139Sru	"value" is the value that GNU Info will assign to that variable
37093139Sru	when commencing execution.  There must be no white space in the
37193139Sru	variable name, nor between the variable name and the '='.  All
37293139Sru	characters immediately following the '=', up to but not
37393139Sru	including the terminating newline, are considered to be the
37493139Sru	value that will be assigned.  In other words, white space
37593139Sru	following the '=' is not ignored.
37693139Sru */
37793139Sru
378146515Srustatic int add_to_section (struct sect *s, const char *str, unsigned int len);
379146515Srustatic int lookup_action (const char *actname);
38093139Sru
38193139Sru/* Compile the input file into its various sections.  Return true if no
38293139Sru   error was encountered.
38393139Sru */
38493139Srustatic int
385146515Srucompile (FILE *fp, const char *filename, struct sect *sections)
38693139Sru{
38793139Sru  int error = 0;
38893139Sru  char rescan = 0;
38993139Sru  unsigned int lnum = 0;
390146515Sru  int c = 0;
39193139Sru
39293139Sru  /* This parser is a true state machine, with no sneaky fetching
39393139Sru     of input characters inside the main loop.  In other words, all
39493139Sru     state is fully represented by the following variables:
39593139Sru   */
39693139Sru  enum
39793139Sru    {
39893139Sru      start_of_line,
39993139Sru      start_of_comment,
40093139Sru      in_line_comment,
40193139Sru      in_trailing_comment,
40293139Sru      get_keyseq,
40393139Sru      got_keyseq,
40493139Sru      get_action,
40593139Sru      got_action,
40693139Sru      get_varname,
40793139Sru      got_varname,
40893139Sru      get_equals,
40993139Sru      got_equals,
410100513Sru      get_value
41193139Sru    }
41293139Sru  state = start_of_line;
41393139Sru  enum sect_e section = info;
41493139Sru  enum
41593139Sru    {
41693139Sru      normal,
41793139Sru      slosh,
41893139Sru      control,
41993139Sru      octal,
420100513Sru      special_key
42193139Sru    }
422146515Sru  seqstate;		/* used if state == get_keyseq */
42393139Sru  char meta = 0;
424146515Sru  char ocnt = 0;	/* used if state == get_keyseq && seqstate == octal */
42593139Sru
42693139Sru  /* Data is accumulated in the following variables.  The code
42793139Sru     avoids overflowing these strings, and throws an error
42893139Sru     where appropriate if a string limit is exceeded.  These string
42993139Sru     lengths are arbitrary (and should be large enough) and their
43093139Sru     lengths are not hard-coded anywhere else, so increasing them
43193139Sru     here will not break anything.  */
432146515Sru  char oval = 0;
43393139Sru  char comment[10];
434146515Sru  unsigned int clen = 0;
43593139Sru  char seq[20];
436146515Sru  unsigned int slen = 0;
43793139Sru  char act[80];
438146515Sru  unsigned int alen = 0;
43993139Sru  char varn[80];
440146515Sru  unsigned int varlen = 0;
44193139Sru  char val[80];
442146515Sru  unsigned int vallen = 0;
44393139Sru
44493139Sru#define	To_seq(c) \
44593139Sru		  do { \
44693139Sru		    if (slen < sizeof seq) \
44793139Sru		      seq[slen++] = meta ? Meta(c) : (c); \
44893139Sru		    else \
44993139Sru		      { \
450146515Sru			syntax_error(filename, lnum, _("key sequence too long"), \
451146515Sru                            NULL, NULL, NULL, NULL); \
45293139Sru			error = 1; \
45393139Sru		      } \
45493139Sru		    meta = 0; \
45593139Sru		  } while (0)
45693139Sru
45793139Sru  sections[info].cur = 1;
45893139Sru  sections[info].data[0] = 0;
45993139Sru  sections[ea].cur = 1;
46093139Sru  sections[ea].data[0] = 0;
46193139Sru  sections[var].cur = 0;
46293139Sru
46393139Sru  while (!error && (rescan || (c = fgetc (fp)) != EOF))
46493139Sru    {
46593139Sru      rescan = 0;
46693139Sru      switch (state)
46793139Sru	{
46893139Sru	case start_of_line:
46993139Sru	  lnum++;
47093139Sru	  if (c == '#')
47193139Sru	    state = start_of_comment;
47293139Sru	  else if (c != '\n')
47393139Sru	    {
47493139Sru	      switch (section)
47593139Sru		{
47693139Sru		case info:
47793139Sru		case ea:
47893139Sru		  state = get_keyseq;
47993139Sru		  seqstate = normal;
48093139Sru		  slen = 0;
48193139Sru		  break;
48293139Sru		case var:
48393139Sru		  state = get_varname;
48493139Sru		  varlen = 0;
48593139Sru		  break;
48693139Sru		}
48793139Sru	      rescan = 1;
48893139Sru	    }
48993139Sru	  break;
49093139Sru
49193139Sru	case start_of_comment:
49293139Sru	  clen = 0;
49393139Sru	  state = in_line_comment;
49493139Sru	  /* fall through */
49593139Sru	case in_line_comment:
49693139Sru	  if (c == '\n')
49793139Sru	    {
49893139Sru	      state = start_of_line;
49993139Sru	      comment[clen] = '\0';
50093139Sru	      if (strcmp (comment, "info") == 0)
50193139Sru		section = info;
50293139Sru	      else if (strcmp (comment, "echo-area") == 0)
50393139Sru		section = ea;
50493139Sru	      else if (strcmp (comment, "var") == 0)
50593139Sru		section = var;
50693139Sru	      else if (strcmp (comment, "stop") == 0
50793139Sru		       && (section == info || section == ea))
50893139Sru		sections[section].data[0] = 1;
50993139Sru	    }
51093139Sru	  else if (clen < sizeof comment - 1)
51193139Sru	    comment[clen++] = c;
51293139Sru	  break;
51393139Sru
51493139Sru	case in_trailing_comment:
51593139Sru	  if (c == '\n')
51693139Sru	    state = start_of_line;
51793139Sru	  break;
51893139Sru
51993139Sru	case get_keyseq:
52093139Sru	  switch (seqstate)
52193139Sru	    {
52293139Sru	    case normal:
52393139Sru	      if (c == '\n' || isspace (c))
52493139Sru		{
52593139Sru		  state = got_keyseq;
52693139Sru		  rescan = 1;
52793139Sru		  if (slen == 0)
52893139Sru		    {
529146515Sru		      syntax_error (filename, lnum, _("missing key sequence"),
530146515Sru                          NULL, NULL, NULL, NULL);
53193139Sru		      error = 1;
53293139Sru		    }
53393139Sru		}
53493139Sru	      else if (c == '\\')
53593139Sru		seqstate = slosh;
53693139Sru	      else if (c == '^')
53793139Sru		seqstate = control;
53893139Sru	      else
53993139Sru		To_seq (c);
54093139Sru	      break;
54193139Sru
54293139Sru	    case slosh:
54393139Sru	      switch (c)
54493139Sru		{
54593139Sru		case '0': case '1': case '2': case '3':
54693139Sru		case '4': case '5': case '6': case '7':
54793139Sru		  seqstate = octal;
54893139Sru		  oval = c - '0';
54993139Sru		  ocnt = 1;
55093139Sru		  break;
55193139Sru		case 'b':
55293139Sru		  To_seq ('\b');
55393139Sru		  seqstate = normal;
55493139Sru		  break;
55593139Sru		case 'e':
55693139Sru		  To_seq ('\033');
55793139Sru		  seqstate = normal;
55893139Sru		  break;
55993139Sru		case 'n':
56093139Sru		  To_seq ('\n');
56193139Sru		  seqstate = normal;
56293139Sru		  break;
56393139Sru		case 'r':
56493139Sru		  To_seq ('\r');
56593139Sru		  seqstate = normal;
56693139Sru		  break;
56793139Sru		case 't':
56893139Sru		  To_seq ('\t');
56993139Sru		  seqstate = normal;
57093139Sru		  break;
57193139Sru		case 'm':
57293139Sru		  meta = 1;
57393139Sru		  seqstate = normal;
57493139Sru		  break;
57593139Sru		case 'k':
57693139Sru		  seqstate = special_key;
57793139Sru		  break;
57893139Sru		default:
579116525Sru		  /* Backslash followed by any other char
58093139Sru		     just means that char.  */
58193139Sru		  To_seq (c);
58293139Sru		  seqstate = normal;
58393139Sru		  break;
58493139Sru		}
58593139Sru	      break;
58693139Sru
58793139Sru	    case octal:
58893139Sru	      switch (c)
58993139Sru		{
59093139Sru		case '0': case '1': case '2': case '3':
59193139Sru		case '4': case '5': case '6': case '7':
59293139Sru		  if (++ocnt <= 3)
59393139Sru		    oval = oval * 8 + c - '0';
59493139Sru		  if (ocnt == 3)
59593139Sru		    seqstate = normal;
59693139Sru		  break;
59793139Sru		default:
59893139Sru		  ocnt = 4;
59993139Sru		  seqstate = normal;
60093139Sru		  rescan = 1;
60193139Sru		  break;
60293139Sru		}
60393139Sru	      if (seqstate != octal)
60493139Sru		{
60593139Sru		  if (oval)
60693139Sru		    To_seq (oval);
60793139Sru		  else
60893139Sru		    {
609146515Sru		      syntax_error (filename, lnum,
610146515Sru                          _("NUL character (\\000) not permitted"),
611146515Sru                          NULL, NULL, NULL, NULL);
61293139Sru		      error = 1;
61393139Sru		    }
61493139Sru		}
61593139Sru	      break;
61693139Sru
61793139Sru	    case special_key:
61893139Sru	      To_seq (SK_ESCAPE);
61993139Sru	      switch (c)
62093139Sru		{
62193139Sru		case 'u': To_seq (SK_UP_ARROW); break;
62293139Sru		case 'd': To_seq (SK_DOWN_ARROW); break;
62393139Sru		case 'r': To_seq (SK_RIGHT_ARROW); break;
62493139Sru		case 'l': To_seq (SK_LEFT_ARROW); break;
62593139Sru		case 'U': To_seq (SK_PAGE_UP); break;
62693139Sru		case 'D': To_seq (SK_PAGE_DOWN); break;
62793139Sru		case 'h': To_seq (SK_HOME); break;
62893139Sru		case 'e': To_seq (SK_END); break;
62993139Sru		case 'x': To_seq (SK_DELETE); break;
63093139Sru		default:  To_seq (SK_LITERAL); rescan = 1; break;
63193139Sru		}
63293139Sru	      seqstate = normal;
63393139Sru	      break;
63493139Sru
63593139Sru	    case control:
63693139Sru	      if (CONTROL (c))
63793139Sru		To_seq (CONTROL (c));
63893139Sru	      else
63993139Sru		{
640146515Sru		  syntax_error (filename, lnum,
641146515Sru                      (char *) _("NUL character (^%c) not permitted"),
642146515Sru                      (void *) (long) c, NULL, NULL, NULL);
64393139Sru		  error = 1;
64493139Sru		}
64593139Sru	      seqstate = normal;
64693139Sru	      break;
64793139Sru	    }
64893139Sru	  break;
64993139Sru
65093139Sru	case got_keyseq:
65193139Sru	  if (isspace (c) && c != '\n')
65293139Sru	    break;
65393139Sru	  state = get_action;
65493139Sru	  alen = 0;
65593139Sru	  /* fall through */
65693139Sru	case get_action:
65793139Sru	  if (c == '\n' || isspace (c))
65893139Sru	    {
65993139Sru	      int a;
66093139Sru
66193139Sru	      state = got_action;
66293139Sru	      rescan = 1;
66393139Sru	      if (alen == 0)
66493139Sru		{
665146515Sru		  syntax_error (filename, lnum, (char *) _("missing action name"),
666146515Sru				(void *) (long) c, NULL, NULL, NULL);
66793139Sru		  error = 1;
66893139Sru		}
66993139Sru	      else
67093139Sru		{
67193139Sru		  act[alen] = '\0';
67293139Sru		  a = lookup_action (act);
67393139Sru		  if (a != -1)
67493139Sru		    {
67593139Sru		      char av = a;
67693139Sru
67793139Sru		      if (!(add_to_section (&sections[section], seq, slen)
67893139Sru			    && add_to_section (&sections[section], "", 1)
67993139Sru			    && add_to_section (&sections[section], &av, 1)))
68093139Sru			{
681146515Sru			  syntax_error (filename, lnum, _("section too long"),
682146515Sru                              NULL, NULL, NULL, NULL);
68393139Sru			  error = 1;
68493139Sru			}
68593139Sru		    }
68693139Sru		  else
68793139Sru		    {
688146515Sru		      syntax_error (filename, lnum, _("unknown action `%s'"),
689146515Sru                          act, NULL, NULL, NULL);
69093139Sru		      error = 1;
69193139Sru		    }
69293139Sru		}
69393139Sru	    }
69493139Sru	  else if (alen < sizeof act - 1)
69593139Sru	    act[alen++] = c;
69693139Sru	  else
69793139Sru	    {
698146515Sru	      syntax_error (filename, lnum, _("action name too long"),
699146515Sru                  NULL, NULL, NULL, NULL);
70093139Sru	      error = 1;
70193139Sru	    }
70293139Sru	  break;
703116525Sru
70493139Sru	case got_action:
70593139Sru	  if (c == '#')
70693139Sru	    state = in_trailing_comment;
70793139Sru	  else if (c == '\n')
70893139Sru	    state = start_of_line;
70993139Sru	  else if (!isspace (c))
71093139Sru	    {
711146515Sru	      syntax_error (filename, lnum,
712146515Sru                  _("extra characters following action `%s'"),
713146515Sru                  act, NULL, NULL, NULL);
71493139Sru	      error = 1;
71593139Sru	    }
71693139Sru	  break;
71793139Sru
71893139Sru	case get_varname:
71993139Sru	  if (c == '=')
72093139Sru	    {
72193139Sru	      if (varlen == 0)
72293139Sru		{
723146515Sru		  syntax_error (filename, lnum, _("missing variable name"),
724146515Sru                      NULL, NULL, NULL, NULL);
72593139Sru		  error = 1;
72693139Sru		}
72793139Sru	      state = get_value;
72893139Sru	      vallen = 0;
72993139Sru	    }
73093139Sru	  else if (c == '\n' || isspace (c))
73193139Sru	    {
732146515Sru	      syntax_error (filename, lnum,
733146515Sru                  _("missing `=' immediately after variable name"),
734146515Sru                  NULL, NULL, NULL, NULL);
73593139Sru	      error = 1;
73693139Sru	    }
73793139Sru	  else if (varlen < sizeof varn)
73893139Sru	    varn[varlen++] = c;
73993139Sru	  else
74093139Sru	    {
741146515Sru	      syntax_error (filename, lnum, _("variable name too long"),
742146515Sru                  NULL, NULL, NULL, NULL);
74393139Sru	      error = 1;
74493139Sru	    }
74593139Sru	  break;
746116525Sru
74793139Sru	case get_value:
74893139Sru	  if (c == '\n')
74993139Sru	    {
75093139Sru	      state = start_of_line;
75193139Sru	      if (!(add_to_section (&sections[section], varn, varlen)
75293139Sru		    && add_to_section (&sections[section], "", 1)
75393139Sru		    && add_to_section (&sections[section], val, vallen)
75493139Sru		    && add_to_section (&sections[section], "", 1)))
75593139Sru		{
756146515Sru		  syntax_error (filename, lnum, _("section too long"),
757146515Sru                      NULL, NULL, NULL, NULL);
75893139Sru		  error = 1;
75993139Sru		}
76093139Sru	    }
76193139Sru	  else if (vallen < sizeof val)
76293139Sru	    val[vallen++] = c;
76393139Sru	  else
76493139Sru	    {
765146515Sru	      syntax_error (filename, lnum, _("value too long"),
766146515Sru                  NULL, NULL, NULL, NULL);
76793139Sru	      error = 1;
76893139Sru	    }
76993139Sru	  break;
770146515Sru
771146515Sru        case get_equals:
772146515Sru        case got_equals:
773146515Sru        case got_varname:
774146515Sru          break;
77593139Sru	}
77693139Sru    }
77793139Sru
77893139Sru#undef To_seq
77993139Sru
78093139Sru  return !error;
78193139Sru}
78293139Sru
78393139Sru/* Add some characters to a section's data.  Return true if all the
78493139Sru   characters fit, or false if the section's size limit was exceeded.
78593139Sru */
78693139Srustatic int
787146515Sruadd_to_section (struct sect *s, const char *str, unsigned int len)
78893139Sru{
78993139Sru  if (s->cur + len > sizeof s->data)
79093139Sru    return 0;
791146515Sru  strncpy ((char *) s->data + s->cur, str, len);
79293139Sru  s->cur += len;
79393139Sru  return 1;
79493139Sru}
79593139Sru
79693139Sru/* Translate from an action name to its numeric code.  This uses the
79793139Sru   auto-generated array in key.c.
79893139Sru */
79993139Srustatic int
800146515Srulookup_action (const char *actname)
80193139Sru{
80293139Sru  int i;
80393139Sru
80493139Sru  if (strcmp ("invalid", actname) == 0)
80593139Sru    return A_INVALID;
80693139Sru  for (i = 0; function_key_array[i].name != NULL; i++)
80793139Sru    if (strcmp (function_key_array[i].name, actname) == 0)
80893139Sru      return function_key_array[i].code;
80993139Sru  return -1;
81093139Sru}
81193139Sru
81293139Sru/* Put an integer to an infokey file.
81393139Sru   Integers are stored as two bytes, low order first,
81493139Sru   in radix INFOKEY_RADIX.
81593139Sru */
81693139Srustatic int
817146515Sruputint (int i, FILE *fp)
81893139Sru{
81993139Sru  return fputc (i % INFOKEY_RADIX, fp) != EOF
82093139Sru    && fputc ((i / INFOKEY_RADIX) % INFOKEY_RADIX, fp) != EOF;
82193139Sru}
82293139Sru
82393139Sru/* Write an entire section to an infokey file.  If the section is
82493139Sru   empty, simply omit it.
82593139Sru */
82693139Srustatic int
827146515Sruputsect (struct sect *s, int code, FILE *fp)
82893139Sru{
82993139Sru  if (s->cur == 0)
83093139Sru    return 1;
83193139Sru  return fputc (code, fp) != EOF
83293139Sru    && putint (s->cur, fp)
83393139Sru    && fwrite (s->data, s->cur, 1, fp) == 1;
83493139Sru}
83593139Sru
83693139Sru/* Write an entire infokey file, given an array containing its sections.
83793139Sru */
83893139Srustatic int
839146515Sruwrite_infokey_file (FILE *fp, struct sect *sections)
84093139Sru{
84193139Sru  /* Get rid of sections with no effect. */
84293139Sru  if (sections[info].cur == 1 && sections[info].data[0] == 0)
84393139Sru    sections[info].cur = 0;
84493139Sru  if (sections[ea].cur == 1 && sections[ea].data[0] == 0)
84593139Sru    sections[ea].cur = 0;
84693139Sru
84793139Sru  /* Write all parts of the file out in order (no lseeks),
84893139Sru     checking for errors all the way. */
84993139Sru  return fputc (INFOKEY_MAGIC_S0, fp) != EOF
85093139Sru    && fputc (INFOKEY_MAGIC_S1, fp) != EOF
85193139Sru    && fputc (INFOKEY_MAGIC_S2, fp) != EOF
85293139Sru    && fputc (INFOKEY_MAGIC_S3, fp) != EOF
85393139Sru    && fputs (VERSION, fp) != EOF
85493139Sru    && fputc ('\0', fp) != EOF
85593139Sru    && putsect (&sections[info], INFOKEY_SECTION_INFO, fp)
85693139Sru    && putsect (&sections[ea], INFOKEY_SECTION_EA, fp)
85793139Sru    && putsect (&sections[var], INFOKEY_SECTION_VAR, fp)
85893139Sru    && fputc (INFOKEY_MAGIC_E0, fp) != EOF
85993139Sru    && fputc (INFOKEY_MAGIC_E1, fp) != EOF
86093139Sru    && fputc (INFOKEY_MAGIC_E2, fp) != EOF
86193139Sru    && fputc (INFOKEY_MAGIC_E3, fp) != EOF;
86293139Sru}
86393139Sru
86493139Sru
86593139Sru/* Error handling. */
86693139Sru
86793139Sru/* Give the user a "syntax error" message in the form
86893139Sru	progname: "filename", line N: message
86993139Sru */
87093139Srustatic void
871146515Sruerror_message (int error_code, const char *fmt,
872146515Sru    const void *a1, const void *a2, const void *a3, const void *a4)
87393139Sru{
87493139Sru  fprintf (stderr, "%s: ", program_name);
87593139Sru  fprintf (stderr, fmt, a1, a2, a3, a4);
87693139Sru  if (error_code)
87793139Sru    fprintf (stderr, " - %s", strerror (error_code));
87893139Sru  fprintf (stderr, "\n");
87993139Sru}
88093139Sru
88193139Sru/* Give the user a generic error message in the form
88293139Sru	progname: message
88393139Sru */
88493139Srustatic void
885146515Srusyntax_error (const char *filename,
886146515Sru    unsigned int linenum, const char *fmt,
887146515Sru    const void *a1, const void *a2, const void *a3, const void *a4)
88893139Sru{
88993139Sru  fprintf (stderr, "%s: ", program_name);
89093139Sru  fprintf (stderr, _("\"%s\", line %u: "), filename, linenum);
89193139Sru  fprintf (stderr, fmt, a1, a2, a3, a4);
89293139Sru  fprintf (stderr, "\n");
89393139Sru}
89493139Sru
89593139Sru/* Produce a gentle rtfm. */
89693139Srustatic void
897146515Srusuggest_help (void)
89893139Sru{
89993139Sru  fprintf (stderr, _("Try --help for more information.\n"));
90093139Sru}
90193139Sru
90293139Sru/* Produce a scaled down description of the available options to Info. */
90393139Srustatic void
904146515Srushort_help (void)
90593139Sru{
906100513Sru  printf (_("\
90793139SruUsage: %s [OPTION]... [INPUT-FILE]\n\
90893139Sru\n\
90993139SruCompile infokey source file to infokey file.  Reads INPUT-FILE (default\n\
91093139Sru$HOME/.infokey) and writes compiled key file to (by default) $HOME/.info.\n\
91193139Sru\n\
91293139SruOptions:\n\
91393139Sru  --output FILE        output to FILE instead of $HOME/.info\n\
91493139Sru  --help               display this help and exit.\n\
91593139Sru  --version            display version information and exit.\n\
916100513Sru"), program_name);
917100513Sru
918100513Sru  puts (_("\n\
91993139SruEmail bug reports to bug-texinfo@gnu.org,\n\
92093139Srugeneral questions and discussion to help-texinfo@gnu.org.\n\
921100513SruTexinfo home page: http://www.gnu.org/software/texinfo/"));
922100513Sru
92393139Sru  xexit (0);
92493139Sru}
925