make-docfile.c 38.5 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-2020 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
along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
20

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 <stdarg.h>
40 41
#include <stddef.h>
#include <stdint.h>
Paul Eggert's avatar
Paul Eggert committed
42 43
#include <stdlib.h>
#include <string.h>
44

45
#include <binary-io.h>
46
#include <c-ctype.h>
47 48 49 50
#include <intprops.h>
#include <min-max.h>
#include <unlocked-io.h>

51
#ifdef WINDOWSNT
52 53 54
/* Defined to be sys_fopen in ms-w32.h, but only #ifdef emacs, so this
   is really just insurance.  */
#undef fopen
55 56
#include <direct.h>
#endif /* WINDOWSNT */
57

58
#ifdef DOS_NT
59 60 61 62 63 64
/* 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
65
#define IS_SLASH(c)  ((c) == '/' || (c) == '\\' || (c) == ':')
66
#else  /* not DOS_NT */
67
#define IS_SLASH(c)  ((c) == '/')
68
#endif /* not DOS_NT */
Richard M. Stallman's avatar
Richard M. Stallman committed
69

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

Andreas Schwab's avatar
Andreas Schwab committed
77 78
#include <unistd.h>

79
/* Name this program was invoked with.  */
80
static char *progname;
81

82 83
/* True if this invocation is generating globals.h.  */
static bool generate_globals;
Tom Tromey's avatar
Tom Tromey committed
84

85
/* Print error message.  Args are like vprintf.  */
86

87 88
static void ATTRIBUTE_FORMAT_PRINTF (1, 0)
verror (char const *m, va_list ap)
89 90
{
  fprintf (stderr, "%s: ", progname);
91
  vfprintf (stderr, m, ap);
92 93 94
  fprintf (stderr, "\n");
}

95
/* Print error message.  Args are like printf.  */
96

97 98 99 100 101 102 103 104 105 106 107 108 109
static void ATTRIBUTE_FORMAT_PRINTF (1, 2)
error (char const *m, ...)
{
  va_list ap;
  va_start (ap, m);
  verror (m, ap);
  va_end (ap);
}

/* Print error message and exit.  Args are like printf.  */

static _Noreturn void ATTRIBUTE_FORMAT_PRINTF (1, 2)
fatal (char const *m, ...)
110
{
111 112 113 114
  va_list ap;
  va_start (ap, m);
  verror (m, ap);
  va_end (ap);
115
  exit (EXIT_FAILURE);
116 117
}

118 119
static _Noreturn void
memory_exhausted (void)
120
{
121
  fatal ("virtual memory exhausted");
122
}
Tom Tromey's avatar
Tom Tromey committed
123

124
/* Like malloc but get fatal error if memory is exhausted.  */
125

126
static void * ATTRIBUTE_MALLOC
127
xmalloc (ptrdiff_t size)
128
{
129 130 131
  void *result = malloc (size);
  if (result == NULL)
    memory_exhausted ();
132 133 134
  return result;
}

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

137
static void *
138
xrealloc (void *arg, ptrdiff_t size)
Tom Tromey's avatar
Tom Tromey committed
139
{
140
  void *result = realloc (arg, size);
Tom Tromey's avatar
Tom Tromey committed
141
  if (result == NULL)
142
    memory_exhausted ();
Tom Tromey's avatar
Tom Tromey committed
143 144 145
  return result;
}

146

