make-docfile.c 37.8 KB
Newer Older
Richard M. Stallman's avatar
Richard M. Stallman committed
1
/* Generate doc-string file for GNU Emacs from source files.
2

Paul Eggert's avatar
Paul Eggert committed
3
Copyright (C) 1985-1986, 1992-1994, 1997, 1999-2016 Free Software
4
Foundation, Inc.
Richard M. Stallman's avatar
Richard M. Stallman committed
5 6 7

This file is part of GNU Emacs.

8
GNU Emacs is free software: you can redistribute it and/or modify
Joseph Arceneaux's avatar
Joseph Arceneaux committed
9
it under the terms of the GNU General Public License as published by
10 11
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Joseph Arceneaux's avatar
Joseph Arceneaux committed
12

Richard M. Stallman's avatar
Richard M. Stallman committed
13
GNU Emacs is distributed in the hope that it will be useful,
Joseph Arceneaux's avatar
Joseph Arceneaux committed
14 15 16
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
Richard M. Stallman's avatar
Richard M. Stallman committed
17

Joseph Arceneaux's avatar
Joseph Arceneaux committed
18
You should have received a copy of the GNU General Public License
19 20
along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */

Richard M. Stallman's avatar
Richard M. Stallman committed
21 22 23 24 25

/* The arguments given to this program are all the C and Lisp source files
 of GNU Emacs.  .elc and .el and .c files are allowed.
 A .o file can also be specified; the .c file it was made from is used.
 This helps the makefile pass the correct list of files.
26
 Option -d DIR means change to DIR before looking for files.
Richard M. Stallman's avatar
Richard M. Stallman committed
27 28 29 30 31 32 33 34 35 36

 The results, which go to standard output or to a file
 specified with -a or -o (-a to append, -o to start from nothing),
 are entries containing function or variable names and their documentation.
 Each entry starts with a ^_ character.
 Then comes F for a function or V for a variable.
 Then comes the function or variable name, terminated with a newline.
 Then comes the documentation for that function or variable.
 */

37 38
#include <config.h>

39
#include <stdbool.h>
40 41
#include <stddef.h>
#include <stdint.h>
Richard M. Stallman's avatar
Richard M. Stallman committed
42
#include <stdio.h>
43
#include <stdlib.h>   /* config.h unconditionally includes this anyway */
44

45
#ifdef WINDOWSNT
46 47 48
/* Defined to be sys_fopen in ms-w32.h, but only #ifdef emacs, so this
   is really just insurance.  */
#undef fopen
49 50
#include <direct.h>
#endif /* WINDOWSNT */
51

52
#include <binary-io.h>
53 54
#include <intprops.h>
#include <min-max.h>
55

56
#ifdef DOS_NT
57 58 59 60 61 62
/* Defined to be sys_chdir in ms-w32.h, but only #ifdef emacs, so this
   is really just insurance.

   Similarly, msdos defines this as sys_chdir, but we're not linking with the
   file where that function is defined.  */
#undef chdir
63
#define IS_SLASH(c)  ((c) == '/' || (c) == '\\' || (c) == ':')
64
#else  /* not DOS_NT */
65
#define IS_SLASH(c)  ((c) == '/')
66
#endif /* not DOS_NT */
Richard M. Stallman's avatar
Richard M. Stallman committed
67

68 69 70
static int scan_file (char *filename);
static int scan_lisp_file (const char *filename, const char *mode);
static int scan_c_file (char *filename, const char *mode);
71
static int scan_c_stream (FILE *infile);
72 73
static void start_globals (void);
static void write_globals (void);
74

Andreas Schwab's avatar
Andreas Schwab committed
75 76
#include <unistd.h>

77 78 79
/* Name this program was invoked with.  */
char *progname;

Tom Tromey's avatar
Tom Tromey committed
80 81 82
/* Nonzero if this invocation is generating globals.h.  */
int generate_globals;

Pavel Janík's avatar
Pavel Janík committed
83
/* Print error message.  `s1' is printf control string, `s2' is arg for it.  */
84 85

/* VARARGS1 */
86
static void
87
error (const char *s1, const char *s2)
88 89 90 91 92 93 94 95 96
{
  fprintf (stderr, "%s: ", progname);
  fprintf (stderr, s1, s2);
  fprintf (stderr, "\n");
}

/* Print error message and exit.  */

/* VARARGS1 */
97
static _Noreturn void
98
fatal (const char *s1, const char *s2)
99 100
{
  error (s1, s2);
101
  exit (EXIT_FAILURE);
102 103
}

104 105
static _Noreturn void
memory_exhausted (void)
106
{
107
  fatal ("virtual memory exhausted", 0);
108
}
Tom Tromey's avatar
Tom Tromey committed
109

110
/* Like malloc but get fatal error if memory is exhausted.  */
111

112 113
static void *
xmalloc (ptrdiff_t size)
114
{
115 116 117
  void *result = malloc (size);
  if (result == NULL)
    memory_exhausted ();
118 119 120
  return result;
}

