groff before CVS: release 1.06
[s-roff.git] / refer / refer.cc
blobcb6e218e3ea1862bd984c895eb0fe195199006cb
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
5 This file is part of groff.
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
17 You should have received a copy of the GNU General Public License along
18 with groff; see the file COPYING. If not, write to the Free Software
19 Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
21 #include "refer.h"
22 #include "refid.h"
23 #include "ref.h"
24 #include "token.h"
25 #include "search.h"
26 #include "command.h"
28 const char PRE_LABEL_MARKER = '\013';
29 const char POST_LABEL_MARKER = '\014';
30 const char LABEL_MARKER = '\015'; // label_type is added on
32 #define FORCE_LEFT_BRACKET 04
33 #define FORCE_RIGHT_BRACKET 010
35 static FILE *outfp = stdout;
37 string capitalize_fields;
38 string reverse_fields;
39 string abbreviate_fields;
40 string period_before_last_name = ". ";
41 string period_before_initial = ".";
42 string period_before_hyphen = "";
43 string period_before_other = ". ";
44 string sort_fields;
45 int annotation_field = -1;
46 string annotation_macro;
47 string discard_fields = "XYZ";
48 string pre_label = "\\*([.";
49 string post_label = "\\*(.]";
50 string sep_label = ", ";
51 int accumulate = 0;
52 int move_punctuation = 0;
53 int abbreviate_label_ranges = 0;
54 string label_range_indicator;
55 int label_in_text = 1;
56 int label_in_reference = 1;
57 int date_as_label = 0;
58 int sort_adjacent_labels = 0;
59 // Join exactly two authors with this.
60 string join_authors_exactly_two = " and ";
61 // When there are more than two authors join the last two with this.
62 string join_authors_last_two = ", and ";
63 // Otherwise join authors with this.
64 string join_authors_default = ", ";
65 string separate_label_second_parts = ", ";
66 // Use this string to represent that there are other authors.
67 string et_al = " et al";
68 // Use et al only if it can replace at least this many authors.
69 int et_al_min_elide = 2;
70 // Use et al only if the total number of authors is at least this.
71 int et_al_min_total = 3;
74 int compatible_flag = 0;
76 int short_label_flag = 0;
78 static int recognize_R1_R2 = 1;
80 search_list database_list;
81 int search_default = 1;
82 static int default_database_loaded = 0;
84 static reference **citation = 0;
85 static int ncitations = 0;
86 static int citation_max = 0;
88 static reference **reference_hash_table = 0;
89 static int hash_table_size;
90 static int nreferences = 0;
92 static int need_syncing = 0;
93 string pending_line;
94 string pending_lf_lines;
96 static void output_pending_line();
97 static unsigned immediately_handle_reference(const string &);
98 static void immediately_output_references();
99 static unsigned store_reference(const string &);
100 static void divert_to_temporary_file();
101 static reference *make_reference(const string &, unsigned *);
102 static void usage();
103 static void do_file(const char *);
104 static void split_punct(string &line, string &punct);
105 static void output_citation_group(reference **v, int n, label_type, FILE *fp);
106 static void possibly_load_default_database();
108 int main(int argc, char **argv)
110 program_name = argv[0];
111 static char stderr_buf[BUFSIZ];
112 setbuf(stderr, stderr_buf);
113 outfp = stdout;
114 int finished_options = 0;
115 int bib_flag = 0;
116 int done_spec = 0;
118 for (--argc, ++argv;
119 !finished_options && argc > 0 && argv[0][0] == '-'
120 && argv[0][1] != '\0';
121 argv++, argc--) {
122 const char *opt = argv[0] + 1;
123 while (opt != 0 && *opt != '\0') {
124 switch (*opt) {
125 case 'C':
126 compatible_flag = 1;
127 opt++;
128 break;
129 case 'B':
130 bib_flag = 1;
131 label_in_reference = 0;
132 label_in_text = 0;
133 ++opt;
134 if (*opt == '\0') {
135 annotation_field = 'X';
136 annotation_macro = "AP";
138 else if (csalnum(opt[0]) && opt[1] == '.' && opt[2] != '\0') {
139 annotation_field = opt[0];
140 annotation_macro = opt + 2;
142 opt = 0;
143 break;
144 case 'P':
145 move_punctuation = 1;
146 opt++;
147 break;
148 case 'R':
149 recognize_R1_R2 = 0;
150 opt++;
151 break;
152 case 'S':
153 // Not a very useful spec.
154 set_label_spec("(A.n|Q)', '(D.y|D)");
155 done_spec = 1;
156 pre_label = " (";
157 post_label = ")";
158 sep_label = "; ";
159 opt++;
160 break;
161 case 'V':
162 verify_flag = 1;
163 opt++;
164 break;
165 case 'f':
167 const char *num = 0;
168 if (*++opt == '\0') {
169 if (argc > 1) {
170 num = *++argv;
171 --argc;
173 else {
174 error("option `f' requires an argument");
175 usage();
178 else {
179 num = opt;
180 opt = 0;
182 for (const char *ptr = num; *ptr; ptr++)
183 if (!csdigit(*ptr)) {
184 error("bad character `%1' in argument to -f option", *ptr);
185 break;
187 if (*ptr == '\0') {
188 string spec;
189 spec = '%';
190 spec += num;
191 spec += '\0';
192 set_label_spec(spec.contents());
193 done_spec = 1;
195 break;
197 case 'b':
198 label_in_text = 0;
199 label_in_reference = 0;
200 opt++;
201 break;
202 case 'e':
203 accumulate = 1;
204 opt++;
205 break;
206 case 'c':
207 capitalize_fields = ++opt;
208 opt = 0;
209 break;
210 case 'k':
212 char buf[5];
213 if (csalpha(*++opt))
214 buf[0] = *opt++;
215 else {
216 if (*opt != '\0')
217 error("bad field name `%1'", *opt++);
218 buf[0] = 'L';
220 buf[1] = '~';
221 buf[2] = '%';
222 buf[3] = 'a';
223 buf[4] = '\0';
224 set_label_spec(buf);
225 done_spec = 1;
227 break;
228 case 'a':
230 for (const char *ptr = ++opt; *ptr; ptr++)
231 if (!csdigit(*ptr)) {
232 error("argument to `a' option not a number");
233 break;
235 if (*ptr == '\0') {
236 reverse_fields = 'A';
237 reverse_fields += opt;
239 opt = 0;
241 break;
242 case 'i':
243 linear_ignore_fields = ++opt;
244 opt = 0;
245 break;
246 case 'l':
248 char buf[INT_DIGITS*2 + 11]; // A.n+2D.y-3%a
249 strcpy(buf, "A.n");
250 if (*++opt != '\0' && *opt != ',') {
251 char *ptr;
252 long n = strtol(opt, &ptr, 10);
253 if (n == 0 && ptr == opt) {
254 error("bad integer `%1' in `l' option", opt);
255 opt = 0;
256 break;
258 if (n < 0)
259 n = 0;
260 opt = ptr;
261 sprintf(strchr(buf, '\0'), "+%d", n);
263 strcat(buf, "D.y");
264 if (*opt == ',')
265 opt++;
266 if (*opt != '\0') {
267 char *ptr;
268 long n = strtol(opt, &ptr, 10);
269 if (n == 0 && ptr == opt) {
270 error("bad integer `%1' in `l' option", opt);
271 opt = 0;
272 break;
274 if (n < 0)
275 n = 0;
276 sprintf(strchr(buf, '\0'), "-%d", n);
277 opt = ptr;
278 if (*opt != '\0')
279 error("argument to `l' option not of form `m,n'");
281 strcat(buf, "%a");
282 if (!set_label_spec(buf))
283 assert(0);
284 done_spec = 1;
286 break;
287 case 'n':
288 search_default = 0;
289 opt++;
290 break;
291 case 'p':
293 const char *filename = 0;
294 if (*++opt == '\0') {
295 if (argc > 1) {
296 filename = *++argv;
297 argc--;
299 else {
300 error("option `p' requires an argument");
301 usage();
304 else {
305 filename = opt;
306 opt = 0;
308 database_list.add_file(filename);
310 break;
311 case 's':
312 if (*++opt == '\0')
313 sort_fields = "AD";
314 else {
315 sort_fields = opt;
316 opt = 0;
318 accumulate = 1;
319 break;
320 case 't':
322 char *ptr;
323 long n = strtol(opt, &ptr, 10);
324 if (n == 0 && ptr == opt) {
325 error("bad integer `%1' in `t' option", opt);
326 opt = 0;
327 break;
329 if (n < 1)
330 n = 1;
331 linear_truncate_len = int(n);
332 opt = ptr;
333 break;
335 case 'v':
337 extern const char *version_string;
338 fprintf(stderr, "GNU refer version %s\n", version_string);
339 fflush(stderr);
340 opt++;
341 break;
343 case '-':
344 if (opt[1] == '\0') {
345 finished_options = 1;
346 opt++;
347 break;
349 // fall through
350 default:
351 error("unrecognized option `%1'", *opt);
352 usage();
353 break;
357 if (!done_spec)
358 set_label_spec("%1");
359 if (argc <= 0) {
360 if (bib_flag)
361 do_bib("-");
362 else
363 do_file("-");
365 else {
366 for (int i = 0; i < argc; i++) {
367 if (bib_flag)
368 do_bib(argv[i]);
369 else
370 do_file(argv[i]);
373 if (accumulate)
374 output_references();
375 if (fflush(stdout) < 0)
376 fatal("output error");
377 exit(0);
380 static void usage()
382 fprintf(stderr,
383 "usage: %s [-benvCPRS] [-aN] [-cXYZ] [-fN] [-iXYZ] [-kX] [-lM,N] [-p file]\n"
384 " [-sXYZ] [-tN] [-BL.M] [files ...]\n",
385 program_name);
386 exit(1);
389 static void possibly_load_default_database()
391 if (search_default && !default_database_loaded) {
392 char *filename = getenv("REFER");
393 if (filename)
394 database_list.add_file(filename);
395 else
396 database_list.add_file(DEFAULT_INDEX, 1);
397 default_database_loaded = 1;
401 static int is_list(const string &str)
403 const char *start = str.contents();
404 const char *end = start + str.length();
405 while (end > start && csspace(end[-1]))
406 end--;
407 while (start < end && csspace(*start))
408 start++;
409 return end - start == 6 && memcmp(start, "$LIST$", 6) == 0;
412 static void do_file(const char *filename)
414 FILE *fp;
415 if (strcmp(filename, "-") == 0) {
416 fp = stdin;
418 else {
419 errno = 0;
420 fp = fopen(filename, "r");
421 if (fp == 0) {
422 error("can't open `%1': %2", filename, strerror(errno));
423 return;
425 current_filename = filename;
427 fprintf(outfp, ".lf 1 %s\n", filename);
428 string line;
429 current_lineno = 0;
430 for (;;) {
431 line.clear();
432 for (;;) {
433 int c = getc(fp);
434 if (c == EOF) {
435 if (line.length() > 0)
436 line += '\n';
437 break;
439 if (illegal_input_char(c))
440 error("illegal input character code %1", c);
441 else {
442 line += c;
443 if (c == '\n')
444 break;
447 int len = line.length();
448 if (len == 0)
449 break;
450 current_lineno++;
451 if (len >= 2 && line[0] == '.' && line[1] == '[') {
452 int start_lineno = current_lineno;
453 int start_of_line = 1;
454 string str;
455 string post;
456 string pre(line.contents() + 2, line.length() - 3);
457 for (;;) {
458 int c = getc(fp);
459 if (c == EOF) {
460 error_with_file_and_line(current_filename, start_lineno,
461 "missing `.]' line");
462 break;
464 if (start_of_line)
465 current_lineno++;
466 if (start_of_line && c == '.') {
467 int d = getc(fp);
468 if (d == ']') {
469 while ((d = getc(fp)) != '\n' && d != EOF) {
470 if (illegal_input_char(d))
471 error("illegal input character code %1", d);
472 else
473 post += d;
475 break;
477 if (d != EOF)
478 ungetc(d, fp);
480 if (illegal_input_char(c))
481 error("illegal input character code %1", c);
482 else
483 str += c;
484 start_of_line = (c == '\n');
486 if (is_list(str)) {
487 output_pending_line();
488 if (accumulate)
489 output_references();
490 else
491 error("found `$LIST$' but not accumulating references");
493 else {
494 unsigned flags = (accumulate
495 ? store_reference(str)
496 : immediately_handle_reference(str));
497 if (label_in_text) {
498 if (accumulate && outfp == stdout)
499 divert_to_temporary_file();
500 if (pending_line.length() == 0) {
501 warning("can't attach citation to previous line");
503 else
504 pending_line.set_length(pending_line.length() - 1);
505 string punct;
506 if (move_punctuation)
507 split_punct(pending_line, punct);
508 int have_text = pre.length() > 0 || post.length() > 0;
509 label_type lt = label_type(flags & ~(FORCE_LEFT_BRACKET
510 |FORCE_RIGHT_BRACKET));
511 if ((flags & FORCE_LEFT_BRACKET) || !have_text)
512 pending_line += PRE_LABEL_MARKER;
513 pending_line += pre;
514 pending_line += LABEL_MARKER + lt;
515 pending_line += post;
516 if ((flags & FORCE_RIGHT_BRACKET) || !have_text)
517 pending_line += POST_LABEL_MARKER;
518 pending_line += punct;
519 pending_line += '\n';
522 need_syncing = 1;
524 else if (len >= 4
525 && line[0] == '.' && line[1] == 'l' && line[2] == 'f'
526 && (compatible_flag || line[3] == '\n' || line[3] == ' ')) {
527 pending_lf_lines += line;
528 line += '\0';
529 if (interpret_lf_args(line.contents() + 3))
530 current_lineno--;
532 else if (recognize_R1_R2
533 && len >= 4
534 && line[0] == '.' && line[1] == 'R' && line[2] == '1'
535 && (compatible_flag || line[3] == '\n' || line[3] == ' ')) {
536 line.clear();
537 int start_of_line = 1;
538 int start_lineno = current_lineno;
539 for (;;) {
540 int c = getc(fp);
541 if (c != EOF && start_of_line)
542 current_lineno++;
543 if (start_of_line && c == '.') {
544 c = getc(fp);
545 if (c == 'R') {
546 c = getc(fp);
547 if (c == '2') {
548 c = getc(fp);
549 if (compatible_flag || c == ' ' || c == '\n' || c == EOF) {
550 while (c != EOF && c != '\n')
551 c = getc(fp);
552 break;
554 else {
555 line += '.';
556 line += 'R';
557 line += '2';
560 else {
561 line += '.';
562 line += 'R';
565 else
566 line += '.';
568 if (c == EOF) {
569 error_with_file_and_line(current_filename, start_lineno,
570 "missing `.R2' line");
571 break;
573 if (illegal_input_char(c))
574 error("illegal input character code %1", int(c));
575 else {
576 line += c;
577 start_of_line = c == '\n';
580 output_pending_line();
581 if (accumulate)
582 output_references();
583 else
584 nreferences = 0;
585 process_commands(line, current_filename, start_lineno + 1);
586 need_syncing = 1;
588 else {
589 output_pending_line();
590 pending_line = line;
593 need_syncing = 0;
594 output_pending_line();
595 if (fp != stdin)
596 fclose(fp);
599 class label_processing_state {
600 enum {
601 NORMAL,
602 PENDING_LABEL,
603 PENDING_LABEL_POST,
604 PENDING_LABEL_POST_PRE,
605 PENDING_POST
606 } state;
607 label_type type; // type of pending labels
608 int count; // number of pending labels
609 reference **rptr; // pointer to next reference
610 int rcount; // number of references left
611 FILE *fp;
612 int handle_pending(int c);
613 public:
614 label_processing_state(reference **, int, FILE *);
615 ~label_processing_state();
616 void process(int c);
619 static void output_pending_line()
621 if (label_in_text && !accumulate && ncitations > 0) {
622 label_processing_state state(citation, ncitations, outfp);
623 int len = pending_line.length();
624 for (int i = 0; i < len; i++)
625 state.process((unsigned char)(pending_line[i]));
627 else
628 put_string(pending_line, outfp);
629 pending_line.clear();
630 if (pending_lf_lines.length() > 0) {
631 put_string(pending_lf_lines, outfp);
632 pending_lf_lines.clear();
634 if (!accumulate)
635 immediately_output_references();
636 if (need_syncing) {
637 fprintf(outfp, ".lf %d %s\n", current_lineno, current_filename);
638 need_syncing = 0;
642 static void split_punct(string &line, string &punct)
644 const char *start = line.contents();
645 const char *end = start + line.length();
646 const char *ptr = start;
647 const char *last_token_start = 0;
648 for (;;) {
649 if (ptr >= end)
650 break;
651 last_token_start = ptr;
652 if (*ptr == PRE_LABEL_MARKER || *ptr == POST_LABEL_MARKER
653 || (*ptr >= LABEL_MARKER && *ptr < LABEL_MARKER + N_LABEL_TYPES))
654 ptr++;
655 else if (!get_token(&ptr, end))
656 break;
658 if (last_token_start) {
659 const token_info *ti = lookup_token(last_token_start, end);
660 if (ti->is_punct()) {
661 punct.append(last_token_start, end - last_token_start);
662 line.set_length(last_token_start - start);
667 static void divert_to_temporary_file()
669 outfp = xtmpfile();
672 static void store_citation(reference *ref)
674 if (ncitations >= citation_max) {
675 if (citation == 0)
676 citation = new reference*[citation_max = 100];
677 else {
678 reference **old_citation = citation;
679 citation_max *= 2;
680 citation = new reference *[citation_max];
681 memcpy(citation, old_citation, ncitations*sizeof(reference *));
682 a_delete old_citation;
685 citation[ncitations++] = ref;
688 static unsigned store_reference(const string &str)
690 if (reference_hash_table == 0) {
691 reference_hash_table = new reference *[17];
692 hash_table_size = 17;
693 for (int i = 0; i < hash_table_size; i++)
694 reference_hash_table[i] = 0;
696 unsigned flags;
697 reference *ref = make_reference(str, &flags);
698 ref->compute_hash_code();
699 unsigned h = ref->hash();
700 for (reference **ptr = reference_hash_table + (h % hash_table_size);
701 *ptr != 0;
702 ((ptr == reference_hash_table)
703 ? (ptr = reference_hash_table + hash_table_size - 1)
704 : --ptr))
705 if (same_reference(**ptr, *ref))
706 break;
707 if (*ptr != 0) {
708 if (ref->is_merged())
709 warning("fields ignored because reference already used");
710 delete ref;
711 ref = *ptr;
713 else {
714 *ptr = ref;
715 ref->set_number(nreferences);
716 nreferences++;
717 ref->pre_compute_label();
718 ref->compute_sort_key();
719 if (nreferences*2 >= hash_table_size) {
720 // Rehash it.
721 reference **old_table = reference_hash_table;
722 int old_size = hash_table_size;
723 hash_table_size = next_size(hash_table_size);
724 reference_hash_table = new reference*[hash_table_size];
725 int i;
726 for (i = 0; i < hash_table_size; i++)
727 reference_hash_table[i] = 0;
728 for (i = 0; i < old_size; i++)
729 if (old_table[i]) {
730 for (reference **p = (reference_hash_table
731 + (old_table[i]->hash() % hash_table_size));
733 ((p == reference_hash_table)
734 ? (p = reference_hash_table + hash_table_size - 1)
735 : --p))
737 *p = old_table[i];
739 a_delete old_table;
742 if (label_in_text)
743 store_citation(ref);
744 return flags;
747 unsigned immediately_handle_reference(const string &str)
749 unsigned flags;
750 reference *ref = make_reference(str, &flags);
751 ref->set_number(nreferences);
752 if (label_in_text || label_in_reference) {
753 ref->pre_compute_label();
754 ref->immediate_compute_label();
756 nreferences++;
757 store_citation(ref);
758 return flags;
761 static void immediately_output_references()
763 for (int i = 0; i < ncitations; i++) {
764 reference *ref = citation[i];
765 if (label_in_reference) {
766 fputs(".ds [F ", outfp);
767 const string &label = ref->get_label(NORMAL_LABEL);
768 if (label.length() > 0
769 && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
770 putc('"', outfp);
771 put_string(label, outfp);
772 putc('\n', outfp);
774 ref->output(outfp);
775 delete ref;
777 ncitations = 0;
780 static void output_citation_group(reference **v, int n, label_type type,
781 FILE *fp)
783 if (sort_adjacent_labels) {
784 // Do an insertion sort. Usually n will be very small.
785 for (int i = 1; i < n; i++) {
786 int num = v[i]->get_number();
787 reference *temp = v[i];
788 for (int j = i - 1; j >= 0 && v[j]->get_number() > num; j--)
789 v[j + 1] = v[j];
790 v[j + 1] = temp;
793 // This messes up if !accumulate.
794 if (accumulate && n > 1) {
795 // remove duplicates
796 int j = 1;
797 for (int i = 1; i < n; i++)
798 if (v[i]->get_label(type) != v[i - 1]->get_label(type))
799 v[j++] = v[i];
800 n = j;
802 string merged_label;
803 for (int i = 0; i < n; i++) {
804 int nmerged = v[i]->merge_labels(v + i + 1, n - i - 1, type, merged_label);
805 if (nmerged > 0) {
806 put_string(merged_label, fp);
807 i += nmerged;
809 else
810 put_string(v[i]->get_label(type), fp);
811 if (i < n - 1)
812 put_string(sep_label, fp);
817 label_processing_state::label_processing_state(reference **p, int n, FILE *f)
818 : state(NORMAL), count(0), rptr(p), rcount(n), fp(f)
822 label_processing_state::~label_processing_state()
824 int handled = handle_pending(EOF);
825 assert(!handled);
826 assert(rcount == 0);
829 int label_processing_state::handle_pending(int c)
831 switch (state) {
832 case NORMAL:
833 break;
834 case PENDING_LABEL:
835 if (c == POST_LABEL_MARKER) {
836 state = PENDING_LABEL_POST;
837 return 1;
839 else {
840 output_citation_group(rptr, count, type, fp);
841 rptr += count ;
842 rcount -= count;
843 state = NORMAL;
845 break;
846 case PENDING_LABEL_POST:
847 if (c == PRE_LABEL_MARKER) {
848 state = PENDING_LABEL_POST_PRE;
849 return 1;
851 else {
852 output_citation_group(rptr, count, type, fp);
853 rptr += count;
854 rcount -= count;
855 put_string(post_label, fp);
856 state = NORMAL;
858 break;
859 case PENDING_LABEL_POST_PRE:
860 if (c >= LABEL_MARKER
861 && c < LABEL_MARKER + N_LABEL_TYPES
862 && c - LABEL_MARKER == type) {
863 count += 1;
864 state = PENDING_LABEL;
865 return 1;
867 else {
868 output_citation_group(rptr, count, type, fp);
869 rptr += count;
870 rcount -= count;
871 put_string(sep_label, fp);
872 state = NORMAL;
874 break;
875 case PENDING_POST:
876 if (c == PRE_LABEL_MARKER) {
877 put_string(sep_label, fp);
878 state = NORMAL;
879 return 1;
881 else {
882 put_string(post_label, fp);
883 state = NORMAL;
885 break;
887 return 0;
890 void label_processing_state::process(int c)
892 if (handle_pending(c))
893 return;
894 assert(state == NORMAL);
895 switch (c) {
896 case PRE_LABEL_MARKER:
897 put_string(pre_label, fp);
898 state = NORMAL;
899 break;
900 case POST_LABEL_MARKER:
901 state = PENDING_POST;
902 break;
903 case LABEL_MARKER:
904 case LABEL_MARKER + 1:
905 count = 1;
906 state = PENDING_LABEL;
907 type = label_type(c - LABEL_MARKER);
908 break;
909 default:
910 state = NORMAL;
911 putc(c, fp);
912 break;
916 extern "C" {
918 static int rcompare(const void *p1, const void *p2)
920 return compare_reference(**(reference **)p1, **(reference **)p2);
925 void output_references()
927 assert(accumulate);
928 if (nreferences > 0) {
929 int j = 0;
930 int i;
931 for (i = 0; i < hash_table_size; i++)
932 if (reference_hash_table[i] != 0)
933 reference_hash_table[j++] = reference_hash_table[i];
934 assert(j == nreferences);
935 for (; j < hash_table_size; j++)
936 reference_hash_table[j] = 0;
937 qsort(reference_hash_table, nreferences, sizeof(reference*), rcompare);
938 for (i = 0; i < nreferences; i++)
939 reference_hash_table[i]->set_number(i);
940 compute_labels(reference_hash_table, nreferences);
942 if (outfp != stdout) {
943 rewind(outfp);
945 label_processing_state state(citation, ncitations, stdout);
946 int c;
947 while ((c = getc(outfp)) != EOF)
948 state.process(c);
950 ncitations = 0;
951 fclose(outfp);
952 outfp = stdout;
954 if (nreferences > 0) {
955 fputs(".]<\n", outfp);
956 for (int i = 0; i < nreferences; i++) {
957 if (sort_fields.length() > 0)
958 reference_hash_table[i]->print_sort_key_comment(outfp);
959 if (label_in_reference) {
960 fputs(".ds [F ", outfp);
961 const string &label = reference_hash_table[i]->get_label(NORMAL_LABEL);
962 if (label.length() > 0
963 && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
964 putc('"', outfp);
965 put_string(label, outfp);
966 putc('\n', outfp);
968 reference_hash_table[i]->output(outfp);
969 delete reference_hash_table[i];
970 reference_hash_table[i] = 0;
972 fputs(".]>\n", outfp);
973 nreferences = 0;
975 clear_labels();
978 static reference *find_reference(const char *query, int query_len)
980 // This is so that error messages look better.
981 while (query_len > 0 && csspace(query[query_len - 1]))
982 query_len--;
983 string str;
984 for (int i = 0; i < query_len; i++)
985 str += query[i] == '\n' ? ' ' : query[i];
986 str += '\0';
987 possibly_load_default_database();
988 search_list_iterator iter(&database_list, str.contents());
989 reference_id rid;
990 const char *start;
991 int len;
992 if (!iter.next(&start, &len, &rid)) {
993 error("no matches for `%1'", str.contents());
994 return 0;
996 const char *end = start + len;
997 while (start < end) {
998 if (*start == '%')
999 break;
1000 while (start < end && *start++ != '\n')
1003 if (start >= end) {
1004 error("found a reference for `%1' but it didn't contain any fields",
1005 str.contents());
1006 return 0;
1008 reference *result = new reference(start, end - start, &rid);
1009 if (iter.next(&start, &len, &rid))
1010 warning("multiple matches for `%1'", str.contents());
1011 return result;
1014 static reference *make_reference(const string &str, unsigned *flagsp)
1016 const char *start = str.contents();
1017 const char *end = start + str.length();
1018 const char *ptr = start;
1019 while (ptr < end) {
1020 if (*ptr == '%')
1021 break;
1022 while (ptr < end && *ptr++ != '\n')
1025 *flagsp = 0;
1026 for (; start < ptr; start++) {
1027 if (*start == '#')
1028 *flagsp = (SHORT_LABEL | (*flagsp & (FORCE_RIGHT_BRACKET
1029 | FORCE_LEFT_BRACKET)));
1030 else if (*start == '[')
1031 *flagsp |= FORCE_LEFT_BRACKET;
1032 else if (*start == ']')
1033 *flagsp |= FORCE_RIGHT_BRACKET;
1034 else if (!csspace(*start))
1035 break;
1037 if (start >= end) {
1038 error("empty reference");
1039 return new reference;
1041 reference *database_ref = 0;
1042 if (start < ptr)
1043 database_ref = find_reference(start, ptr - start);
1044 reference *inline_ref = 0;
1045 if (ptr < end)
1046 inline_ref = new reference(ptr, end - ptr);
1047 if (inline_ref) {
1048 if (database_ref) {
1049 database_ref->merge(*inline_ref);
1050 delete inline_ref;
1051 return database_ref;
1053 else
1054 return inline_ref;
1056 else if (database_ref)
1057 return database_ref;
1058 else
1059 return new reference;
1062 static void do_ref(const string &str)
1064 if (accumulate)
1065 (void)store_reference(str);
1066 else {
1067 (void)immediately_handle_reference(str);
1068 immediately_output_references();
1072 static void trim_blanks(string &str)
1074 const char *start = str.contents();
1075 const char *end = start + str.length();
1076 while (end > start && end[-1] != '\n' && csspace(end[-1]))
1077 --end;
1078 str.set_length(end - start);
1081 void do_bib(const char *filename)
1083 FILE *fp;
1084 if (strcmp(filename, "-") == 0)
1085 fp = stdin;
1086 else {
1087 errno = 0;
1088 fp = fopen(filename, "r");
1089 if (fp == 0) {
1090 error("can't open `%1': %2", filename, strerror(errno));
1091 return;
1093 current_filename = filename;
1095 enum {
1096 START, MIDDLE, BODY, BODY_START, BODY_BLANK, BODY_DOT
1097 } state = START;
1098 string body;
1099 for (;;) {
1100 int c = getc(fp);
1101 if (c == EOF)
1102 break;
1103 if (illegal_input_char(c)) {
1104 error("illegal input character code %1", c);
1105 continue;
1107 switch (state) {
1108 case START:
1109 if (c == '%') {
1110 body = c;
1111 state = BODY;
1113 else if (c != '\n')
1114 state = MIDDLE;
1115 break;
1116 case MIDDLE:
1117 if (c == '\n')
1118 state = START;
1119 break;
1120 case BODY:
1121 body += c;
1122 if (c == '\n')
1123 state = BODY_START;
1124 break;
1125 case BODY_START:
1126 if (c == '\n') {
1127 do_ref(body);
1128 state = START;
1130 else if (c == '.')
1131 state = BODY_DOT;
1132 else if (csspace(c)) {
1133 state = BODY_BLANK;
1134 body += c;
1136 else {
1137 body += c;
1138 state = BODY;
1140 break;
1141 case BODY_BLANK:
1142 if (c == '\n') {
1143 trim_blanks(body);
1144 do_ref(body);
1145 state = START;
1147 else if (csspace(c))
1148 body += c;
1149 else {
1150 body += c;
1151 state = BODY;
1153 break;
1154 case BODY_DOT:
1155 if (c == ']') {
1156 do_ref(body);
1157 state = MIDDLE;
1159 else {
1160 body += '.';
1161 body += c;
1162 state = c == '\n' ? BODY_START : BODY;
1164 break;
1165 default:
1166 assert(0);
1168 if (c == '\n')
1169 current_lineno++;
1171 switch (state) {
1172 case START:
1173 case MIDDLE:
1174 break;
1175 case BODY:
1176 body += '\n';
1177 do_ref(body);
1178 break;
1179 case BODY_DOT:
1180 case BODY_START:
1181 do_ref(body);
1182 break;
1183 case BODY_BLANK:
1184 trim_blanks(body);
1185 do_ref(body);
1186 break;
1188 fclose(fp);
1191 // from the Dragon Book
1193 unsigned hash_string(const char *s, int len)
1195 const char *end = s + len;
1196 unsigned h = 0, g;
1197 while (s < end) {
1198 h <<= 4;
1199 h += *s++;
1200 if ((g = h & 0xf0000000) != 0) {
1201 h ^= g >> 24;
1202 h ^= g;
1205 return h;
1208 int next_size(int n)
1210 static const int table_sizes[] = {
1211 101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009,
1212 80021, 160001, 500009, 1000003, 2000003, 4000037, 8000009,
1213 16000057, 32000011, 64000031, 128000003, 0
1216 for (const int *p = table_sizes; *p <= n && *p != 0; p++)
1218 assert(*p != 0);
1219 return *p;