147
int
148
main (int argc, char **argv)
Richard M. Stallman's avatar
Richard M. Stallman committed
149 150 151
{
  int i;

152 153
  progname = argv[0];

Richard M. Stallman's avatar
Richard M. Stallman committed
154 155 156 157
  /* If first two args are -o FILE, output to FILE.  */
  i = 1;
  if (argc > i + 1 && !strcmp (argv[i], "-o"))
    {
158 159 160 161 162
      if (! freopen (argv[i + 1], "w", stdout))
	{
	  perror (argv[i + 1]);
	  return EXIT_FAILURE;
	}
Richard M. Stallman's avatar
Richard M. Stallman committed
163 164 165 166
      i += 2;
    }
  if (argc > i + 1 && !strcmp (argv[i], "-a"))
    {
167 168 169 170 171
      if (! freopen (argv[i + 1], "a", stdout))
	{
	  perror (argv[i + 1]);
	  return EXIT_FAILURE;
	}
Richard M. Stallman's avatar
Richard M. Stallman committed
172 173
      i += 2;
    }
174 175
  if (argc > i + 1 && !strcmp (argv[i], "-d"))
    {
176 177 178 179 180
      if (chdir (argv[i + 1]) != 0)
	{
	  perror (argv[i + 1]);
	  return EXIT_FAILURE;
	}
181 182
      i += 2;
    }
Tom Tromey's avatar
Tom Tromey committed
183 184
  if (argc > i && !strcmp (argv[i], "-g"))
    {
185
      generate_globals = true;
Tom Tromey's avatar
Tom Tromey committed
186 187
      ++i;
    }
Richard M. Stallman's avatar
Richard M. Stallman committed
188

189
  set_binary_mode (fileno (stdout), O_BINARY);
190

Tom Tromey's avatar
Tom Tromey committed
191 192 193
  if (generate_globals)
    start_globals ();

194 195 196
  if (argc <= i)
    scan_c_stream (stdin);
  else
197
    {
198 199 200 201 202 203 204 205 206
      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)
207
	    scan_file (argv[i]);
208
	}
209
    }
Tom Tromey's avatar
Tom Tromey committed
210

211
  if (generate_globals)
Tom Tromey's avatar
Tom Tromey committed
212 213
    write_globals ();

214 215 216 217
  if (ferror (stdout) || fclose (stdout) != 0)
    fatal ("write error");

  return EXIT_SUCCESS;
Richard M. Stallman's avatar
Richard M. Stallman committed
218 219
}

Stefan Monnier's avatar
Stefan Monnier committed
220
/* Add a source file name boundary marker in the output file.  */
221
static void
222
put_filename (char *filename)
Stefan Monnier's avatar
Stefan Monnier committed
223
{
Eli Zaretskii's avatar
Eli Zaretskii committed
224 225 226 227
  char *tmp;

  for (tmp = filename; *tmp; tmp++)
    {
228
      if (IS_DIRECTORY_SEP (*tmp))
Eli Zaretskii's avatar
Eli Zaretskii committed
229 230
	filename = tmp + 1;
    }
Stefan Monnier's avatar
Stefan Monnier committed
231

232
  printf ("\037S%s\n", filename);
Stefan Monnier's avatar
Stefan Monnier committed
233 234
}

235
/* Read file FILENAME and output its doc strings to stdout.
236
   Return true if file is found, false otherwise.  */
Richard M. Stallman's avatar
Richard M. Stallman committed
237

238
static void
239
scan_file (char *filename)
Richard M. Stallman's avatar
Richard M. Stallman committed
240
{
241
  ptrdiff_t len = strlen (filename);
Stefan Monnier's avatar
Stefan Monnier committed
242

Tom Tromey's avatar
Tom Tromey committed
243 244
  if (!generate_globals)
    put_filename (filename);
245
  if (len > 4 && !strcmp (filename + len - 4, ".elc"))
246
    scan_lisp_file (filename, "rb");
247
  else if (len > 3 && !strcmp (filename + len - 3, ".el"))
248
    scan_lisp_file (filename, "r");
Richard M. Stallman's avatar
Richard M. Stallman committed
249
  else
250
    scan_c_file (filename, "r");
Richard M. Stallman's avatar
Richard M. Stallman committed
251
}
Tom Tromey's avatar
Tom Tromey committed
252

253
static void
Tom Tromey's avatar
Tom Tromey committed
254 255
start_globals (void)
{
256 257 258
  puts ("/* This file was auto-generated by make-docfile.  */");
  puts ("/* DO NOT EDIT.  */");
  puts ("struct emacs_globals {");
Tom Tromey's avatar
Tom Tromey committed
259
}
Richard M. Stallman's avatar
Richard M. Stallman committed
260

261
static char input_buffer[128];
Richard M. Stallman's avatar
Richard M. Stallman committed
262