Tom Tromey's avatar
Tom Tromey committed
121 122
/* Like realloc but get fatal error if memory is exhausted.  */

123
static void *
124
xrealloc (void *arg, ptrdiff_t size)
Tom Tromey's avatar
Tom Tromey committed
125
{
126
  void *result = realloc (arg, size);
Tom Tromey's avatar
Tom Tromey committed
127
  if (result == NULL)
128
    memory_exhausted ();
Tom Tromey's avatar
Tom Tromey committed
129 130 131
  return result;
}

132

133
int
134
main (int argc, char **argv)
Richard M. Stallman's avatar
Richard M. Stallman committed
135 136 137 138
{
  int i;
  int err_count = 0;

139 140
  progname = argv[0];

Richard M. Stallman's avatar
Richard M. Stallman committed
141 142 143 144
  /* If first two args are -o FILE, output to FILE.  */
  i = 1;
  if (argc > i + 1 && !strcmp (argv[i], "-o"))
    {
145 146 147 148 149
      if (! freopen (argv[i + 1], "w", stdout))
	{
	  perror (argv[i + 1]);
	  return EXIT_FAILURE;
	}
Richard M. Stallman's avatar
Richard M. Stallman committed
150 151 152 153
      i += 2;
    }
  if (argc > i + 1 && !strcmp (argv[i], "-a"))
    {
154 155 156 157 158
      if (! freopen (argv[i + 1], "a", stdout))
	{
	  perror (argv[i + 1]);
	  return EXIT_FAILURE;
	}
Richard M. Stallman's avatar
Richard M. Stallman committed
159 160
      i += 2;
    }
161 162
  if (argc > i + 1 && !strcmp (argv[i], "-d"))
    {
163 164 165 166 167
      if (chdir (argv[i + 1]) != 0)
	{
	  perror (argv[i + 1]);
	  return EXIT_FAILURE;
	}
168 169
      i += 2;
    }
Tom Tromey's avatar
Tom Tromey committed
170 171 172 173 174
  if (argc > i && !strcmp (argv[i], "-g"))
    {
      generate_globals = 1;
      ++i;
    }
Richard M. Stallman's avatar
Richard M. Stallman committed
175

176
  set_binary_mode (fileno (stdout), O_BINARY);
177

Tom Tromey's avatar
Tom Tromey committed
178 179 180
  if (generate_globals)
    start_globals ();

181 182 183
  if (argc <= i)
    scan_c_stream (stdin);
  else
184
    {
185 186 187 188 189 190 191 192 193 194 195
      int first_infile = i;
      for (; i < argc; i++)
	{
	  int j;
	  /* Don't process one file twice.  */
	  for (j = first_infile; j < i; j++)
	    if (strcmp (argv[i], argv[j]) == 0)
	      break;
	  if (j == i)
	    err_count += scan_file (argv[i]);
	}
196
    }
Tom Tromey's avatar
Tom Tromey committed
197 198 199 200

  if (err_count == 0 && generate_globals)
    write_globals ();

201
  return (err_count > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
Richard M. Stallman's avatar
Richard M. Stallman committed
202 203
}

Stefan Monnier's avatar
Stefan Monnier committed
204
/* Add a source file name boundary marker in the output file.  */
205
static void
206
put_filename (char *filename)
Stefan Monnier's avatar
Stefan Monnier committed
207
{
Eli Zaretskii's avatar
Eli Zaretskii committed
208 209 210 211
  char *tmp;

  for (tmp = filename; *tmp; tmp++)
    {
212
      if (IS_DIRECTORY_SEP (*tmp))
Eli Zaretskii's avatar
Eli Zaretskii committed
213 214
	filename = tmp + 1;
    }
Stefan Monnier's avatar
Stefan Monnier committed
215

216
  printf ("\037S%s\n", filename);
Stefan Monnier's avatar
Stefan Monnier committed
217 218
}

219 220
/* Read file FILENAME and output its doc strings to stdout.
   Return 1 if file is not found, 0 if it is found.  */
Richard M. Stallman's avatar
Richard M. Stallman committed
221

222
static int
223
scan_file (char *filename)
Richard M. Stallman's avatar
Richard M. Stallman committed
224
{
225
  ptrdiff_t len = strlen (filename);
Stefan Monnier's avatar
Stefan Monnier committed
226

Tom Tromey's avatar
Tom Tromey committed
227 228
  if (!generate_globals)
    put_filename (filename);
229
  if (len > 4 && !strcmp (filename + len - 4, ".elc"))
230
    return scan_lisp_file (filename, "rb");
231
  else if (len > 3 && !strcmp (filename + len - 3, ".el"))
232
    return scan_lisp_file (filename, "r");
Richard M. Stallman's avatar
Richard M. Stallman committed
233
  else
234
    return scan_c_file (filename, "r");
Richard M. Stallman's avatar
Richard M. Stallman committed
235
}
Tom Tromey's avatar
Tom Tromey committed
236

237
static void
Tom Tromey's avatar
Tom Tromey committed
238 239
start_globals (void)
{
240 241 242
  puts ("/* This file was auto-generated by make-docfile.  */");
  puts ("/* DO NOT EDIT.  */");
  puts ("struct emacs_globals {");
Tom Tromey's avatar
Tom Tromey committed
243
}
Richard M. Stallman's avatar
Richard M. Stallman committed
244

245
static char input_buffer[128];
Richard M. Stallman's avatar
Richard M. Stallman committed
246

Miles Bader's avatar
Miles Bader committed
247 248 249
/* Some state during the execution of `read_c_string_or_comment'.  */
struct rcsoc_state
{
Pavel Janík's avatar
Pavel Janík committed
250
  /* A count of spaces and newlines that have been read, but not output.  */
251
  intmax_t pending_spaces, pending_newlines;
Miles Bader's avatar
Miles Bader committed
252 253 254 255 256 257 258 259 260 261 262

  /* Where we're reading from.  */
  FILE *in_file;

  /* If non-zero, a buffer into which to copy characters.  */
  char *buf_ptr;
  /* If non-zero, a file into which to copy characters.  */
  FILE *out_file;

  /* A keyword we look for at the beginning of lines.  If found, it is
     not copied, and SAW_KEYWORD is set to true.  */
263
  const char *keyword;
Juanma Barranquero's avatar
Juanma Barranquero committed
264
  /* The current point we've reached in an occurrence of KEYWORD in
Miles Bader's avatar
Miles Bader committed
265
     the input stream.  */
266
  const char *cur_keyword_ptr;
Juanma Barranquero's avatar
Juanma Barranquero committed
267
  /* Set to true if we saw an occurrence of KEYWORD.  */
Miles Bader's avatar
Miles Bader committed
268 269 270 271 272
  int saw_keyword;
};

/* Output CH to the file or buffer in STATE.  Any pending newlines or
   spaces are output first.  */
Miles Bader's avatar
Miles Bader committed
273

274
static void
275
put_char (int ch, struct rcsoc_state *state)
Miles Bader's avatar
Miles Bader committed
276 277 278 279
{
  int out_ch;
  do
    {
Miles Bader's avatar
Miles Bader committed
280
      if (state->pending_newlines > 0)
Miles Bader's avatar
Miles Bader committed
281
	{
Miles Bader's avatar
Miles Bader committed
282
	  state->pending_newlines--;
Miles Bader's avatar
Miles Bader committed
283 284
	  out_ch = '\n';
	}
Miles Bader's avatar
Miles Bader committed
285
      else if (state->pending_spaces > 0)
Miles Bader's avatar
Miles Bader committed
286
	{
Miles Bader's avatar
Miles Bader committed
287
	  state->pending_spaces--;
Miles Bader's avatar
Miles Bader committed
288 289 290 291 292
	  out_ch = ' ';
	}
      else
	out_ch = ch;

Miles Bader's avatar
Miles Bader committed
293 294 295 296
      if (state->out_file)
	putc (out_ch, state->out_file);
      if (state->buf_ptr)
	*state->buf_ptr++ = out_ch;
Miles Bader's avatar
Miles Bader committed
297 298 299 300
    }
  while (out_ch != ch);
}

Miles Bader's avatar
Miles Bader committed
301 302 303 304 305 306 307
/* If in the middle of scanning a keyword, continue scanning with
   character CH, otherwise output CH to the file or buffer in STATE.
   Any pending newlines or spaces are output first, as well as any
   previously scanned characters that were thought to be part of a
   keyword, but were in fact not.  */

static void
308
scan_keyword_or_put_char (int ch, struct rcsoc_state *state)
Miles Bader's avatar
Miles Bader committed
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
{
  if (state->keyword
      && *state->cur_keyword_ptr == ch
      && (state->cur_keyword_ptr > state->keyword
	  || state->pending_newlines > 0))
    /* We might be looking at STATE->keyword at some point.
       Keep looking until we know for sure.  */
    {
      if (*++state->cur_keyword_ptr == '\0')
	/* Saw the whole keyword.  Set SAW_KEYWORD flag to true.  */
	{
	  state->saw_keyword = 1;

	  /* Reset the scanning pointer.  */
	  state->cur_keyword_ptr = state->keyword;

Pavel Janík's avatar
Pavel Janík committed
325
	  /* Canonicalize whitespace preceding a usage string.  */
Miles Bader's avatar
Miles Bader committed
326 327 328 329 330 331 332 333 334
	  state->pending_newlines = 2;
	  state->pending_spaces = 0;

	  /* Skip any whitespace between the keyword and the
	     usage string.  */
	  do
	    ch = getc (state->in_file);
	  while (ch == ' ' || ch == '\n');

335 336 337 338 339 340 341 342 343
	  /* Output the open-paren we just read.  */
	  put_char (ch, state);

	  /* Skip the function name and replace it with `fn'.  */
	  do
	    ch = getc (state->in_file);
	  while (ch != ' ' && ch != ')');
	  put_char ('f', state);
	  put_char ('n', state);
344

345
	  /* Put back the last character.  */
Miles Bader's avatar
Miles Bader committed
346 347 348 349 350 351 352 353 354 355
	  ungetc (ch, state->in_file);
	}
    }
  else
    {
      if (state->keyword && state->cur_keyword_ptr > state->keyword)
	/* We scanned the beginning of a potential usage
	   keyword, but it was a false alarm.  Output the
	   part we scanned.  */
	{
356
	  const char *p;
Miles Bader's avatar
Miles Bader committed
357 358 359 360 361 362 363 364 365 366 367 368

	  for (p = state->keyword; p < state->cur_keyword_ptr; p++)
	    put_char (*p, state);

	  state->cur_keyword_ptr = state->keyword;
	}

      put_char (ch, state);
    }
}


369 370
/* Skip a C string or C-style comment from INFILE, and return the
   character that follows.  COMMENT non-zero means skip a comment.  If
371
   PRINTFLAG is positive, output string contents to stdout.  If it is
372
   negative, store contents in buf.  Convert escape sequences \n and
373
   \t to newline and tab; discard \ followed by newline.
Juanma Barranquero's avatar
Juanma Barranquero committed
374
   If SAW_USAGE is non-zero, then any occurrences of the string `usage:'
375 376
   at the beginning of a line will be removed, and *SAW_USAGE set to
   true if any were encountered.  */
Richard M. Stallman's avatar
Richard M. Stallman committed
377

378
static int
379
read_c_string_or_comment (FILE *infile, int printflag, int comment, int *saw_usage)
Richard M. Stallman's avatar
Richard M. Stallman committed
380 381
{
  register int c;
Miles Bader's avatar
Miles Bader committed
382 383 384
  struct rcsoc_state state;

  state.in_file = infile;
385
  state.buf_ptr = (printflag < 0 ? input_buffer : 0);
386
  state.out_file = (printflag > 0 ? stdout : 0);
Miles Bader's avatar
Miles Bader committed
387 388 389 390 391 392 393
  state.pending_spaces = 0;
  state.pending_newlines = 0;
  state.keyword = (saw_usage ? "usage:" : 0);
  state.cur_keyword_ptr = state.keyword;
  state.saw_keyword = 0;

  c = getc (infile);
394
  if (comment)
Miles Bader's avatar
Miles Bader committed
395 396
    while (c == '\n' || c == '\r' || c == '\t' || c == ' ')
      c = getc (infile);
397

Richard M. Stallman's avatar
Richard M. Stallman committed
398 399
  while (c != EOF)
    {
400
      while (c != EOF && (comment ? c != '*' : c != '"'))
Richard M. Stallman's avatar
Richard M. Stallman committed
401 402 403 404
	{
	  if (c == '\\')
	    {
	      c = getc (infile);
405
	      if (c == '\n' || c == '\r')
Richard M. Stallman's avatar
Richard M. Stallman committed
406 407 408 409 410 411 412 413 414
		{
		  c = getc (infile);
		  continue;
		}
	      if (c == 'n')
		c = '\n';
	      if (c == 't')
		c = '\t';
	    }
Pavel Janík's avatar
Pavel Janík committed
415

Miles Bader's avatar
Miles Bader committed
416
	  if (c == ' ')
Miles Bader's avatar
Miles Bader committed
417
	    state.pending_spaces++;
Miles Bader's avatar
Miles Bader committed
418 419
	  else if (c == '\n')
	    {
Miles Bader's avatar
Miles Bader committed
420 421
	      state.pending_newlines++;
	      state.pending_spaces = 0;
Miles Bader's avatar
Miles Bader committed
422 423
	    }
	  else
Miles Bader's avatar
Miles Bader committed
424
	    scan_keyword_or_put_char (c, &state);
Miles Bader's avatar
Miles Bader committed
425

Richard M. Stallman's avatar
Richard M. Stallman committed
426 427
	  c = getc (infile);
	}
428

429 430
      if (c != EOF)
	c = getc (infile);
Richard M. Stallman's avatar
Richard M. Stallman committed
431

432 433 434 435 436 437 438
      if (comment)
	{
	  if (c == '/')
	    {
	      c = getc (infile);
	      break;
	    }
Pavel Janík's avatar
Pavel Janík committed
439

Miles Bader's avatar
Miles Bader committed
440
	  scan_keyword_or_put_char ('*', &state);
441 442 443 444 445
	}
      else
	{
	  if (c != '"')
	    break;
Pavel Janík's avatar
Pavel Janík committed
446

447 448 449 450
	  /* If we had a "", concatenate the two strings.  */
	  c = getc (infile);
	}
    }
Pavel Janík's avatar
Pavel Janík committed
451

Richard M. Stallman's avatar
Richard M. Stallman committed
452
  if (printflag < 0)
Miles Bader's avatar
Miles Bader committed
453 454 455 456
    *state.buf_ptr = 0;

  if (saw_usage)
    *saw_usage = state.saw_keyword;
Richard M. Stallman's avatar
Richard M. Stallman committed
457 458 459

  return c;
}
460 461


Richard M. Stallman's avatar
Richard M. Stallman committed
462

463
/* Write to stdout the argument names of function FUNC, whose text is in BUF.
Richard M. Stallman's avatar
Richard M. Stallman committed
464 465
   MINARGS and MAXARGS are the minimum and maximum number of arguments.  */

466
static void
467
write_c_args (char *func, char *buf, int minargs, int maxargs)
Richard M. Stallman's avatar
Richard M. Stallman committed
468
{
469
  char *p;
Jim Blandy's avatar
Jim Blandy committed
470
  int in_ident = 0;
471
  char *ident_start IF_LINT (= NULL);
472
  ptrdiff_t ident_length = 0;
Richard M. Stallman's avatar
Richard M. Stallman committed
473

474
  fputs ("(fn", stdout);
475 476 477

  if (*buf == '(')
    ++buf;
Richard M. Stallman's avatar
Richard M. Stallman committed
478

Jim Blandy's avatar
Jim Blandy committed
479
  for (p = buf; *p; p++)
Richard M. Stallman's avatar
Richard M. Stallman committed
480
    {
481
      char c = *p;
Jim Blandy's avatar
Jim Blandy committed
482

483
      /* Notice when a new identifier starts.  */
Jim Blandy's avatar
Jim Blandy committed
484 485 486 487 488
      if ((('A' <= c && c <= 'Z')
	   || ('a' <= c && c <= 'z')
	   || ('0' <= c && c <= '9')
	   || c == '_')
	  != in_ident)
Richard M. Stallman's avatar
Richard M. Stallman committed
489
	{
Jim Blandy's avatar
Jim Blandy committed
490 491 492
	  if (!in_ident)
	    {
	      in_ident = 1;
493
	      ident_start = p;
Jim Blandy's avatar
Jim Blandy committed
494 495
	    }
	  else
496 497 498 499
	    {
	      in_ident = 0;
	      ident_length = p - ident_start;
	    }
Richard M. Stallman's avatar
Richard M. Stallman committed
500
	}
Jim Blandy's avatar
Jim Blandy committed
501

502 503 504
      /* Found the end of an argument, write out the last seen
	 identifier.  */
      if (c == ',' || c == ')')
505
	{
506 507
	  if (ident_length == 0)
	    {
508
	      error ("empty arg list for '%s' should be (void), not ()", func);
509 510 511
	      continue;
	    }

512 513 514
	  if (strncmp (ident_start, "void", ident_length) == 0)
	    continue;

515
	  putchar (' ');
516 517

	  if (minargs == 0 && maxargs > 0)
518
	    fputs ("&optional ", stdout);
Jim Blandy's avatar
Jim Blandy committed
519

520 521 522 523
	  minargs--;
	  maxargs--;

	  /* In C code, `default' is a reserved word, so we spell it
Paul Eggert's avatar
Paul Eggert committed
524
	     `defalt'; demangle that here.  */
525
	  if (ident_length == 6 && memcmp (ident_start, "defalt", 6) == 0)
526
	    fputs ("DEFAULT", stdout);
527 528 529 530 531 532 533 534 535 536
	  else
	    while (ident_length-- > 0)
	      {
		c = *ident_start++;
		if (c >= 'a' && c <= 'z')
		  /* Upcase the letter.  */
		  c += 'A' - 'a';
		else if (c == '_')
		  /* Print underscore as hyphen.  */
		  c = '-';
537
		putchar (c);
538 539
	      }
	}
Richard M. Stallman's avatar
Richard M. Stallman committed
540
    }
541

542
  putchar (')');
Richard M. Stallman's avatar
Richard M. Stallman committed
543
}
Tom Tromey's avatar
Tom Tromey committed
544

545
/* The types of globals.  These are sorted roughly in decreasing alignment
546 547
   order to avoid allocation gaps, except that symbols and functions
   are last.  */
Tom Tromey's avatar
Tom Tromey committed
548 549
enum global_type
{
550 551
  INVALID,
  LISP_OBJECT,
552
  EMACS_INTEGER,
Tom Tromey's avatar
Tom Tromey committed
553
  BOOLEAN,
554
  SYMBOL,
Paul Eggert's avatar
Paul Eggert committed
555
  FUNCTION
Tom Tromey's avatar
Tom Tromey committed
556 557 558 559 560 561 562
};

/* A single global.  */
struct global
{
  enum global_type type;
  char *name;
563
  int flags;
564 565 566 567 568
  union
  {
    int value;
    char const *svalue;
  } v;
Tom Tromey's avatar
Tom Tromey committed
569 570
};

571 572 573
/* Bit values for FLAGS field from the above.  Applied for DEFUNs only.  */
enum { DEFUN_noreturn = 1, DEFUN_const = 2 };

Tom Tromey's avatar
Tom Tromey committed
574 575
/* All the variable names we saw while scanning C sources in `-g'
   mode.  */
576 577
ptrdiff_t num_globals;
ptrdiff_t num_globals_allocated;
Tom Tromey's avatar
Tom Tromey committed
578 579
struct global *globals;

580
static struct global *
581 582
add_global (enum global_type type, char const *name, int value,
	    char const *svalue)
Tom Tromey's avatar
Tom Tromey committed
583 584 585 586
{
  /* Ignore the one non-symbol that can occur.  */
  if (strcmp (name, "..."))
    {
587
      if (num_globals == num_globals_allocated)
Tom Tromey's avatar
Tom Tromey committed
588
	{
589 590 591 592 593 594 595 596 597
	  ptrdiff_t num_globals_max = (min (PTRDIFF_MAX, SIZE_MAX)
				       / sizeof *globals);
	  if (num_globals_allocated == num_globals_max)
	    memory_exhausted ();
	  if (num_globals_allocated < num_globals_max / 2)
	    num_globals_allocated = 2 * num_globals_allocated + 1;
	  else
	    num_globals_allocated = num_globals_max;
	  globals = xrealloc (globals, num_globals_allocated * sizeof *globals);
Tom Tromey's avatar
Tom Tromey committed
598 599
	}

600 601 602 603
      ++num_globals;

      ptrdiff_t namesize = strlen (name) + 1;
      char *buf = xmalloc (namesize + (svalue ? strlen (svalue) + 1 : 0));
Tom Tromey's avatar
Tom Tromey committed
604
      globals[num_globals - 1].type = type;
605
      globals[num_globals - 1].name = strcpy (buf, name);
606
      if (svalue)
607
	globals[num_globals - 1].v.svalue = strcpy (buf + namesize, svalue);
608 609
      else
	globals[num_globals - 1].v.value = value;
610 611
      globals[num_globals - 1].flags = 0;
      return globals + num_globals - 1;
Tom Tromey's avatar
Tom Tromey committed
612
    }
613
  return NULL;
Tom Tromey's avatar
Tom Tromey committed
614 615 616 617 618 619 620
}

static int
compare_globals (const void *a, const void *b)
{
  const struct global *ga = a;
  const struct global *gb = b;
621

622 623
  if (ga->type != gb->type)
    return ga->type - gb->type;
624

Paul Eggert's avatar
Paul Eggert committed
625
  /* Consider "nil" to be the least, so that iQnil is zero.  That
Paul Eggert's avatar
Paul Eggert committed
626 627 628 629 630 631 632 633 634
     way, Qnil's internal representation is zero, which is a bit faster.  */
  if (ga->type == SYMBOL)
    {
      bool a_nil = strcmp (ga->name, "Qnil") == 0;
      bool b_nil = strcmp (gb->name, "Qnil") == 0;
      if (a_nil | b_nil)
	return b_nil - a_nil;
    }

Tom Tromey's avatar
Tom Tromey committed
635 636 637
  return strcmp (ga->name, gb->name);
}

638
static void
639
close_emacs_globals (int num_symbols)
640
{
641 642 643 644 645 646
  printf (("};\n"
	   "extern struct emacs_globals globals;\n"
	   "\n"
	   "#ifndef DEFINE_SYMBOLS\n"
	   "extern\n"
	   "#endif\n"
Paul Eggert's avatar
Paul Eggert committed
647
	   "struct Lisp_Symbol alignas (GCALIGNMENT) lispsym[%d];\n"),
648
	  num_symbols);
649 650
}

651
static void
Tom Tromey's avatar
Tom Tromey committed
652 653
write_globals (void)
{
654
  ptrdiff_t i, j;
655
  bool seen_defun = false;
656 657
  ptrdiff_t symnum = 0;
  ptrdiff_t num_symbols = 0;
Tom Tromey's avatar
Tom Tromey committed
658
  qsort (globals, num_globals, sizeof (struct global), compare_globals);
659 660 661 662 663 664 665 666 667 668 669

  j = 0;
  for (i = 0; i < num_globals; i++)
    {
      while (i + 1 < num_globals
	     && strcmp (globals[i].name, globals[i + 1].name) == 0)
	{
	  if (globals[i].type == FUNCTION
	      && globals[i].v.value != globals[i + 1].v.value)
	    error ("function '%s' defined twice with differing signatures",
		   globals[i].name);
670
	  free (globals[i].name);
671 672 673 674 675 676 677
	  i++;
	}
      num_symbols += globals[i].type == SYMBOL;
      globals[j++] = globals[i];
    }
  num_globals = j;

Tom Tromey's avatar
Tom Tromey committed
678 679
  for (i = 0; i < num_globals; ++i)
    {
680
      char const *type = 0;
Tom Tromey's avatar
Tom Tromey committed
681 682 683

      switch (globals[i].type)
	{
684
	case EMACS_INTEGER:
Tom Tromey's avatar
Tom Tromey committed
685 686 687
	  type = "EMACS_INT";
	  break;
	case BOOLEAN:
688
	  type = "bool";
Tom Tromey's avatar
Tom Tromey committed
689 690 691 692
	  break;
	case LISP_OBJECT:
	  type = "Lisp_Object";
	  break;
693
	case SYMBOL:
694 695 696
	case FUNCTION:
	  if (!seen_defun)
	    {
697
	      close_emacs_globals (num_symbols);
698
	      putchar ('\n');
699
	      seen_defun = true;
700 701
	    }
	  break;
Tom Tromey's avatar
Tom Tromey committed
702 703 704 705
	default:
	  fatal ("not a recognized DEFVAR_", 0);
	}

706
      if (type)
707
	{
708 709 710
	  printf ("  %s f_%s;\n", type, globals[i].name);
	  printf ("#define %s globals.f_%s\n",
		  globals[i].name, globals[i].name);
711
	}
712
      else if (globals[i].type == SYMBOL)
713
	printf (("#define i%s %td\n"
714 715
		 "DEFINE_LISP_SYMBOL (%s)\n"),
		globals[i].name, symnum++, globals[i].name);
716 717
      else
	{
718
	  if (globals[i].flags & DEFUN_noreturn)
719
	    fputs ("_Noreturn ", stdout);
Paul Eggert's avatar
Paul Eggert committed
720

721
	  printf ("EXFUN (%s, ", globals[i].name);
722
	  if (globals[i].v.value == -1)
723
	    fputs ("MANY", stdout);
724
	  else if (globals[i].v.value == -2)
725
	    fputs ("UNEVALLED", stdout);
726
	  else
727
	    printf ("%d", globals[i].v.value);
728
	  putchar (')');
Paul Eggert's avatar
Paul Eggert committed
729

730
	  if (globals[i].flags & DEFUN_const)
731
	    fputs (" ATTRIBUTE_CONST", stdout);
Paul Eggert's avatar
Paul Eggert committed
732

733
	  puts (";");
734
	}
Tom Tromey's avatar
Tom Tromey committed
735 736
    }

737
  if (!seen_defun)
738 739 740 741
    close_emacs_globals (num_symbols);

  puts ("#ifdef DEFINE_SYMBOLS");
  puts ("static char const *const defsym_name[] = {");
742
  for (ptrdiff_t i = 0; i < num_globals; i++)
743 744
    if (globals[i].type == SYMBOL)
      printf ("\t\"%s\",\n", globals[i].v.svalue);
745 746
  puts ("};");
  puts ("#endif");
747 748

  puts ("#define Qnil builtin_lisp_symbol (0)");
Paul Eggert's avatar
Paul Eggert committed
749
  puts ("#if DEFINE_NON_NIL_Q_SYMBOL_MACROS");
750
  num_symbols = 0;
751
  for (ptrdiff_t i = 0; i < num_globals; i++)
752
    if (globals[i].type == SYMBOL && num_symbols++ != 0)
753
      printf ("# define %s builtin_lisp_symbol (%td)\n",
754 755
	      globals[i].name, num_symbols - 1);
  puts ("#endif");
Tom Tromey's avatar
Tom Tromey committed
756 757
}

Richard M. Stallman's avatar
Richard M. Stallman committed
758 759

/* Read through a c file.  If a .o file is named,
760
   the corresponding .c or .m file is read instead.
Richard M. Stallman's avatar
Richard M. Stallman committed
761 762 763
   Looks for DEFUN constructs such as are defined in ../src/lisp.h.
   Accepts any word starting DEF... so it finds DEFSIMPLE and DEFPRED.  */

764
static int
765
scan_c_file (char *filename, const char *mode)
Richard M. Stallman's avatar
Richard M. Stallman committed
766 767
{
  FILE *infile;
768
  int extension = filename[strlen (filename) - 1];
Richard M. Stallman's avatar
Richard M. Stallman committed
769

770
  if (extension == 'o')
Richard M. Stallman's avatar
Richard M. Stallman committed
771 772
    filename[strlen (filename) - 1] = 'c';

773
  infile = fopen (filename, mode);
Richard M. Stallman's avatar
Richard M. Stallman committed
774

775 776
  if (infile == NULL && extension == 'o')
    {
777
      /* Try .m.  */
778 779 780
      filename[strlen (filename) - 1] = 'm';
      infile = fopen (filename, mode);
      if (infile == NULL)
781
        filename[strlen (filename) - 1] = 'c'; /* Don't confuse people.  */
782 783
    }

784
  /* No error if non-ex input file.  */
Richard M. Stallman's avatar
Richard M. Stallman committed
785 786 787 788 789 790
  if (infile == NULL)
    {
      perror (filename);
      return 0;
    }

Pavel Janík's avatar
Pavel Janík committed
791
  /* Reset extension to be able to detect duplicate files.  */
792
  filename[strlen (filename) - 1] = extension;
793 794 795
  return scan_c_stream (infile);
}

796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812
/* Return 1 if next input from INFILE is equal to P, -1 if EOF,
   0 if input doesn't match.  */

static int
stream_match (FILE *infile, const char *p)
{
  for (; *p; p++)
    {
      int c = getc (infile);
      if (c == EOF)
       return -1;
      if (c != *p)
       return 0;
    }
  return 1;
}

813 814 815 816 817
static int
scan_c_stream (FILE *infile)
{
  int commas, minargs, maxargs;
  int c = '\n';
818

Richard M. Stallman's avatar
Richard M. Stallman committed
819 820
  while (!feof (infile))
    {
821
      int doc_keyword = 0;
822 823 824 825
      int defunflag = 0;
      int defvarperbufferflag = 0;
      int defvarflag = 0;
      enum global_type type = INVALID;
826 827
      static char *name;
      static ptrdiff_t name_size;
828

829
      if (c != '\n' && c != '\r')
Richard M. Stallman's avatar
Richard M. Stallman committed
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
	{
	  c = getc (infile);
	  continue;
	}
      c = getc (infile);
      if (c == ' ')
	{
	  while (c == ' ')
	    c = getc (infile);
	  if (c != 'D')
	    continue;
	  c = getc (infile);
	  if (c != 'E')
	    continue;
	  c = getc (infile);
	  if (c != 'F')
	    continue;
	  c = getc (infile);
848
	  if (c == 'S')
Tom Tromey's avatar
Tom Tromey committed
849
	    {
850 851 852 853 854 855 856 857 858 859
	      c = getc (infile);
	      if (c != 'Y')
		continue;
	      c = getc (infile);
	      if (c != 'M')
		continue;
	      c = getc (infile);
	      if (c != ' ' && c != '\t' && c != '(')
		continue;
	      type = SYMBOL;
Tom Tromey's avatar
Tom Tromey committed
860
	    }
861 862 863 864 865 866 867 868 869 870 871
	  else if (c == 'V')
	    {
	      c = getc (infile);
	      if (c != 'A')
		continue;
	      c = getc (infile);
	      if (c != 'R')
		continue;
	      c = getc (infile);
	      if (c != '_')
		continue;
872

873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
	      defvarflag = 1;

	      c = getc (infile);
	      defvarperbufferflag = (c == 'P');
	      if (generate_globals)
		{
		  if (c == 'I')
		    type = EMACS_INTEGER;
		  else if (c == 'L')
		    type = LISP_OBJECT;
		  else if (c == 'B')
		    type = BOOLEAN;
		}

	      c = getc (infile);
	      /* We need to distinguish between DEFVAR_BOOL and
		 DEFVAR_BUFFER_DEFAULTS.  */
	      if (generate_globals && type == BOOLEAN && c != 'O')
		type = INVALID;
	    }
	  else
	    continue;
Richard M. Stallman's avatar
Richard M. Stallman committed
895 896 897 898 899 900 901 902 903 904 905 906 907 908
	}
      else if (c == 'D')
	{
	  c = getc (infile);
	  if (c != 'E')
	    continue;
	  c = getc (infile);
	  if (c != 'F')
	    continue;
	  c = getc (infile);
	  defunflag = c == 'U';
	}
      else continue;

909 910
      if (generate_globals
	  && (!defvarflag || defvarperbufferflag || type == INVALID)
911
	  && !defunflag && type != SYMBOL)
Tom Tromey's avatar
Tom Tromey committed
912 913
	continue;

Richard M. Stallman's avatar
Richard M. Stallman committed
914 915 916 917 918 919 920
      while (c != '(')
	{
	  if (c < 0)
	    goto eof;
	  c = getc (infile);
	}

921 922 923 924 925 926 927 928
      if (type != SYMBOL)
	{
	  /* Lisp variable or function name.  */
	  c = getc (infile);
	  if (c != '"')
	    continue;
	  c = read_c_string_or_comment (infile, -1, 0, 0);
	}
929

Tom Tromey's avatar
Tom Tromey committed
930 931
      if (generate_globals)
	{
932
	  ptrdiff_t i = 0;
933
	  char const *svalue = 0;
Tom Tromey's avatar
Tom Tromey committed
934 935 936 937 938 939 940 941 942 943 944

	  /* Skip "," and whitespace.  */
	  do
	    {
	      c = getc (infile);
	    }
	  while (c == ',' || c == ' ' || c == '\t' || c == '\n' || c == '\r');

	  /* Read in the identifier.  */
	  do
	    {
945 946
	      if (c < 0)
		goto eof;
947
	      input_buffer[i++] = c;
Tom Tromey's avatar
Tom Tromey committed
948 949
	      c = getc (infile);
	    }
950 951
	  while (! (c == ',' || c == ' ' || c == '\t'
		    || c == '\n' || c == '\r'));
952
	  input_buffer[i] = '\0';
Tom Tromey's avatar
Tom Tromey committed
953

954 955 956 957 958 959 960 961 962 963
	  if (name_size <= i)
	    {
	      free (name);
	      name_size = i + 1;
	      ptrdiff_t doubled;
	      if (! INT_MULTIPLY_WRAPV (name_size, 2, &doubled)
		  && doubled <= SIZE_MAX)
		name_size = doubled;
	      name = xmalloc (name_size);
	    }
964
	  memcpy (name, input_buffer, i + 1);
965