Line data Source code
1 : /* Copyright (C) 1989-2025 Free Software Foundation, Inc.
2 : Written by James Clark (jjc@jclark.com)
3 :
4 : This file is part of groff, the GNU roff typesetting system.
5 :
6 : groff is free software; you can redistribute it and/or modify it under
7 : the terms of the GNU General Public License as published by the Free
8 : Software Foundation, either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : groff is distributed in the hope that it will be useful, but WITHOUT ANY
12 : WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 : FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 : for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 :
19 : #ifdef HAVE_CONFIG_H
20 : #include <config.h>
21 : #endif
22 :
23 : #include <assert.h>
24 : #include <errno.h>
25 : #include <stdio.h> // EOF, FILE, fclose(), ferror(), fflush(), fopen(),
26 : // fprintf(), getc(), printf(), putc(), rewind(),
27 : // setbuf(), sprintf(), stderr, stdin, stdout,
28 : // ungetc()
29 : #include <stdlib.h> // getenv(), qsort(), strtol()
30 : #include <string.h> // strcat(), strchr(), strcmp(), strcpy(),
31 : // strerror()
32 :
33 : #include "refer.h" // includes cset.h
34 : #include "refid.h"
35 : #include "ref.h"
36 : #include "token.h"
37 : #include "search.h"
38 : #include "command.h"
39 :
40 : extern "C" const char *Version_string;
41 :
42 : const char PRE_LABEL_MARKER = '\013';
43 : const char POST_LABEL_MARKER = '\014';
44 : const char LABEL_MARKER = '\015'; // label_type is added on
45 :
46 : #define FORCE_LEFT_BRACKET 04
47 : #define FORCE_RIGHT_BRACKET 010
48 :
49 : static FILE *outfp = stdout;
50 :
51 : string capitalize_fields;
52 : string reverse_fields;
53 : string abbreviate_fields;
54 : string period_before_last_name = ". ";
55 : string period_before_initial = ".";
56 : string period_before_hyphen = "";
57 : string period_before_other = ". ";
58 : string sort_fields;
59 : int annotation_field = -1;
60 : string annotation_macro;
61 : string discard_fields = "XYZ";
62 : string pre_label = "\\*([.";
63 : string post_label = "\\*(.]";
64 : string sep_label = ", ";
65 : int have_bibliography = 0;
66 : int accumulate = 0;
67 : int move_punctuation = 0;
68 : int abbreviate_label_ranges = 0;
69 : string label_range_indicator;
70 : int label_in_text = 1;
71 : int label_in_reference = 1;
72 : int date_as_label = 0;
73 : int sort_adjacent_labels = 0;
74 : // Join exactly two authors with this.
75 : string join_authors_exactly_two = " and ";
76 : // When there are more than two authors join the last two with this.
77 : string join_authors_last_two = ", and ";
78 : // Otherwise join authors with this.
79 : string join_authors_default = ", ";
80 : string separate_label_second_parts = ", ";
81 : // Use this string to represent that there are other authors.
82 : string et_al = " et al";
83 : // Use et al only if it can replace at least this many authors.
84 : int et_al_min_elide = 2;
85 : // Use et al only if the total number of authors is at least this.
86 : int et_al_min_total = 3;
87 :
88 :
89 : int compatible_flag = 0;
90 :
91 : int short_label_flag = 0;
92 :
93 : static bool recognize_R1_R2 = true;
94 :
95 : search_list database_list;
96 : int search_default = 1;
97 : static int default_database_loaded = 0;
98 :
99 : static reference **citation = 0;
100 : static int ncitations = 0;
101 : static int citation_max = 0;
102 :
103 : static reference **reference_hash_table = 0;
104 : static int hash_table_size;
105 : static int nreferences = 0;
106 :
107 : static int need_syncing = 0;
108 : string pending_line;
109 : string pending_lf_lines;
110 :
111 : static void output_pending_line();
112 : static unsigned immediately_handle_reference(const string &);
113 : static void immediately_output_references();
114 : static unsigned store_reference(const string &);
115 : static void divert_to_temporary_file();
116 : static reference *make_reference(const string &, unsigned *);
117 : static void usage(FILE *stream);
118 : static void do_file(const char *);
119 : static void split_punct(string &line, string &punct);
120 : static void output_citation_group(reference **v, int n, label_type,
121 : FILE *fp);
122 : static void possibly_load_default_database();
123 :
124 6 : int main(int argc, char **argv)
125 : {
126 6 : program_name = argv[0];
127 : static char stderr_buf[BUFSIZ];
128 6 : setbuf(stderr, stderr_buf);
129 6 : outfp = stdout;
130 6 : int finished_options = 0;
131 6 : int bib_flag = 0;
132 6 : int done_spec = 0;
133 :
134 : // TODO: Migrate to getopt_long; see, e.g., src/preproc/eqn/main.cpp.
135 8 : for (--argc, ++argv;
136 8 : !finished_options && argc > 0 && argv[0][0] == '-'
137 10 : && argv[0][1] != '\0';
138 : argv++, argc--) {
139 2 : const char *opt = argv[0] + 1;
140 4 : while (opt != 0 && *opt != '\0') {
141 2 : switch (*opt) {
142 0 : case 'C':
143 0 : compatible_flag = 1;
144 0 : opt++;
145 0 : break;
146 0 : case 'B':
147 0 : bib_flag = 1;
148 0 : label_in_reference = 0;
149 0 : label_in_text = 0;
150 0 : ++opt;
151 0 : if (*opt == '\0') {
152 0 : annotation_field = 'X';
153 0 : annotation_macro = "AP";
154 : }
155 0 : else if (csalnum(opt[0]) && opt[1] == '.' && opt[2] != '\0') {
156 0 : annotation_field = opt[0];
157 0 : annotation_macro = opt + 2;
158 : }
159 0 : opt = 0 /* nullptr */;
160 0 : break;
161 0 : case 'P':
162 0 : move_punctuation = 1;
163 0 : opt++;
164 0 : break;
165 0 : case 'R':
166 0 : recognize_R1_R2 = false;
167 0 : opt++;
168 0 : break;
169 0 : case 'S':
170 : // Not a very useful spec.
171 0 : set_label_spec("(A.n|Q)', '(D.y|D)");
172 0 : done_spec = 1;
173 0 : pre_label = " (";
174 0 : post_label = ")";
175 0 : sep_label = "; ";
176 0 : opt++;
177 0 : break;
178 0 : case 'V':
179 0 : do_verify = true;
180 0 : opt++;
181 0 : break;
182 0 : case 'f':
183 : {
184 0 : const char *num = 0;
185 0 : if (*++opt == '\0') {
186 0 : if (argc > 1) {
187 0 : num = *++argv;
188 0 : --argc;
189 : }
190 : else {
191 0 : error("command-line option 'f' requires an argument");
192 0 : usage(stderr);
193 0 : exit(2);
194 : }
195 : }
196 : else {
197 0 : num = opt;
198 0 : opt = 0 /* nullptr */;
199 : }
200 : const char *ptr;
201 0 : for (ptr = num; *ptr; ptr++)
202 0 : if (!csdigit(*ptr)) {
203 0 : error("invalid character '%1' in argument to command-line"
204 0 : " option 'f'; ignoring", *ptr);
205 0 : break;
206 : }
207 0 : if (*ptr == '\0') {
208 0 : string spec;
209 0 : spec = '%';
210 0 : spec += num;
211 0 : spec += '\0';
212 0 : set_label_spec(spec.contents());
213 0 : done_spec = 1;
214 : }
215 0 : break;
216 : }
217 0 : case 'b':
218 0 : label_in_text = 0;
219 0 : label_in_reference = 0;
220 0 : opt++;
221 0 : break;
222 1 : case 'e':
223 1 : accumulate = 1;
224 1 : opt++;
225 1 : break;
226 0 : case 'c':
227 0 : capitalize_fields = ++opt;
228 0 : opt = 0 /* nullptr */;
229 0 : break;
230 0 : case 'k':
231 : {
232 : char buf[5];
233 0 : if (csalpha(*++opt))
234 0 : buf[0] = *opt++;
235 : else {
236 0 : if (*opt != '\0')
237 0 : error("invalid field name '%1' in argument to"
238 0 : " command-line option 'k'; assuming 'L'", *opt++);
239 0 : buf[0] = 'L';
240 : }
241 0 : buf[1] = '~';
242 0 : buf[2] = '%';
243 0 : buf[3] = 'a';
244 0 : buf[4] = '\0';
245 0 : set_label_spec(buf);
246 0 : done_spec = 1;
247 : }
248 0 : break;
249 0 : case 'a':
250 : {
251 : const char *ptr;
252 0 : for (ptr = ++opt; *ptr; ptr++)
253 0 : if (!csdigit(*ptr)) {
254 0 : error("invalid integer '%1' in argument to command-line"
255 0 : " option 'a'; ignoring", opt);
256 0 : break;
257 : }
258 0 : if (*ptr == '\0') {
259 0 : reverse_fields = 'A';
260 0 : reverse_fields += opt;
261 : }
262 0 : opt = 0 /* nullptr */;
263 : }
264 0 : break;
265 0 : case 'i':
266 0 : linear_ignore_fields = ++opt;
267 0 : opt = 0 /* nullptr */;
268 0 : break;
269 0 : case 'l':
270 : {
271 : char buf[INT_DIGITS*2 + 11]; // A.n+2D.y-3%a
272 0 : strcpy(buf, "A.n");
273 0 : if (*++opt != '\0' && *opt != ',') {
274 : char *ptr;
275 0 : long n = strtol(opt, &ptr, 10);
276 0 : if (ptr == opt) {
277 0 : error("invalid integer '%1' in argument to command-line"
278 0 : " option 'l'; ignoring", opt);
279 0 : opt = 0 /* nullptr */;
280 0 : break;
281 : }
282 0 : if (n < 0)
283 0 : n = 0;
284 0 : opt = ptr;
285 0 : sprintf(strchr(buf, '\0'), "+%ld", n);
286 : }
287 0 : strcat(buf, "D.y");
288 0 : if (',' == *opt)
289 0 : opt++;
290 0 : if (*opt != '\0') {
291 : char *ptr;
292 0 : long n = strtol(opt, &ptr, 10);
293 0 : if (ptr == opt) {
294 0 : error("invalid integer '%1' in argument to command-line"
295 0 : " option 'l'; ignoring", opt);
296 0 : opt = 0 /* nullptr */;
297 0 : break;
298 : }
299 0 : if (n < 0)
300 0 : n = 0;
301 0 : sprintf(strchr(buf, '\0'), "-%ld", n);
302 0 : opt = ptr;
303 0 : if (*opt != '\0') {
304 0 : error("argument to 'l' option not of form 'm,n';"
305 : " ignoring");
306 0 : while ((*opt != '\0') && (*opt != ' '))
307 0 : opt++;
308 0 : break;
309 : }
310 : }
311 0 : strcat(buf, "%a");
312 0 : if (!set_label_spec(buf))
313 0 : assert(0 == "set_label_spec() failed");
314 0 : done_spec = 1;
315 : }
316 0 : break;
317 0 : case 'n':
318 0 : search_default = 0;
319 0 : opt++;
320 0 : break;
321 1 : case 'p':
322 : {
323 1 : const char *filename = 0;
324 1 : if ('\0' == *++opt) {
325 1 : if (argc > 1) {
326 1 : filename = *++argv;
327 1 : argc--;
328 : }
329 : else {
330 0 : error("option 'p' requires an argument");
331 0 : usage(stderr);
332 0 : exit(2);
333 : }
334 : }
335 : else {
336 0 : filename = opt;
337 0 : opt = 0 /* nullptr */;
338 : }
339 1 : database_list.add_file(filename);
340 : }
341 1 : break;
342 0 : case 's':
343 0 : if ('\0' == *++opt)
344 0 : sort_fields = "AD";
345 : else {
346 0 : sort_fields = opt;
347 0 : opt = 0 /* nullptr */;
348 : }
349 0 : accumulate = 1;
350 0 : break;
351 0 : case 't':
352 : {
353 : char *ptr;
354 0 : long n = strtol(opt, &ptr, 10);
355 0 : if (ptr == opt) {
356 0 : error("invalid integer '%1' in argument to command-line"
357 0 : " option 't'; ignoring", opt);
358 0 : opt = 0 /* nullptr */;
359 0 : break;
360 : }
361 0 : if (n < 1)
362 0 : n = 1;
363 0 : linear_truncate_len = int(n);
364 0 : opt = ptr;
365 0 : break;
366 : }
367 0 : case '-':
368 0 : if (opt[1] == '\0') {
369 0 : finished_options = 1;
370 0 : opt++;
371 0 : break;
372 : }
373 0 : if (strcmp(opt, "-version") == 0) {
374 : case 'v':
375 0 : printf("GNU refer (groff) version %s\n", Version_string);
376 0 : exit(EXIT_SUCCESS);
377 : break;
378 : }
379 0 : if (strcmp(opt, "-help") == 0) {
380 0 : usage(stdout);
381 0 : exit(EXIT_SUCCESS);
382 : break;
383 : }
384 : // fall through
385 : default:
386 0 : error("unrecognized option '%1'", opt);
387 0 : usage(stderr);
388 0 : exit(2);
389 : break;
390 : }
391 : }
392 : }
393 6 : if (!done_spec)
394 6 : set_label_spec("%1");
395 6 : if (argc <= 0) {
396 6 : if (bib_flag)
397 0 : do_bib("-");
398 : else
399 6 : do_file("-");
400 : }
401 : else {
402 0 : for (int i = 0; i < argc; i++) {
403 0 : if (bib_flag)
404 0 : do_bib(argv[i]);
405 : else
406 0 : do_file(argv[i]);
407 : }
408 : }
409 6 : if (accumulate)
410 0 : output_references();
411 6 : if (ferror(stdout))
412 0 : fatal("error status on standard output stream");
413 6 : if (fflush(stdout) < 0)
414 0 : fatal("cannot flush standard output stream: %1", strerror(errno));
415 6 : return 0;
416 : }
417 :
418 0 : static void usage(FILE *stream)
419 : {
420 0 : fprintf(stream,
421 : "usage: %s [-bCenPRS] [-aN] [-cXYZ] [-fN] [-iXYZ] [-kX] [-lM,N]"
422 : " [-p db-file] [-sXYZ] [-tN] [-Bl.m] [file ...]\n"
423 : "usage: %s {-v | --version}\n"
424 : "usage: %s --help\n",
425 : program_name, program_name, program_name);
426 0 : if (stdout == stream)
427 0 : fputs("\n"
428 : "GNU refer is a troff(1) preprocessor that prepares bibliographic\n"
429 : "citations by looking up keywords specified in a roff(7) document,\n"
430 : "obviating the need to type such annotations, and permitting the\n"
431 : "citation style in formatted output to be altered independently and\n"
432 : "systematically. See the refer(1) manual page.\n",
433 : stream);
434 0 : }
435 :
436 0 : static void possibly_load_default_database()
437 : {
438 0 : if (search_default && !default_database_loaded) {
439 0 : char *filename = getenv("REFER");
440 0 : if (filename)
441 0 : database_list.add_file(filename);
442 : else
443 0 : database_list.add_file(DEFAULT_INDEX, 1);
444 0 : default_database_loaded = 1;
445 : }
446 0 : }
447 :
448 0 : static bool is_list(const string &str)
449 : {
450 0 : const char *start = str.contents();
451 0 : const char *end = start + str.length();
452 0 : while (end > start && csspace(end[-1]))
453 0 : end--;
454 0 : while (start < end && csspace(*start))
455 0 : start++;
456 0 : return end - start == 6 && memcmp(start, "$LIST$", 6) == 0;
457 : }
458 :
459 6 : static void do_file(const char *filename)
460 : {
461 : FILE *fp;
462 6 : if (strcmp(filename, "-") == 0) {
463 6 : fp = stdin;
464 : }
465 : else {
466 0 : errno = 0;
467 0 : fp = fopen(filename, "r");
468 0 : if (fp == 0) {
469 0 : error("cannot open '%1': %2", filename, strerror(errno));
470 0 : return;
471 : }
472 : }
473 12 : string fn(filename);
474 6 : fn += '\0';
475 6 : normalize_file_name_for_lf_request(fn);
476 6 : current_lineno = 1;
477 6 : current_filename = fn.contents();
478 6 : (void) fprintf(outfp, ".lf %d %s%s\n", current_lineno,
479 6 : ('"' == current_filename[0]) ? "" : "\"", current_filename);
480 12 : string line;
481 : for (;;) {
482 15 : line.clear();
483 : for (;;) {
484 37 : int c = getc(fp);
485 37 : if (EOF == c) {
486 6 : if (line.length() > 0)
487 0 : line += '\n';
488 6 : break;
489 : }
490 31 : if (is_invalid_input_char(c))
491 1 : error("invalid input character code %1; ignoring", c);
492 : else {
493 30 : line += c;
494 30 : if ('\n' == c)
495 9 : break;
496 : }
497 22 : }
498 15 : int len = line.length();
499 15 : if (len == 0)
500 6 : break;
501 9 : current_lineno++;
502 9 : if (len >= 2 && line[0] == '.' && line[1] == '[') {
503 0 : int start_lineno = current_lineno;
504 0 : bool at_start_of_line = true;
505 0 : string str;
506 0 : string post;
507 0 : string pre(line.contents() + 2, line.length() - 3);
508 : for (;;) {
509 0 : int c = getc(fp);
510 0 : if (EOF == c) {
511 0 : error_with_file_and_line(current_filename, start_lineno,
512 : "missing '.]' line");
513 0 : break;
514 : }
515 0 : if (at_start_of_line)
516 0 : current_lineno++;
517 0 : if (at_start_of_line && '.' == c) {
518 0 : int d = getc(fp);
519 0 : if (d == ']') {
520 0 : while ((d = getc(fp)) != '\n' && d != EOF) {
521 0 : if (is_invalid_input_char(d))
522 0 : error("invalid input character code %1; ignoring", d);
523 : else
524 0 : post += d;
525 : }
526 0 : break;
527 : }
528 0 : if (d != EOF)
529 0 : ungetc(d, fp);
530 : }
531 0 : if (is_invalid_input_char(c))
532 0 : error("invalid input character code %1; ignoring", c);
533 : else
534 0 : str += c;
535 0 : at_start_of_line = ('\n' == c);
536 0 : }
537 0 : if (is_list(str)) {
538 0 : output_pending_line();
539 0 : if (accumulate)
540 0 : output_references();
541 : else
542 0 : error("found '$LIST$' but not accumulating references");
543 : }
544 : else {
545 0 : unsigned flags = (accumulate
546 0 : ? store_reference(str)
547 0 : : immediately_handle_reference(str));
548 0 : if (label_in_text) {
549 0 : if (accumulate && outfp == stdout)
550 0 : divert_to_temporary_file();
551 0 : if (pending_line.length() == 0) {
552 0 : warning("cannot attach citation to empty previous line");
553 : }
554 : else
555 0 : pending_line.set_length(pending_line.length() - 1);
556 0 : string punct;
557 0 : if (move_punctuation)
558 0 : split_punct(pending_line, punct);
559 0 : int have_text = pre.length() > 0 || post.length() > 0;
560 0 : label_type lt = label_type(flags & ~(FORCE_LEFT_BRACKET
561 : |FORCE_RIGHT_BRACKET));
562 0 : if ((flags & FORCE_LEFT_BRACKET) || !have_text)
563 0 : pending_line += PRE_LABEL_MARKER;
564 0 : pending_line += pre;
565 0 : char lm = LABEL_MARKER + int(lt);
566 0 : pending_line += lm;
567 0 : pending_line += post;
568 0 : if ((flags & FORCE_RIGHT_BRACKET) || !have_text)
569 0 : pending_line += POST_LABEL_MARKER;
570 0 : pending_line += punct;
571 0 : pending_line += '\n';
572 : }
573 : }
574 0 : need_syncing = 1;
575 : }
576 9 : else if (len >= 4
577 6 : && '.' == line[0] && 'l' == line[1] && 'f' == line[2]
578 15 : && (compatible_flag || '\n' == line[3] || ' ' == line[3]))
579 : {
580 0 : pending_lf_lines += line;
581 0 : line += '\0';
582 0 : if (interpret_lf_request_arguments(line.contents() + 3))
583 0 : current_lineno--;
584 : }
585 9 : else if (recognize_R1_R2
586 9 : && len >= 4
587 6 : && '.' == line[0] && 'R' == line[1] && '1' == line[2]
588 18 : && (compatible_flag || '\n' == line[3] || ' ' == line[3]))
589 : {
590 6 : line.clear();
591 6 : int start_lineno = current_lineno;
592 6 : bool at_start_of_line = true;
593 : for (;;) {
594 261 : int c = getc(fp);
595 261 : if (c != EOF && at_start_of_line)
596 14 : current_lineno++;
597 261 : if (at_start_of_line && '.' == c) {
598 6 : c = getc(fp);
599 6 : if ('R' == c) {
600 6 : c = getc(fp);
601 6 : if ('2' == c) {
602 6 : c = getc(fp);
603 6 : if (compatible_flag || ' ' == c || '\n' == c || EOF == c)
604 : {
605 6 : while (c != EOF && c != '\n')
606 0 : c = getc(fp);
607 6 : break;
608 : }
609 : else {
610 0 : line += '.';
611 0 : line += 'R';
612 0 : line += '2';
613 : }
614 : }
615 : else {
616 0 : line += '.';
617 0 : line += 'R';
618 : }
619 : }
620 : else
621 0 : line += '.';
622 : }
623 255 : if (EOF == c) {
624 0 : error_with_file_and_line(current_filename, start_lineno,
625 : "missing '.R2' line");
626 0 : break;
627 : }
628 255 : if (is_invalid_input_char(c))
629 1 : error_with_file_and_line(current_filename, start_lineno,
630 : "invalid input character code %1;"
631 2 : " ignoring", c);
632 : else {
633 254 : line += c;
634 254 : at_start_of_line = ('\n' == c);
635 : }
636 255 : }
637 6 : output_pending_line();
638 6 : if (accumulate)
639 1 : output_references();
640 : else
641 5 : nreferences = 0;
642 6 : process_commands(line, current_filename, start_lineno + 1);
643 6 : need_syncing = 1;
644 : }
645 : else {
646 3 : output_pending_line();
647 3 : pending_line = line;
648 : }
649 9 : }
650 6 : need_syncing = 0;
651 6 : output_pending_line();
652 6 : if (fp != stdin)
653 0 : fclose(fp);
654 : }
655 :
656 : class label_processing_state {
657 : enum {
658 : NORMAL,
659 : PENDING_LABEL,
660 : PENDING_LABEL_POST,
661 : PENDING_LABEL_POST_PRE,
662 : PENDING_POST
663 : } state;
664 : label_type type; // type of pending labels
665 : int count; // number of pending labels
666 : reference **rptr; // pointer to next reference
667 : int rcount; // number of references left
668 : FILE *fp;
669 : int handle_pending(int c);
670 : public:
671 : label_processing_state(reference **, int, FILE *);
672 : ~label_processing_state();
673 : void process(int c);
674 : };
675 :
676 15 : static void output_pending_line()
677 : {
678 15 : if (label_in_text && !accumulate && ncitations > 0) {
679 0 : label_processing_state state(citation, ncitations, outfp);
680 0 : int len = pending_line.length();
681 0 : for (int i = 0; i < len; i++)
682 0 : state.process((unsigned char)(pending_line[i]));
683 : }
684 : else
685 15 : put_string(pending_line, outfp);
686 15 : pending_line.clear();
687 15 : if (pending_lf_lines.length() > 0) {
688 0 : put_string(pending_lf_lines, outfp);
689 0 : pending_lf_lines.clear();
690 : }
691 15 : if (!accumulate)
692 13 : immediately_output_references();
693 15 : if (need_syncing) {
694 1 : fprintf(outfp, ".lf %d %s%s\n", current_lineno,
695 1 : ('"' == current_filename[0]) ? "" : "\"", current_filename);
696 1 : need_syncing = 0;
697 : }
698 15 : }
699 :
700 0 : static void split_punct(string &line, string &punct)
701 : {
702 0 : const char *start = line.contents();
703 0 : const char *end = start + line.length();
704 0 : const char *ptr = start;
705 0 : const char *last_token_start = 0;
706 : for (;;) {
707 0 : if (ptr >= end)
708 0 : break;
709 0 : last_token_start = ptr;
710 0 : if (*ptr == PRE_LABEL_MARKER || *ptr == POST_LABEL_MARKER
711 0 : || (*ptr >= LABEL_MARKER
712 0 : && *ptr < LABEL_MARKER + N_LABEL_TYPES))
713 0 : ptr++;
714 0 : else if (!get_token(&ptr, end))
715 0 : break;
716 : }
717 0 : if (last_token_start) {
718 0 : const token_info *ti = lookup_token(last_token_start, end);
719 0 : if (ti->is_punct()) {
720 0 : punct.append(last_token_start, end - last_token_start);
721 0 : line.set_length(last_token_start - start);
722 : }
723 : }
724 0 : }
725 :
726 0 : static void divert_to_temporary_file()
727 : {
728 0 : outfp = xtmpfile();
729 0 : }
730 :
731 5 : static void store_citation(reference *ref)
732 : {
733 5 : if (ncitations >= citation_max) {
734 2 : if (citation == 0)
735 2 : citation = new reference*[citation_max = 100];
736 : else {
737 0 : reference **old_citation = citation;
738 0 : citation_max *= 2;
739 0 : citation = new reference *[citation_max];
740 0 : memcpy(citation, old_citation, ncitations*sizeof(reference *));
741 0 : delete[] old_citation;
742 : }
743 : }
744 5 : citation[ncitations++] = ref;
745 5 : }
746 :
747 0 : static unsigned store_reference(const string &str)
748 : {
749 0 : if (reference_hash_table == 0) {
750 0 : reference_hash_table = new reference *[17];
751 0 : hash_table_size = 17;
752 0 : for (int i = 0; i < hash_table_size; i++)
753 0 : reference_hash_table[i] = 0;
754 : }
755 : unsigned flags;
756 0 : reference *ref = make_reference(str, &flags);
757 0 : ref->compute_hash_code();
758 0 : unsigned h = ref->hash();
759 : reference **ptr;
760 0 : for (ptr = reference_hash_table + (h % hash_table_size);
761 0 : *ptr != 0;
762 0 : ((ptr == reference_hash_table)
763 0 : ? (ptr = reference_hash_table + hash_table_size - 1)
764 : : --ptr))
765 0 : if (same_reference(**ptr, *ref))
766 0 : break;
767 0 : if (*ptr != 0) {
768 0 : if (ref->is_merged())
769 0 : warning("fields ignored because reference already used");
770 0 : delete ref;
771 0 : ref = *ptr;
772 : }
773 : else {
774 0 : *ptr = ref;
775 0 : ref->set_number(nreferences);
776 0 : nreferences++;
777 0 : ref->pre_compute_label();
778 0 : ref->compute_sort_key();
779 0 : if (nreferences*2 >= hash_table_size) {
780 : // Rehash it.
781 0 : reference **old_table = reference_hash_table;
782 0 : int old_size = hash_table_size;
783 0 : hash_table_size = next_size(hash_table_size);
784 0 : reference_hash_table = new reference*[hash_table_size];
785 : int i;
786 0 : for (i = 0; i < hash_table_size; i++)
787 0 : reference_hash_table[i] = 0;
788 0 : for (i = 0; i < old_size; i++)
789 0 : if (old_table[i]) {
790 : reference **p;
791 0 : for (p = (reference_hash_table
792 0 : + (old_table[i]->hash() % hash_table_size));
793 0 : *p;
794 0 : ((p == reference_hash_table)
795 0 : ? (p = reference_hash_table + hash_table_size - 1)
796 : : --p))
797 : ;
798 0 : *p = old_table[i];
799 : }
800 0 : delete[] old_table;
801 : }
802 : }
803 0 : if (label_in_text)
804 0 : store_citation(ref);
805 0 : return flags;
806 : }
807 :
808 5 : unsigned immediately_handle_reference(const string &str)
809 : {
810 : unsigned flags;
811 5 : reference *ref = make_reference(str, &flags);
812 5 : ref->set_number(nreferences);
813 5 : if (label_in_text || label_in_reference) {
814 5 : ref->pre_compute_label();
815 5 : ref->immediate_compute_label();
816 : }
817 5 : nreferences++;
818 5 : store_citation(ref);
819 5 : return flags;
820 : }
821 :
822 18 : static void immediately_output_references()
823 : {
824 23 : for (int i = 0; i < ncitations; i++) {
825 5 : reference *ref = citation[i];
826 5 : if (label_in_reference) {
827 5 : fputs(".ds [F ", outfp);
828 5 : const string &label = ref->get_label(NORMAL_LABEL);
829 5 : if (label.length() > 0
830 5 : && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
831 0 : putc('"', outfp);
832 5 : put_string(label, outfp);
833 5 : putc('\n', outfp);
834 : }
835 5 : ref->output(outfp);
836 5 : delete ref;
837 : }
838 18 : ncitations = 0;
839 18 : }
840 :
841 0 : static void output_citation_group(reference **v, int n, label_type type,
842 : FILE *fp)
843 : {
844 0 : if (sort_adjacent_labels) {
845 : // Do an insertion sort. Usually n will be very small.
846 0 : for (int i = 1; i < n; i++) {
847 0 : int num = v[i]->get_number();
848 0 : reference *temp = v[i];
849 : int j;
850 0 : for (j = i - 1; j >= 0 && v[j]->get_number() > num; j--)
851 0 : v[j + 1] = v[j];
852 0 : v[j + 1] = temp;
853 : }
854 : }
855 : // This messes up if !accumulate.
856 0 : if (accumulate && n > 1) {
857 : // remove duplicates
858 0 : int j = 1;
859 0 : for (int i = 1; i < n; i++)
860 0 : if (v[i]->get_label(type) != v[i - 1]->get_label(type))
861 0 : v[j++] = v[i];
862 0 : n = j;
863 : }
864 0 : string merged_label;
865 0 : for (int i = 0; i < n; i++) {
866 0 : int nmerged = v[i]->merge_labels(v + i + 1, n - i - 1, type,
867 : merged_label);
868 0 : if (nmerged > 0) {
869 0 : put_string(merged_label, fp);
870 0 : i += nmerged;
871 : }
872 : else
873 0 : put_string(v[i]->get_label(type), fp);
874 0 : if (i < n - 1)
875 0 : put_string(sep_label, fp);
876 : }
877 0 : }
878 :
879 :
880 0 : label_processing_state::label_processing_state(reference **p, int n,
881 0 : FILE *f)
882 0 : : state(NORMAL), count(0), rptr(p), rcount(n), fp(f)
883 : {
884 0 : }
885 :
886 0 : label_processing_state::~label_processing_state()
887 : {
888 0 : int handled = handle_pending(EOF);
889 0 : assert(!handled);
890 0 : assert(rcount == 0);
891 0 : }
892 :
893 0 : int label_processing_state::handle_pending(int c)
894 : {
895 0 : switch (state) {
896 0 : case NORMAL:
897 0 : break;
898 0 : case PENDING_LABEL:
899 0 : if (POST_LABEL_MARKER == c) {
900 0 : state = PENDING_LABEL_POST;
901 0 : return 1;
902 : }
903 : else {
904 0 : output_citation_group(rptr, count, type, fp);
905 0 : rptr += count ;
906 0 : rcount -= count;
907 0 : state = NORMAL;
908 : }
909 0 : break;
910 0 : case PENDING_LABEL_POST:
911 0 : if (PRE_LABEL_MARKER == c) {
912 0 : state = PENDING_LABEL_POST_PRE;
913 0 : return 1;
914 : }
915 : else {
916 0 : output_citation_group(rptr, count, type, fp);
917 0 : rptr += count;
918 0 : rcount -= count;
919 0 : put_string(post_label, fp);
920 0 : state = NORMAL;
921 : }
922 0 : break;
923 0 : case PENDING_LABEL_POST_PRE:
924 0 : if (c >= LABEL_MARKER
925 0 : && c < LABEL_MARKER + N_LABEL_TYPES
926 0 : && c - LABEL_MARKER == type) {
927 0 : count += 1;
928 0 : state = PENDING_LABEL;
929 0 : return 1;
930 : }
931 : else {
932 0 : output_citation_group(rptr, count, type, fp);
933 0 : rptr += count;
934 0 : rcount -= count;
935 0 : put_string(sep_label, fp);
936 0 : state = NORMAL;
937 : }
938 0 : break;
939 0 : case PENDING_POST:
940 0 : if (PRE_LABEL_MARKER == c) {
941 0 : put_string(sep_label, fp);
942 0 : state = NORMAL;
943 0 : return 1;
944 : }
945 : else {
946 0 : put_string(post_label, fp);
947 0 : state = NORMAL;
948 : }
949 0 : break;
950 : }
951 0 : return 0;
952 : }
953 :
954 0 : void label_processing_state::process(int c)
955 : {
956 0 : if (handle_pending(c))
957 0 : return;
958 0 : assert(state == NORMAL);
959 0 : switch (c) {
960 0 : case PRE_LABEL_MARKER:
961 0 : put_string(pre_label, fp);
962 0 : state = NORMAL;
963 0 : break;
964 0 : case POST_LABEL_MARKER:
965 0 : state = PENDING_POST;
966 0 : break;
967 0 : case LABEL_MARKER:
968 : case LABEL_MARKER + 1:
969 0 : count = 1;
970 0 : state = PENDING_LABEL;
971 0 : type = label_type(c - LABEL_MARKER);
972 0 : break;
973 0 : default:
974 0 : state = NORMAL;
975 0 : putc(c, fp);
976 0 : break;
977 : }
978 : }
979 :
980 : extern "C" {
981 :
982 0 : int rcompare(const void *p1, const void *p2)
983 : {
984 : // XXX: Would it make more sense to make non-const copies of p1, p2?
985 0 : return compare_reference(
986 : **static_cast<reference **>(const_cast<void *>(p1)),
987 0 : **static_cast<reference **>(const_cast<void *>(p2)));
988 : }
989 :
990 : }
991 :
992 1 : void output_references()
993 : {
994 1 : assert(accumulate);
995 1 : if (!hash_table_size) {
996 1 : if (have_bibliography)
997 0 : error("nothing to reference (probably 'bibliography' before"
998 : " 'sort')");
999 1 : accumulate = 0;
1000 1 : nreferences = 0;
1001 1 : return;
1002 : }
1003 0 : if (nreferences > 0) {
1004 0 : int j = 0;
1005 : int i;
1006 0 : for (i = 0; i < hash_table_size; i++)
1007 0 : if (reference_hash_table[i] != 0)
1008 0 : reference_hash_table[j++] = reference_hash_table[i];
1009 0 : assert(j == nreferences);
1010 0 : for (; j < hash_table_size; j++)
1011 0 : reference_hash_table[j] = 0;
1012 0 : qsort(reference_hash_table, nreferences, sizeof(reference*),
1013 : rcompare);
1014 0 : for (i = 0; i < nreferences; i++)
1015 0 : reference_hash_table[i]->set_number(i);
1016 0 : compute_labels(reference_hash_table, nreferences);
1017 : }
1018 0 : if (outfp != stdout) {
1019 0 : rewind(outfp);
1020 : {
1021 0 : label_processing_state state(citation, ncitations, stdout);
1022 : int c;
1023 0 : while ((c = getc(outfp)) != EOF)
1024 0 : state.process(c);
1025 : }
1026 0 : ncitations = 0;
1027 0 : fclose(outfp);
1028 0 : outfp = stdout;
1029 : }
1030 0 : if (nreferences > 0) {
1031 0 : fputs(".]<\n", outfp);
1032 0 : for (int i = 0; i < nreferences; i++) {
1033 0 : if (sort_fields.length() > 0)
1034 0 : reference_hash_table[i]->print_sort_key_comment(outfp);
1035 0 : if (label_in_reference) {
1036 0 : fputs(".ds [F ", outfp);
1037 : const string &label
1038 0 : = reference_hash_table[i]->get_label(NORMAL_LABEL);
1039 0 : if (label.length() > 0
1040 0 : && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
1041 0 : putc('"', outfp);
1042 0 : put_string(label, outfp);
1043 0 : putc('\n', outfp);
1044 : }
1045 0 : reference_hash_table[i]->output(outfp);
1046 0 : delete reference_hash_table[i];
1047 0 : reference_hash_table[i] = 0;
1048 : }
1049 0 : fputs(".]>\n", outfp);
1050 0 : nreferences = 0;
1051 : }
1052 0 : clear_labels();
1053 : }
1054 :
1055 0 : static reference *find_reference(const char *query, int query_len)
1056 : {
1057 : // This is so that error messages look better.
1058 0 : while (query_len > 0 && csspace(query[query_len - 1]))
1059 0 : query_len--;
1060 0 : string str;
1061 0 : for (int i = 0; i < query_len; i++)
1062 0 : str += query[i] == '\n' ? ' ' : query[i];
1063 0 : str += '\0';
1064 0 : possibly_load_default_database();
1065 0 : search_list_iterator iter(&database_list, str.contents());
1066 0 : reference_id rid;
1067 : const char *start;
1068 : int len;
1069 0 : if (!iter.next(&start, &len, &rid)) {
1070 0 : error("no reference matches '%1'", str.contents());
1071 0 : return 0 /* nullptr */;
1072 : }
1073 0 : const char *end = start + len;
1074 0 : while (start < end) {
1075 0 : if (*start == '%')
1076 0 : break;
1077 0 : while (start < end && *start++ != '\n')
1078 : ;
1079 : }
1080 0 : if (start >= end) {
1081 0 : error("reference matching '%1' has no fields",
1082 0 : str.contents());
1083 0 : return 0 /* nullptr */;
1084 : }
1085 0 : reference *result = new reference(start, end - start, &rid);
1086 0 : if (iter.next(&start, &len, &rid))
1087 0 : warning("multiple references match '%1'", str.contents());
1088 0 : return result;
1089 : }
1090 :
1091 5 : static reference *make_reference(const string &str, unsigned *flagsp)
1092 : {
1093 5 : const char *start = str.contents();
1094 5 : const char *end = start + str.length();
1095 5 : const char *ptr = start;
1096 5 : while (ptr < end) {
1097 5 : if (*ptr == '%')
1098 5 : break;
1099 0 : while (ptr < end && *ptr++ != '\n')
1100 : ;
1101 : }
1102 5 : *flagsp = 0;
1103 5 : for (; start < ptr; start++) {
1104 0 : if (*start == '#')
1105 0 : *flagsp = (SHORT_LABEL | (*flagsp & (FORCE_RIGHT_BRACKET
1106 : | FORCE_LEFT_BRACKET)));
1107 0 : else if (*start == '[')
1108 0 : *flagsp |= FORCE_LEFT_BRACKET;
1109 0 : else if (*start == ']')
1110 0 : *flagsp |= FORCE_RIGHT_BRACKET;
1111 0 : else if (!csspace(*start))
1112 0 : break;
1113 : }
1114 5 : if (start >= end) {
1115 0 : error("empty reference");
1116 0 : return new reference;
1117 : }
1118 5 : reference *database_ref = 0;
1119 5 : if (start < ptr)
1120 0 : database_ref = find_reference(start, ptr - start);
1121 5 : reference *inline_ref = 0;
1122 5 : if (ptr < end)
1123 5 : inline_ref = new reference(ptr, end - ptr);
1124 5 : if (inline_ref) {
1125 5 : if (database_ref) {
1126 0 : database_ref->merge(*inline_ref);
1127 0 : delete inline_ref;
1128 0 : return database_ref;
1129 : }
1130 : else
1131 5 : return inline_ref;
1132 : }
1133 0 : else if (database_ref)
1134 0 : return database_ref;
1135 : else
1136 0 : return new reference;
1137 : }
1138 :
1139 5 : static void do_ref(const string &str)
1140 : {
1141 5 : if (accumulate)
1142 0 : (void) store_reference(str);
1143 : else {
1144 5 : (void) immediately_handle_reference(str);
1145 5 : immediately_output_references();
1146 : }
1147 5 : }
1148 :
1149 0 : static void trim_blanks(string &str)
1150 : {
1151 0 : const char *start = str.contents();
1152 0 : const char *end = start + str.length();
1153 0 : while (end > start && end[-1] != '\n' && csspace(end[-1]))
1154 0 : --end;
1155 0 : str.set_length(end - start);
1156 0 : }
1157 :
1158 3 : void do_bib(const char *filename)
1159 : {
1160 : FILE *fp;
1161 3 : if (strcmp(filename, "-") == 0)
1162 0 : fp = stdin;
1163 : else {
1164 3 : errno = 0;
1165 3 : fp = fopen(filename, "r");
1166 3 : if (fp == 0) {
1167 0 : error("cannot open '%1': %2", filename, strerror(errno));
1168 0 : return;
1169 : }
1170 3 : current_filename = filename;
1171 : }
1172 3 : current_lineno = 1;
1173 : enum {
1174 : START, MIDDLE, BODY, BODY_START, BODY_BLANK, BODY_DOT
1175 3 : } state = START;
1176 6 : string body;
1177 : for (;;) {
1178 464 : int c = getc(fp);
1179 464 : if (EOF == c)
1180 3 : break;
1181 461 : if (is_invalid_input_char(c)) {
1182 10 : error("invalid input character code %1; ignoring", c);
1183 10 : continue;
1184 : }
1185 451 : switch (state) {
1186 5 : case START:
1187 5 : if ('%' == c) {
1188 5 : body = c;
1189 5 : state = BODY;
1190 : }
1191 0 : else if (c != '\n')
1192 0 : state = MIDDLE;
1193 5 : break;
1194 0 : case MIDDLE:
1195 0 : if ('\n' == c)
1196 0 : state = START;
1197 0 : break;
1198 432 : case BODY:
1199 432 : body += c;
1200 432 : if ('\n' == c)
1201 17 : state = BODY_START;
1202 432 : break;
1203 14 : case BODY_START:
1204 14 : if ('\n' == c) {
1205 2 : do_ref(body);
1206 2 : state = START;
1207 : }
1208 12 : else if ('.' == c)
1209 0 : state = BODY_DOT;
1210 12 : else if (csspace(c)) {
1211 0 : state = BODY_BLANK;
1212 0 : body += c;
1213 : }
1214 : else {
1215 12 : body += c;
1216 12 : state = BODY;
1217 : }
1218 14 : break;
1219 0 : case BODY_BLANK:
1220 0 : if ('\n' == c) {
1221 0 : trim_blanks(body);
1222 0 : do_ref(body);
1223 0 : state = START;
1224 : }
1225 0 : else if (csspace(c))
1226 0 : body += c;
1227 : else {
1228 0 : body += c;
1229 0 : state = BODY;
1230 : }
1231 0 : break;
1232 0 : case BODY_DOT:
1233 0 : if (']' == c) {
1234 0 : do_ref(body);
1235 0 : state = MIDDLE;
1236 : }
1237 : else {
1238 0 : body += '.';
1239 0 : body += c;
1240 0 : state = ('\n' == c) ? BODY_START : BODY;
1241 : }
1242 0 : break;
1243 0 : default:
1244 0 : assert(0 == "unhandled case while parsing bibliography file");
1245 : }
1246 451 : if ('\n' == c)
1247 19 : current_lineno++;
1248 461 : }
1249 3 : switch (state) {
1250 0 : case START:
1251 : case MIDDLE:
1252 0 : break;
1253 0 : case BODY:
1254 0 : body += '\n';
1255 0 : do_ref(body);
1256 0 : break;
1257 3 : case BODY_DOT:
1258 : case BODY_START:
1259 3 : do_ref(body);
1260 3 : break;
1261 0 : case BODY_BLANK:
1262 0 : trim_blanks(body);
1263 0 : do_ref(body);
1264 0 : break;
1265 : }
1266 3 : fclose(fp);
1267 : }
1268 :
1269 : // from the Dragon Book
1270 :
1271 2112 : unsigned hash_string(const char *s, int len)
1272 : {
1273 2112 : const char *end = s + len;
1274 2112 : unsigned h = 0, g;
1275 8856 : while (s < end) {
1276 6744 : h <<= 4;
1277 6744 : h += *s++;
1278 6744 : if ((g = h & 0xf0000000) != 0) {
1279 372 : h ^= g >> 24;
1280 372 : h ^= g;
1281 : }
1282 : }
1283 2112 : return h;
1284 : }
1285 :
1286 0 : int next_size(int n)
1287 : {
1288 : static const int table_sizes[] = {
1289 : 101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009,
1290 : 80021, 160001, 500009, 1000003, 2000003, 4000037, 8000009,
1291 : 16000057, 32000011, 64000031, 128000003, 0
1292 : };
1293 :
1294 : const int *p;
1295 0 : for (p = table_sizes; *p <= n && *p != 0; p++)
1296 : ;
1297 0 : assert(*p != 0);
1298 0 : return *p;
1299 : }
1300 :
1301 : // Local Variables:
1302 : // fill-column: 72
1303 : // mode: C++
1304 : // End:
1305 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|