Miles Bader's avatar
Miles Bader committed
263 264 265
/* Some state during the execution of `read_c_string_or_comment'.  */
struct rcsoc_state
{
Pavel Janík's avatar
Pavel Janík committed
266
  /* A count of spaces and newlines that have been read, but not output.  */
267
  intmax_t pending_spaces, pending_newlines;
Miles Bader's avatar
Miles Bader committed
268 269 270 271 272 273 274 275 276 277 278

  /* 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.  */
279
  const char *keyword;
Juanma Barranquero's avatar
Juanma Barranquero committed
280
  /* The current point we've reached in an occurrence of KEYWORD in
Miles Bader's avatar
Miles Bader committed
281
     the input stream.  */
282
  const char *cur_keyword_ptr;
Juanma Barranquero's avatar
Juanma Barranquero committed
283
  /* Set to true if we saw an occurrence of KEYWORD.  */
284
  bool saw_keyword;
Miles Bader's avatar
Miles Bader committed
285 286 287 288
};

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

290
static void
291
put_char (char ch, struct rcsoc_state *state)
Miles Bader's avatar
Miles Bader committed
292
{
293
  char out_ch;
Miles Bader's avatar
Miles Bader committed
294 295
  do
    {
Miles Bader's avatar
Miles Bader committed
296
      if (state->pending_newlines > 0)
Miles Bader's avatar
Miles Bader committed
297
	{
Miles Bader's avatar
Miles Bader committed
298
	  state->pending_newlines--;
Miles Bader's avatar
Miles Bader committed
299 300
	  out_ch = '\n';
	}
Miles Bader's avatar
Miles Bader committed
301
      else if (state->pending_spaces > 0)
Miles Bader's avatar
Miles Bader committed
302
	{
Miles Bader's avatar
Miles Bader committed
303
	  state->pending_spaces--;
Miles Bader's avatar
Miles Bader committed
304 305 306 307 308
	  out_ch = ' ';
	}
      else
	out_ch = ch;

Miles Bader's avatar
Miles Bader committed
309 310 311 312
      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
313 314 315 316
    }
  while (out_ch != ch);
}

Miles Bader's avatar
Miles Bader committed
317 318 319 320 321 322 323
/* 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
324
scan_keyword_or_put_char (char ch, struct rcsoc_state *state)
Miles Bader's avatar
Miles Bader committed
325 326 327 328 329 330 331 332 333 334 335
{
  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.  */
	{
336
	  state->saw_keyword = true;
Miles Bader's avatar
Miles Bader committed
337 338 339 340

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

Pavel Janík's avatar
Pavel Janík committed
341
	  /* Canonicalize whitespace preceding a usage string.  */
Miles Bader's avatar
Miles Bader committed
342 343 344
	  state->pending_newlines = 2;
	  state->pending_spaces = 0;

345
	  /* Skip any spaces and newlines between the keyword and the
Miles Bader's avatar
Miles Bader committed
346
	     usage string.  */
347
	  int c;
Miles Bader's avatar
Miles Bader committed
348
	  do
349 350
	    c = getc (state->in_file);
	  while (c == ' ' || c == '\n');
Miles Bader's avatar
Miles Bader committed
351

352
	  /* Output the open-paren we just read.  */
353 354 355
	  if (c != '(')
	    fatal ("Missing '(' after keyword");
	  put_char (c, state);
356 357 358

	  /* Skip the function name and replace it with `fn'.  */
	  do
359 360 361 362 363 364
	    {
	      c = getc (state->in_file);
	      if (c == EOF)
		fatal ("Unexpected EOF after keyword");
	    }
	  while (c != ' ' && c != ')');
365

366 367
	  put_char ('f', state);
	  put_char ('n', state);
368

369
	  /* Put back the last character.  */
370
	  ungetc (c, state->in_file);
Miles Bader's avatar
Miles Bader committed
371 372 373 374 375 376 377 378 379
	}
    }
  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.  */
	{
380
	  const char *p;
Miles Bader's avatar
Miles Bader committed
381 382 383 384 385 386 387 388 389 390 391 392

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

	  state->cur_keyword_ptr = state->keyword;
	}

      put_char (ch, state);
    }
}


