2 /* Copyright (C) 1989-1992, 2000, 2001, 2002 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
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
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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
28 extern "C" const char *Version_string;
30 const char PRE_LABEL_MARKER = '\013';
31 const char POST_LABEL_MARKER = '\014';
32 const char LABEL_MARKER = '\015'; // label_type is added on
34 #define FORCE_LEFT_BRACKET 04
35 #define FORCE_RIGHT_BRACKET 010
37 static FILE *outfp = stdout;
39 string capitalize_fields;
40 string reverse_fields;
41 string abbreviate_fields;
42 string period_before_last_name = ". ";
43 string period_before_initial = ".";
44 string period_before_hyphen = "";
45 string period_before_other = ". ";
47 int annotation_field = -1;
48 string annotation_macro;
49 string discard_fields = "XYZ";
50 string pre_label = "\\*([.";
51 string post_label = "\\*(.]";
52 string sep_label = ", ";
54 int move_punctuation = 0;
55 int abbreviate_label_ranges = 0;
56 string label_range_indicator;
57 int label_in_text = 1;
58 int label_in_reference = 1;
59 int date_as_label = 0;
60 int sort_adjacent_labels = 0;
61 // Join exactly two authors with this.
62 string join_authors_exactly_two = " and ";
63 // When there are more than two authors join the last two with this.
64 string join_authors_last_two = ", and ";
65 // Otherwise join authors with this.
66 string join_authors_default = ", ";
67 string separate_label_second_parts = ", ";
68 // Use this string to represent that there are other authors.
69 string et_al = " et al";
70 // Use et al only if it can replace at least this many authors.
71 int et_al_min_elide = 2;
72 // Use et al only if the total number of authors is at least this.
73 int et_al_min_total = 3;
76 int compatible_flag = 0;
78 int short_label_flag = 0;
80 static int recognize_R1_R2 = 1;
82 search_list database_list;
83 int search_default = 1;
84 static int default_database_loaded = 0;
86 static reference **citation = 0;
87 static int ncitations = 0;
88 static int citation_max = 0;
90 static reference **reference_hash_table = 0;
91 static int hash_table_size;
92 static int nreferences = 0;
94 static int need_syncing = 0;
96 string pending_lf_lines;
98 static void output_pending_line();
99 static unsigned immediately_handle_reference(const string &);
100 static void immediately_output_references();
101 static unsigned store_reference(const string &);
102 static void divert_to_temporary_file();
103 static reference *make_reference(const string &, unsigned *);
104 static void usage(FILE *stream);
105 static void do_file(const char *);
106 static void split_punct(string &line, string &punct);
107 static void output_citation_group(reference **v, int n, label_type, FILE *fp);
108 static void possibly_load_default_database();
110 int main(int argc, char **argv)
112 program_name = argv[0];
113 static char stderr_buf[BUFSIZ];
114 setbuf(stderr, stderr_buf);
116 int finished_options = 0;
121 !finished_options && argc > 0 && argv[0][0] == '-'
122 && argv[0][1] != '\0';
124 const char *opt = argv[0] + 1;
125 while (opt != 0 && *opt != '\0') {
133 label_in_reference = 0;
137 annotation_field = 'X';
138 annotation_macro = "AP";
140 else if (csalnum(opt[0]) && opt[1] == '.' && opt[2] != '\0') {
141 annotation_field = opt[0];
142 annotation_macro = opt + 2;
147 move_punctuation = 1;
155 // Not a very useful spec.
156 set_label_spec("(A.n|Q)', '(D.y|D)");
170 if (*++opt == '\0') {
176 error("option `f' requires an argument");
186 for (ptr = num; *ptr; ptr++)
187 if (!csdigit(*ptr)) {
188 error("bad character `%1' in argument to -f option", *ptr);
196 set_label_spec(spec.contents());
203 label_in_reference = 0;
211 capitalize_fields = ++opt;
221 error("bad field name `%1'", *opt++);
235 for (ptr = ++opt; *ptr; ptr++)
236 if (!csdigit(*ptr)) {
237 error("argument to `a' option not a number");
241 reverse_fields = 'A';
242 reverse_fields += opt;
248 linear_ignore_fields = ++opt;
253 char buf[INT_DIGITS*2 + 11]; // A.n+2D.y-3%a
255 if (*++opt != '\0' && *opt != ',') {
257 long n = strtol(opt, &ptr, 10);
258 if (n == 0 && ptr == opt) {
259 error("bad integer `%1' in `l' option", opt);
266 sprintf(strchr(buf, '\0'), "+%ld", n);
273 long n = strtol(opt, &ptr, 10);
274 if (n == 0 && ptr == opt) {
275 error("bad integer `%1' in `l' option", opt);
281 sprintf(strchr(buf, '\0'), "-%ld", n);
284 error("argument to `l' option not of form `m,n'");
287 if (!set_label_spec(buf))
298 const char *filename = 0;
299 if (*++opt == '\0') {
305 error("option `p' requires an argument");
314 database_list.add_file(filename);
329 long n = strtol(opt, &ptr, 10);
330 if (n == 0 && ptr == opt) {
331 error("bad integer `%1' in `t' option", opt);
337 linear_truncate_len = int(n);
342 if (opt[1] == '\0') {
343 finished_options = 1;
347 if (strcmp(opt,"-version")==0) {
349 printf("GNU refer (groff) version %s\n", Version_string);
353 if (strcmp(opt,"-help")==0) {
360 error("unrecognized option `%1'", *opt);
368 set_label_spec("%1");
376 for (int i = 0; i < argc; i++) {
385 if (fflush(stdout) < 0)
386 fatal("output error");
390 static void usage(FILE *stream)
393 "usage: %s [-benvCPRS] [-aN] [-cXYZ] [-fN] [-iXYZ] [-kX] [-lM,N] [-p file]\n"
394 " [-sXYZ] [-tN] [-BL.M] [files ...]\n",
398 static void possibly_load_default_database()
400 if (search_default && !default_database_loaded) {
401 char *filename = getenv("REFER");
403 database_list.add_file(filename);
405 database_list.add_file(DEFAULT_INDEX, 1);
406 default_database_loaded = 1;
410 static int is_list(const string &str)
412 const char *start = str.contents();
413 const char *end = start + str.length();
414 while (end > start && csspace(end[-1]))
416 while (start < end && csspace(*start))
418 return end - start == 6 && memcmp(start, "$LIST$", 6) == 0;
421 static void do_file(const char *filename)
424 if (strcmp(filename, "-") == 0) {
429 fp = fopen(filename, "r");
431 error("can't open `%1': %2", filename, strerror(errno));
435 current_filename = filename;
436 fprintf(outfp, ".lf 1 %s\n", filename);
444 if (line.length() > 0)
448 if (invalid_input_char(c))
449 error("invalid input character code %1", c);
456 int len = line.length();
460 if (len >= 2 && line[0] == '.' && line[1] == '[') {
461 int start_lineno = current_lineno;
462 int start_of_line = 1;
465 string pre(line.contents() + 2, line.length() - 3);
469 error_with_file_and_line(current_filename, start_lineno,
470 "missing `.]' line");
475 if (start_of_line && c == '.') {
478 while ((d = getc(fp)) != '\n' && d != EOF) {
479 if (invalid_input_char(d))
480 error("invalid input character code %1", d);
489 if (invalid_input_char(c))
490 error("invalid input character code %1", c);
493 start_of_line = (c == '\n');
496 output_pending_line();
500 error("found `$LIST$' but not accumulating references");
503 unsigned flags = (accumulate
504 ? store_reference(str)
505 : immediately_handle_reference(str));
507 if (accumulate && outfp == stdout)
508 divert_to_temporary_file();
509 if (pending_line.length() == 0) {
510 warning("can't attach citation to previous line");
513 pending_line.set_length(pending_line.length() - 1);
515 if (move_punctuation)
516 split_punct(pending_line, punct);
517 int have_text = pre.length() > 0 || post.length() > 0;
518 label_type lt = label_type(flags & ~(FORCE_LEFT_BRACKET
519 |FORCE_RIGHT_BRACKET));
520 if ((flags & FORCE_LEFT_BRACKET) || !have_text)
521 pending_line += PRE_LABEL_MARKER;
523 char lm = LABEL_MARKER + (int)lt;
525 pending_line += post;
526 if ((flags & FORCE_RIGHT_BRACKET) || !have_text)
527 pending_line += POST_LABEL_MARKER;
528 pending_line += punct;
529 pending_line += '\n';
535 && line[0] == '.' && line[1] == 'l' && line[2] == 'f'
536 && (compatible_flag || line[3] == '\n' || line[3] == ' ')) {
537 pending_lf_lines += line;
539 if (interpret_lf_args(line.contents() + 3))
542 else if (recognize_R1_R2
544 && line[0] == '.' && line[1] == 'R' && line[2] == '1'
545 && (compatible_flag || line[3] == '\n' || line[3] == ' ')) {
547 int start_of_line = 1;
548 int start_lineno = current_lineno;
551 if (c != EOF && start_of_line)
553 if (start_of_line && c == '.') {
559 if (compatible_flag || c == ' ' || c == '\n' || c == EOF) {
560 while (c != EOF && c != '\n')
579 error_with_file_and_line(current_filename, start_lineno,
580 "missing `.R2' line");
583 if (invalid_input_char(c))
584 error("invalid input character code %1", int(c));
587 start_of_line = c == '\n';
590 output_pending_line();
595 process_commands(line, current_filename, start_lineno + 1);
599 output_pending_line();
604 output_pending_line();
609 class label_processing_state {
614 PENDING_LABEL_POST_PRE,
617 label_type type; // type of pending labels
618 int count; // number of pending labels
619 reference **rptr; // pointer to next reference
620 int rcount; // number of references left
622 int handle_pending(int c);
624 label_processing_state(reference **, int, FILE *);
625 ~label_processing_state();
629 static void output_pending_line()
631 if (label_in_text && !accumulate && ncitations > 0) {
632 label_processing_state state(citation, ncitations, outfp);
633 int len = pending_line.length();
634 for (int i = 0; i < len; i++)
635 state.process((unsigned char)(pending_line[i]));
638 put_string(pending_line, outfp);
639 pending_line.clear();
640 if (pending_lf_lines.length() > 0) {
641 put_string(pending_lf_lines, outfp);
642 pending_lf_lines.clear();
645 immediately_output_references();
647 fprintf(outfp, ".lf %d %s\n", current_lineno, current_filename);
652 static void split_punct(string &line, string &punct)
654 const char *start = line.contents();
655 const char *end = start + line.length();
656 const char *ptr = start;
657 const char *last_token_start = 0;
661 last_token_start = ptr;
662 if (*ptr == PRE_LABEL_MARKER || *ptr == POST_LABEL_MARKER
663 || (*ptr >= LABEL_MARKER && *ptr < LABEL_MARKER + N_LABEL_TYPES))
665 else if (!get_token(&ptr, end))
668 if (last_token_start) {
669 const token_info *ti = lookup_token(last_token_start, end);
670 if (ti->is_punct()) {
671 punct.append(last_token_start, end - last_token_start);
672 line.set_length(last_token_start - start);
677 static void divert_to_temporary_file()
682 static void store_citation(reference *ref)
684 if (ncitations >= citation_max) {
686 citation = new reference*[citation_max = 100];
688 reference **old_citation = citation;
690 citation = new reference *[citation_max];
691 memcpy(citation, old_citation, ncitations*sizeof(reference *));
692 a_delete old_citation;
695 citation[ncitations++] = ref;
698 static unsigned store_reference(const string &str)
700 if (reference_hash_table == 0) {
701 reference_hash_table = new reference *[17];
702 hash_table_size = 17;
703 for (int i = 0; i < hash_table_size; i++)
704 reference_hash_table[i] = 0;
707 reference *ref = make_reference(str, &flags);
708 ref->compute_hash_code();
709 unsigned h = ref->hash();
711 for (ptr = reference_hash_table + (h % hash_table_size);
713 ((ptr == reference_hash_table)
714 ? (ptr = reference_hash_table + hash_table_size - 1)
716 if (same_reference(**ptr, *ref))
719 if (ref->is_merged())
720 warning("fields ignored because reference already used");
726 ref->set_number(nreferences);
728 ref->pre_compute_label();
729 ref->compute_sort_key();
730 if (nreferences*2 >= hash_table_size) {
732 reference **old_table = reference_hash_table;
733 int old_size = hash_table_size;
734 hash_table_size = next_size(hash_table_size);
735 reference_hash_table = new reference*[hash_table_size];
737 for (i = 0; i < hash_table_size; i++)
738 reference_hash_table[i] = 0;
739 for (i = 0; i < old_size; i++)
742 for (p = (reference_hash_table
743 + (old_table[i]->hash() % hash_table_size));
745 ((p == reference_hash_table)
746 ? (p = reference_hash_table + hash_table_size - 1)
759 unsigned immediately_handle_reference(const string &str)
762 reference *ref = make_reference(str, &flags);
763 ref->set_number(nreferences);
764 if (label_in_text || label_in_reference) {
765 ref->pre_compute_label();
766 ref->immediate_compute_label();
773 static void immediately_output_references()
775 for (int i = 0; i < ncitations; i++) {
776 reference *ref = citation[i];
777 if (label_in_reference) {
778 fputs(".ds [F ", outfp);
779 const string &label = ref->get_label(NORMAL_LABEL);
780 if (label.length() > 0
781 && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
783 put_string(label, outfp);
792 static void output_citation_group(reference **v, int n, label_type type,
795 if (sort_adjacent_labels) {
796 // Do an insertion sort. Usually n will be very small.
797 for (int i = 1; i < n; i++) {
798 int num = v[i]->get_number();
799 reference *temp = v[i];
801 for (j = i - 1; j >= 0 && v[j]->get_number() > num; j--)
806 // This messes up if !accumulate.
807 if (accumulate && n > 1) {
810 for (int i = 1; i < n; i++)
811 if (v[i]->get_label(type) != v[i - 1]->get_label(type))
816 for (int i = 0; i < n; i++) {
817 int nmerged = v[i]->merge_labels(v + i + 1, n - i - 1, type, merged_label);
819 put_string(merged_label, fp);
823 put_string(v[i]->get_label(type), fp);
825 put_string(sep_label, fp);
830 label_processing_state::label_processing_state(reference **p, int n, FILE *f)
831 : state(NORMAL), count(0), rptr(p), rcount(n), fp(f)
835 label_processing_state::~label_processing_state()
837 int handled = handle_pending(EOF);
842 int label_processing_state::handle_pending(int c)
848 if (c == POST_LABEL_MARKER) {
849 state = PENDING_LABEL_POST;
853 output_citation_group(rptr, count, type, fp);
859 case PENDING_LABEL_POST:
860 if (c == PRE_LABEL_MARKER) {
861 state = PENDING_LABEL_POST_PRE;
865 output_citation_group(rptr, count, type, fp);
868 put_string(post_label, fp);
872 case PENDING_LABEL_POST_PRE:
873 if (c >= LABEL_MARKER
874 && c < LABEL_MARKER + N_LABEL_TYPES
875 && c - LABEL_MARKER == type) {
877 state = PENDING_LABEL;
881 output_citation_group(rptr, count, type, fp);
884 put_string(sep_label, fp);
889 if (c == PRE_LABEL_MARKER) {
890 put_string(sep_label, fp);
895 put_string(post_label, fp);
903 void label_processing_state::process(int c)
905 if (handle_pending(c))
907 assert(state == NORMAL);
909 case PRE_LABEL_MARKER:
910 put_string(pre_label, fp);
913 case POST_LABEL_MARKER:
914 state = PENDING_POST;
917 case LABEL_MARKER + 1:
919 state = PENDING_LABEL;
920 type = label_type(c - LABEL_MARKER);
931 int rcompare(const void *p1, const void *p2)
933 return compare_reference(**(reference **)p1, **(reference **)p2);
938 void output_references()
941 if (nreferences > 0) {
944 for (i = 0; i < hash_table_size; i++)
945 if (reference_hash_table[i] != 0)
946 reference_hash_table[j++] = reference_hash_table[i];
947 assert(j == nreferences);
948 for (; j < hash_table_size; j++)
949 reference_hash_table[j] = 0;
950 qsort(reference_hash_table, nreferences, sizeof(reference*), rcompare);
951 for (i = 0; i < nreferences; i++)
952 reference_hash_table[i]->set_number(i);
953 compute_labels(reference_hash_table, nreferences);
955 if (outfp != stdout) {
958 label_processing_state state(citation, ncitations, stdout);
960 while ((c = getc(outfp)) != EOF)
967 if (nreferences > 0) {
968 fputs(".]<\n", outfp);
969 for (int i = 0; i < nreferences; i++) {
970 if (sort_fields.length() > 0)
971 reference_hash_table[i]->print_sort_key_comment(outfp);
972 if (label_in_reference) {
973 fputs(".ds [F ", outfp);
974 const string &label = reference_hash_table[i]->get_label(NORMAL_LABEL);
975 if (label.length() > 0
976 && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
978 put_string(label, outfp);
981 reference_hash_table[i]->output(outfp);
982 delete reference_hash_table[i];
983 reference_hash_table[i] = 0;
985 fputs(".]>\n", outfp);
991 static reference *find_reference(const char *query, int query_len)
993 // This is so that error messages look better.
994 while (query_len > 0 && csspace(query[query_len - 1]))
997 for (int i = 0; i < query_len; i++)
998 str += query[i] == '\n' ? ' ' : query[i];
1000 possibly_load_default_database();
1001 search_list_iterator iter(&database_list, str.contents());
1005 if (!iter.next(&start, &len, &rid)) {
1006 error("no matches for `%1'", str.contents());
1009 const char *end = start + len;
1010 while (start < end) {
1013 while (start < end && *start++ != '\n')
1017 error("found a reference for `%1' but it didn't contain any fields",
1021 reference *result = new reference(start, end - start, &rid);
1022 if (iter.next(&start, &len, &rid))
1023 warning("multiple matches for `%1'", str.contents());
1027 static reference *make_reference(const string &str, unsigned *flagsp)
1029 const char *start = str.contents();
1030 const char *end = start + str.length();
1031 const char *ptr = start;
1035 while (ptr < end && *ptr++ != '\n')
1039 for (; start < ptr; start++) {
1041 *flagsp = (SHORT_LABEL | (*flagsp & (FORCE_RIGHT_BRACKET
1042 | FORCE_LEFT_BRACKET)));
1043 else if (*start == '[')
1044 *flagsp |= FORCE_LEFT_BRACKET;
1045 else if (*start == ']')
1046 *flagsp |= FORCE_RIGHT_BRACKET;
1047 else if (!csspace(*start))
1051 error("empty reference");
1052 return new reference;
1054 reference *database_ref = 0;
1056 database_ref = find_reference(start, ptr - start);
1057 reference *inline_ref = 0;
1059 inline_ref = new reference(ptr, end - ptr);
1062 database_ref->merge(*inline_ref);
1064 return database_ref;
1069 else if (database_ref)
1070 return database_ref;
1072 return new reference;
1075 static void do_ref(const string &str)
1078 (void)store_reference(str);
1080 (void)immediately_handle_reference(str);
1081 immediately_output_references();
1085 static void trim_blanks(string &str)
1087 const char *start = str.contents();
1088 const char *end = start + str.length();
1089 while (end > start && end[-1] != '\n' && csspace(end[-1]))
1091 str.set_length(end - start);
1094 void do_bib(const char *filename)
1097 if (strcmp(filename, "-") == 0)
1101 fp = fopen(filename, "r");
1103 error("can't open `%1': %2", filename, strerror(errno));
1106 current_filename = filename;
1109 START, MIDDLE, BODY, BODY_START, BODY_BLANK, BODY_DOT
1116 if (invalid_input_char(c)) {
1117 error("invalid input character code %1", c);
1145 else if (csspace(c)) {
1160 else if (csspace(c))
1175 state = c == '\n' ? BODY_START : BODY;
1204 // from the Dragon Book
1206 unsigned hash_string(const char *s, int len)
1208 const char *end = s + len;
1213 if ((g = h & 0xf0000000) != 0) {
1221 int next_size(int n)
1223 static const int table_sizes[] = {
1224 101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009,
1225 80021, 160001, 500009, 1000003, 2000003, 4000037, 8000009,
1226 16000057, 32000011, 64000031, 128000003, 0
1230 for (p = table_sizes; *p <= n && *p != 0; p++)