393
/* Skip a C string or C-style comment from INFILE, and return the
394
   byte that follows, or EOF.  COMMENT means skip a comment.  If
395
   PRINTFLAG is positive, output string contents to stdout.  If it is
396
   negative, store contents in buf.  Convert escape sequences \n and
397
   \t to newline and tab; discard \ followed by newline.
398
   If SAW_USAGE is non-null, then any occurrences of the string "usage:"
399 400
   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
401

402
static int
403 404
read_c_string_or_comment (FILE *infile, int printflag, bool comment,
			  bool *saw_usage)
Richard M. Stallman's avatar
Richard M. Stallman committed
405
{
406
  int c;
Miles Bader's avatar
Miles Bader committed
407 408 409
  struct rcsoc_state state;

  state.in_file = infile;
410
  state.buf_ptr = (printflag < 0 ? input_buffer : 0);
411
  state.out_file = (printflag > 0 ? stdout : 0);
Miles Bader's avatar
Miles Bader committed
412 413 414 415
  state.pending_spaces = 0;
  state.pending_newlines = 0;
  state.keyword = (saw_usage ? "usage:" : 0);
  state.cur_keyword_ptr = state.keyword;
416
  state.saw_keyword = false;
Miles Bader's avatar
Miles Bader committed
417 418

  c = getc (infile);
419
  if (comment)
420
    while (c_isspace (c))
Miles Bader's avatar
Miles Bader committed
421
      c = getc (infile);
422

Richard M. Stallman's avatar
Richard M. Stallman committed
423 424
  while (c != EOF)
    {
425
      while (c != EOF && (comment ? c != '*' : c != '"'))
Richard M. Stallman's avatar
Richard M. Stallman committed
426 427 428 429
	{
	  if (c == '\\')
	    {
	      c = getc (infile);
430
	      switch (c)
Richard M. Stallman's avatar
Richard M. Stallman committed
431
		{
432
		case '\n': case '\r':
Richard M. Stallman's avatar
Richard M. Stallman committed
433 434
		  c = getc (infile);
		  continue;
435 436
		case 'n': c = '\n'; break;
		case 't': c = '\t'; break;
Richard M. Stallman's avatar
Richard M. Stallman committed
437 438
		}
	    }
Pavel Janík's avatar
Pavel Janík committed
439

Miles Bader's avatar
Miles Bader committed
440
	  if (c == ' ')
Miles Bader's avatar
Miles Bader committed
441
	    state.pending_spaces++;
Miles Bader's avatar
Miles Bader committed
442 443
	  else if (c == '\n')
	    {
Miles Bader's avatar
Miles Bader committed
444 445
	      state.pending_newlines++;
	      state.pending_spaces = 0;
Miles Bader's avatar
Miles Bader committed
446 447
	    }
	  else
Miles Bader's avatar
Miles Bader committed
448
	    scan_keyword_or_put_char (c, &state);
Miles Bader's avatar
Miles Bader committed
449

Richard M. Stallman's avatar
Richard M. Stallman committed
450 451
	  c = getc (infile);
	}
452

453 454
      if (c != EOF)
	c = getc (infile);
Richard M. Stallman's avatar
Richard M. Stallman committed
455

456 457 458 459 460 461 462
      if (comment)
	{
	  if (c == '/')
	    {
	      c = getc (infile);
	      break;
	    }
Pavel Janík's avatar
Pavel Janík committed
463

Miles Bader's avatar
Miles Bader committed
464
	  scan_keyword_or_put_char ('*', &state);
465 466 467 468 469
	}
      else
	{
	  if (c != '"')
	    break;
Pavel Janík's avatar
Pavel Janík committed
470

471 472 473 474
	  /* If we had a "", concatenate the two strings.  */
	  c = getc (infile);
	}
    }
Pavel Janík's avatar
Pavel Janík committed
475

Richard M. Stallman's avatar
Richard M. Stallman committed
476
  if (printflag < 0)
Miles Bader's avatar
Miles Bader committed
477 478 479 480
    *state.buf_ptr = 0;

  if (saw_usage)
    *saw_usage = state.saw_keyword;
Richard M. Stallman's avatar
Richard M. Stallman committed
481 482 483

  return c;
}
484 485


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

490
static void
491
write_c_args (char *func, char *buf, int minargs, int maxargs)
Richard M. Stallman's avatar
Richard M. Stallman committed
492
{
493
  char *p;
494
  bool in_ident = false;
495
  char *ident_start UNINIT;
496
  ptrdiff_t ident_length = 0;
Richard M. Stallman's avatar
Richard M. Stallman committed
497

498
  fputs ("(fn", stdout);
499 500 501

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

Jim Blandy's avatar
Jim Blandy committed
503
  for (p = buf; *p; p++)
Richard M. Stallman's avatar
Richard M. Stallman committed
504
    {
505
      char c = *p;
Jim Blandy's avatar
Jim Blandy committed
506

507
      /* Notice when a new identifier starts.  */
508
      if ((c_isalnum (c) || c == '_')
Jim Blandy's avatar
Jim Blandy committed
509
	  != in_ident)
Richard M. Stallman's avatar
Richard M. Stallman committed
510
	{
Jim Blandy's avatar
Jim Blandy committed
511 512
	  if (!in_ident)
	    {
513
	      in_ident = true;
514
	      ident_start = p;
Jim Blandy's avatar
Jim Blandy committed
515 516
	    }
	  else
517
	    {
518
	      in_ident = false;
519 520
	      ident_length = p - ident_start;
	    }
Richard M. Stallman's avatar
Richard M. Stallman committed
521
	}
Jim Blandy's avatar
Jim Blandy committed
522

523 524 525
      /* Found the end of an argument, write out the last seen
	 identifier.  */
      if (c == ',' || c == ')')
526
	{
527 528
	  if (ident_length == 0)
	    {
529
	      error ("empty arg list for '%s' should be (void), not ()", func);
530 531 532
	      continue;
	    }

533 534 535
	  if (strncmp (ident_start, "void", ident_length) == 0)
	    continue;

536
	  putchar (' ');
537 538

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

541 542 543 544
	  minargs--;
	  maxargs--;

	  /* In C code, `default' is a reserved word, so we spell it
Paul Eggert's avatar
Paul Eggert committed
545
	     `defalt'; demangle that here.  */
546
	  if (ident_length == 6 && memcmp (ident_start, "defalt", 6) == 0)
547
	    fputs ("DEFAULT", stdout);
548 549 550
	  else
	    while (ident_length-- > 0)
	      {
551 552
		c = c_toupper (*ident_start++);
		if (c == '_')
553 554
		  /* Print underscore as hyphen.  */
		  c = '-';
555
		putchar (c);
556 557
	      }
	}
Richard M. Stallman's avatar
Richard M. Stallman committed
558
    }
559

560
  putchar (')');
Richard M. Stallman's avatar
Richard M. Stallman committed
561
}
Tom Tromey's avatar
Tom Tromey committed
562

563
/* The types of globals.  These are sorted roughly in decreasing alignment
564 565
   order to avoid allocation gaps, except that symbols and functions
   are last.  */
Tom Tromey's avatar
Tom Tromey committed
566 567
enum global_type
{
568 569
  INVALID,
  LISP_OBJECT,
570
  EMACS_INTEGER,
Tom Tromey's avatar
Tom Tromey committed
571
  BOOLEAN,
572
  SYMBOL,
Paul Eggert's avatar
Paul Eggert committed
573
  FUNCTION
Tom Tromey's avatar
Tom Tromey committed
574 575 576 577 578 579 580
};

/* A single global.  */
struct global
{
  enum global_type type;
  char *name;
581
  int flags;
582 583 584 585 586
  union
  {
    int value;
    char const *svalue;
  } v;
Tom Tromey's avatar
Tom Tromey committed
587 588
};

589
/* Bit values for FLAGS field from the above.  Applied for DEFUNs only.  */
Paul Eggert's avatar
Paul Eggert committed
590
enum { DEFUN_noreturn = 1, DEFUN_const = 2, DEFUN_noinline = 4 };
591

Tom Tromey's avatar
Tom Tromey committed
592 593
/* All the variable names we saw while scanning C sources in `-g'
   mode.  */
594 595 596
static ptrdiff_t num_globals;
static ptrdiff_t num_globals_allocated;
static struct global *globals;
Tom Tromey's avatar
Tom Tromey committed
597

598
static struct global *
599 600
add_global (enum global_type type, char const *name, int value,
	    char const *svalue)
Tom Tromey's avatar
Tom Tromey committed
601 602 603 604
{
  /* Ignore the one non-symbol that can occur.  */
  if (strcmp (name, "..."))
    {
605
      if (num_globals == num_globals_allocated)
Tom Tromey's avatar
Tom Tromey committed
606
	{
607 608 609 610 611 612 613 614 615
	  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
616 617
	}

618 619 620 621
      ++num_globals;

      ptrdiff_t namesize = strlen (name) + 1;
      char *buf = xmalloc (namesize + (svalue ? strlen (svalue) + 1 : 0));
Tom Tromey's avatar
Tom Tromey committed
622
      globals[num_globals - 1].type = type;
623
      globals[num_globals - 1].name = strcpy (buf, name);
624
      if (svalue)
625
	globals[num_globals - 1].v.svalue = strcpy (buf + namesize, svalue);
626 627
      else
	globals[num_globals - 1].v.value = value;
628 629
      globals[num_globals - 1].flags = 0;
      return globals + num_globals - 1;
Tom Tromey's avatar
Tom Tromey committed
630
    }
631
  return NULL;
Tom Tromey's avatar
Tom Tromey committed
632 633 634 635 636 637 638
}

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

640 641
  if (ga->type != gb->type)
    return ga->type - gb->type;
642

Paul Eggert's avatar
Paul Eggert committed
643
  /* Consider "nil" to be the least, so that iQnil is zero.  That
644
     way, Qnil's internal representation is zero, which is a bit faster.
645
     Similarly, consider "t" to be the second-least, and so forth.  */
Paul Eggert's avatar
Paul Eggert committed
646 647
  if (ga->type == SYMBOL)
    {
648 649 650 651 652 653 654 655 656 657 658 659 660 661
      /* Common symbols in decreasing popularity order.  */
      static char const commonsym[][8]
	= { "nil", "t", "unbound", "error", "lambda" };
      int ncommonsym = sizeof commonsym / sizeof *commonsym;
      int ai = ncommonsym, bi = ncommonsym;
      for (int i = 0; i < ncommonsym; i++)
	{
	  if (ga->name[0] == 'Q' && strcmp (ga->name + 1, commonsym[i]) == 0)
	    ai = i;
	  if (gb->name[0] == 'Q' && strcmp (gb->name + 1, commonsym[i]) == 0)
	    bi = i;
	}
      if (! (ai == ncommonsym && bi == ncommonsym))
	return ai - bi;
Paul Eggert's avatar
Paul Eggert committed
662 663
    }

Tom Tromey's avatar
Tom Tromey committed
664 665 666
  return strcmp (ga->name, gb->name);
}

667
static void
668
close_emacs_globals (ptrdiff_t num_symbols)
669
{
670 671 672 673 674 675
  printf (("};\n"
	   "extern struct emacs_globals globals;\n"
	   "\n"
	   "#ifndef DEFINE_SYMBOLS\n"
	   "extern\n"
	   "#endif\n"
676
	   "struct Lisp_Symbol lispsym[%td];\n"),
677
	  num_symbols);
678 679
}

680
static void
Tom Tromey's avatar
Tom Tromey committed
681 682
write_globals (void)
{
683
  ptrdiff_t i, j;
684
  bool seen_defun = false;
685 686
  ptrdiff_t symnum = 0;
  ptrdiff_t num_symbols = 0;
Tom Tromey's avatar
Tom Tromey committed
687
  qsort (globals, num_globals, sizeof (struct global), compare_globals);
688 689 690 691 692 693 694 695 696 697 698

  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);
699
	  free (globals[i].name);
700 701 702 703 704 705 706
	  i++;
	}
      num_symbols += globals[i].type == SYMBOL;
      globals[j++] = globals[i];
    }
  num_globals = j;

Tom Tromey's avatar
Tom Tromey committed
707 708
  for (i = 0; i < num_globals; ++i)
    {
709
      char const *type = 0;
Tom Tromey's avatar
Tom Tromey committed
710 711 712

      switch (globals[i].type)
	{
713
	case EMACS_INTEGER:
714
	  type = "intmax_t";
Tom Tromey's avatar
Tom Tromey committed
715 716
	  break;
	case BOOLEAN:
717
	  type = "bool";
Tom Tromey's avatar
Tom Tromey committed
718 719 720 721
	  break;
	case LISP_OBJECT:
	  type = "Lisp_Object";
	  break;
722
	case SYMBOL:
723 724 725
	case FUNCTION:
	  if (!seen_defun)
	    {
726
	      close_emacs_globals (num_symbols);
727
	      putchar ('\n');
728
	      seen_defun = true;
729 730
	    }
	  break;
Tom Tromey's avatar
Tom Tromey committed
731
	default:
732
	  fatal ("not a recognized DEFVAR_");
Tom Tromey's avatar
Tom Tromey committed
733 734
	}

735
      if (type)
736
	{
737 738 739
	  printf ("  %s f_%s;\n", type, globals[i].name);
	  printf ("#define %s globals.f_%s\n",
		  globals[i].name, globals[i].name);
740
	}
741
      else if (globals[i].type == SYMBOL)
742
	printf (("#define i%s %td\n"
743 744
		 "DEFINE_LISP_SYMBOL (%s)\n"),
		globals[i].name, symnum++, globals[i].name);
745 746
      else
	{
747
	  if (globals[i].flags & DEFUN_noreturn)
748
	    fputs ("_Noreturn ", stdout);
Paul Eggert's avatar
Paul Eggert committed
749 750
	  if (globals[i].flags & DEFUN_noinline)
	    fputs ("NO_INLINE ", stdout);
Paul Eggert's avatar
Paul Eggert committed
751

752
	  printf ("EXFUN (%s, ", globals[i].name);
753
	  if (globals[i].v.value == -1)
754
	    fputs ("MANY", stdout);
755
	  else if (globals[i].v.value == -2)
756
	    fputs ("UNEVALLED", stdout);
757
	  else
758
	    printf ("%d", globals[i].v.value);
759
	  putchar (')');
Paul Eggert's avatar
Paul Eggert committed
760

761 762
	  if (globals[i].flags & DEFUN_noreturn)
	    fputs (" ATTRIBUTE_COLD", stdout);
763
	  if (globals[i].flags & DEFUN_const)
764
	    fputs (" ATTRIBUTE_CONST", stdout);
Paul Eggert's avatar
Paul Eggert committed
765

766
	  puts (";");
767
	}
Tom Tromey's avatar
Tom Tromey committed
768 769
    }

770
  if (!seen_defun)
771 772 773 774
    close_emacs_globals (num_symbols);

  puts ("#ifdef DEFINE_SYMBOLS");
  puts ("static char const *const defsym_name[] = {");
775
  for (ptrdiff_t i = 0; i < num_globals; i++)
776 777
    if (globals[i].type == SYMBOL)
      printf ("\t\"%s\",\n", globals[i].v.svalue);
778 779
  puts ("};");
  puts ("#endif");
780 781

  puts ("#define Qnil builtin_lisp_symbol (0)");
Paul Eggert's avatar
Paul Eggert committed
782
  puts ("#if DEFINE_NON_NIL_Q_SYMBOL_MACROS");
783
  num_symbols = 0;
784
  for (ptrdiff_t i = 0; i < num_globals; i++)
785
    if (globals[i].type == SYMBOL && num_symbols++ != 0)
786
      printf ("# define %s builtin_lisp_symbol (%td)\n",
787 788
	      globals[i].name, num_symbols - 1);
  puts ("#endif");
Tom Tromey's avatar
Tom Tromey committed
789 790
}

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

797
static void
798
scan_c_file (char *filename, const char *mode)
Richard M. Stallman's avatar
Richard M. Stallman committed
799 800
{
  FILE *infile;
801
  char extension = filename[strlen (filename) - 1];
Richard M. Stallman's avatar
Richard M. Stallman committed
802

803
  if (extension == 'o')
Richard M. Stallman's avatar
Richard M. Stallman committed
804 805
    filename[strlen (filename) - 1] = 'c';

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

808 809
  if (infile == NULL && extension == 'o')
    {
810
      /* Try .m.  */
811 812 813
      filename[strlen (filename) - 1] = 'm';
      infile = fopen (filename, mode);
      if (infile == NULL)
814
        filename[strlen (filename) - 1] = 'c'; /* Don't confuse people.  */
815 816
    }

Richard M. Stallman's avatar
Richard M. Stallman committed
817 818 819
  if (infile == NULL)
    {
      perror (filename);
820
      exit (EXIT_FAILURE);
Richard M. Stallman's avatar
Richard M. Stallman committed
821 822
    }

Pavel Janík's avatar
Pavel Janík committed
823
  /* Reset extension to be able to detect duplicate files.  */
824
  filename[strlen (filename) - 1] = extension;
825
  scan_c_stream (infile);
826 827
}

828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844
/* 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;
}

845
static void
846 847 848 849
scan_c_stream (FILE *infile)
{
  int commas, minargs, maxargs;
  int c = '\n';
850

Richard M. Stallman's avatar
Richard M. Stallman committed
851 852
  while (!feof (infile))
    {
853 854 855 856
      bool doc_keyword = false;
      bool defunflag = false;
      bool defvarperbufferflag = false;
      bool defvarflag = false;
857
      enum global_type type = INVALID;
858
      static char name[sizeof input_buffer];
859

860
      if (c != '\n' && c != '\r')
Richard M. Stallman's avatar
Richard M. Stallman committed
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
	{
	  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);
879
	  if (c == 'S')
Tom Tromey's avatar
Tom Tromey committed
880
	    {
881 882 883 884 885 886 887 888 889 890
	      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
891
	    }
892 893 894 895 896 897 898 899 900 901 902
	  else if (c == 'V')
	    {
	      c = getc (infile);
	      if (c != 'A')
		continue;
	      c = getc (infile);
	      if (c != 'R')
		continue;
	      c = getc (infile);
	      if (c != '_')
		continue;
903

904
	      defvarflag = true;
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925

	      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
926 927 928 929 930 931 932 933 934 935 936 937 938 939
	}
      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;

940 941
      if (generate_globals
	  && (!defvarflag || defvarperbufferflag || type == INVALID)
942
	  && !defunflag && type != SYMBOL)
Tom Tromey's avatar
Tom Tromey committed
943 944
	continue;

Richard M. Stallman's avatar
Richard M. Stallman committed
945 946 947 948 949 950 951
      while (c != '(')
	{
	  if (c < 0)
	    goto eof;
	  c = getc (infile);
	}

952 953 954 955 956 957
      if (type != SYMBOL)
	{
	  /* Lisp variable or function name.  */
	  c = getc (infile);
	  if (c != '"')
	    continue;
958
	  c = read_c_string_or_comment (infile, -1, false, 0);
959
	}
960

Tom Tromey's avatar
Tom Tromey committed
961 962
      if (generate_globals)
	{
963
	  ptrdiff_t i = 0;
964
	  char const *svalue = 0;
Tom Tromey's avatar
Tom Tromey committed
965 966 967 968 969 970

	  /* Skip "," and whitespace.  */
	  do
	    {
	      c = getc (infile);
	    }
971
	  while (c == ',' || c_isspace (c));
Tom Tromey's avatar
Tom Tromey committed
972 973 974 975

	  /* Read in the identifier.  */
	  do
	    {
976 977
	      if (c < 0)
		goto eof;
978
	      input_buffer[i++] = c;
979 980
	      if (sizeof input_buffer <= i)
		fatal ("identifier too long");
Tom Tromey's avatar
Tom Tromey committed
981 982
	      c = getc (infile);
	    }
983 984
	  while (! (c == ',' || c_isspace (c)));

985 986
	  input_buffer[i] = '\0';
	  memcpy (name, input_buffer, i + 1);
987

988 989 990 991
	  if (type == SYMBOL)
	    {
	      do
		c = getc (infile);
992 993
	      while (c_isspace (c));

994 995
	      if (c != '"')
		continue;
996
	      c = read_c_string_or_comment (infile, -1, false, 0);
997
	      svalue = input_buffer;
998 999
	    }

1000 1001
	  if (!defunflag)
	    {
1002
	      add_global (type, name, 0, svalue);
1003 1004
	      continue;