2 /* Copyright (C) 1989-2018 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 3 of the License, or
10 (at your option) any later 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
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
27 extern "C" const char *Version_string;
29 const char PRE_LABEL_MARKER = '\013';
30 const char POST_LABEL_MARKER = '\014';
31 const char LABEL_MARKER = '\015'; // label_type is added on
33 #define FORCE_LEFT_BRACKET 04
34 #define FORCE_RIGHT_BRACKET 010
36 static FILE *outfp = stdout;
38 string capitalize_fields;
39 string reverse_fields;
40 string abbreviate_fields;
41 string period_before_last_name = ". ";
42 string period_before_initial = ".";
43 string period_before_hyphen = "";
44 string period_before_other = ". ";
46 int annotation_field = -1;
47 string annotation_macro;
48 string discard_fields = "XYZ";
49 string pre_label = "\\*([.";
50 string post_label = "\\*(.]";
51 string sep_label = ", ";
52 int have_bibliography = 0;
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));
437 normalize_for_lf(fn);
438 current_filename = fn.contents();
439 fprintf(outfp, ".lf 1 %s\n", current_filename);
447 if (line.length() > 0)
451 if (invalid_input_char(c))
452 error("invalid input character code %1", c);
459 int len = line.length();
463 if (len >= 2 && line[0] == '.' && line[1] == '[') {
464 int start_lineno = current_lineno;
465 int start_of_line = 1;
468 string pre(line.contents() + 2, line.length() - 3);
472 error_with_file_and_line(current_filename, start_lineno,
473 "missing '.]' line");
478 if (start_of_line && c == '.') {
481 while ((d = getc(fp)) != '\n' && d != EOF) {
482 if (invalid_input_char(d))
483 error("invalid input character code %1", d);
492 if (invalid_input_char(c))
493 error("invalid input character code %1", c);
496 start_of_line = (c == '\n');
499 output_pending_line();
503 error("found '$LIST$' but not accumulating references");
506 unsigned flags = (accumulate
507 ? store_reference(str)
508 : immediately_handle_reference(str));
510 if (accumulate && outfp == stdout)
511 divert_to_temporary_file();
512 if (pending_line.length() == 0) {
513 warning("can't attach citation to previous line");
516 pending_line.set_length(pending_line.length() - 1);
518 if (move_punctuation)
519 split_punct(pending_line, punct);
520 int have_text = pre.length() > 0 || post.length() > 0;
521 label_type lt = label_type(flags & ~(FORCE_LEFT_BRACKET
522 |FORCE_RIGHT_BRACKET));
523 if ((flags & FORCE_LEFT_BRACKET) || !have_text)
524 pending_line += PRE_LABEL_MARKER;
526 char lm = LABEL_MARKER + (int)lt;
528 pending_line += post;
529 if ((flags & FORCE_RIGHT_BRACKET) || !have_text)
530 pending_line += POST_LABEL_MARKER;
531 pending_line += punct;
532 pending_line += '\n';
538 && line[0] == '.' && line[1] == 'l' && line[2] == 'f'
539 && (compatible_flag || line[3] == '\n' || line[3] == ' ')) {
540 pending_lf_lines += line;
542 if (interpret_lf_args(line.contents() + 3))
545 else if (recognize_R1_R2
547 && line[0] == '.' && line[1] == 'R' && line[2] == '1'
548 && (compatible_flag || line[3] == '\n' || line[3] == ' ')) {
550 int start_of_line = 1;
551 int start_lineno = current_lineno;
554 if (c != EOF && start_of_line)
556 if (start_of_line && c == '.') {
562 if (compatible_flag || c == ' ' || c == '\n' || c == EOF) {
563 while (c != EOF && c != '\n')
582 error_with_file_and_line(current_filename, start_lineno,
583 "missing '.R2' line");
586 if (invalid_input_char(c))
587 error("invalid input character code %1", int(c));
590 start_of_line = c == '\n';
593 output_pending_line();
598 process_commands(line, current_filename, start_lineno + 1);
602 output_pending_line();
607 output_pending_line();
612 class label_processing_state {
617 PENDING_LABEL_POST_PRE,
620 label_type type; // type of pending labels
621 int count; // number of pending labels
622 reference **rptr; // pointer to next reference
623 int rcount; // number of references left
625 int handle_pending(int c);
627 label_processing_state(reference **, int, FILE *);
628 ~label_processing_state();
632 static void output_pending_line()
634 if (label_in_text && !accumulate && ncitations > 0) {
635 label_processing_state state(citation, ncitations, outfp);
636 int len = pending_line.length();
637 for (int i = 0; i < len; i++)
638 state.process((unsigned char)(pending_line[i]));
641 put_string(pending_line, outfp);
642 pending_line.clear();
643 if (pending_lf_lines.length() > 0) {
644 put_string(pending_lf_lines, outfp);
645 pending_lf_lines.clear();
648 immediately_output_references();
650 fprintf(outfp, ".lf %d %s\n", current_lineno, current_filename);
655 static void split_punct(string &line, string &punct)
657 const char *start = line.contents();
658 const char *end = start + line.length();
659 const char *ptr = start;
660 const char *last_token_start = 0;
664 last_token_start = ptr;
665 if (*ptr == PRE_LABEL_MARKER || *ptr == POST_LABEL_MARKER
666 || (*ptr >= LABEL_MARKER && *ptr < LABEL_MARKER + N_LABEL_TYPES))
668 else if (!get_token(&ptr, end))
671 if (last_token_start) {
672 const token_info *ti = lookup_token(last_token_start, end);
673 if (ti->is_punct()) {
674 punct.append(last_token_start, end - last_token_start);
675 line.set_length(last_token_start - start);
680 static void divert_to_temporary_file()
685 static void store_citation(reference *ref)
687 if (ncitations >= citation_max) {
689 citation = new reference*[citation_max = 100];
691 reference **old_citation = citation;
693 citation = new reference *[citation_max];
694 memcpy(citation, old_citation, ncitations*sizeof(reference *));
695 a_delete old_citation;
698 citation[ncitations++] = ref;
701 static unsigned store_reference(const string &str)
703 if (reference_hash_table == 0) {
704 reference_hash_table = new reference *[17];
705 hash_table_size = 17;
706 for (int i = 0; i < hash_table_size; i++)
707 reference_hash_table[i] = 0;
710 reference *ref = make_reference(str, &flags);
711 ref->compute_hash_code();
712 unsigned h = ref->hash();
714 for (ptr = reference_hash_table + (h % hash_table_size);
716 ((ptr == reference_hash_table)
717 ? (ptr = reference_hash_table + hash_table_size - 1)
719 if (same_reference(**ptr, *ref))
722 if (ref->is_merged())
723 warning("fields ignored because reference already used");
729 ref->set_number(nreferences);
731 ref->pre_compute_label();
732 ref->compute_sort_key();
733 if (nreferences*2 >= hash_table_size) {
735 reference **old_table = reference_hash_table;
736 int old_size = hash_table_size;
737 hash_table_size = next_size(hash_table_size);
738 reference_hash_table = new reference*[hash_table_size];
740 for (i = 0; i < hash_table_size; i++)
741 reference_hash_table[i] = 0;
742 for (i = 0; i < old_size; i++)
745 for (p = (reference_hash_table
746 + (old_table[i]->hash() % hash_table_size));
748 ((p == reference_hash_table)
749 ? (p = reference_hash_table + hash_table_size - 1)
762 unsigned immediately_handle_reference(const string &str)
765 reference *ref = make_reference(str, &flags);
766 ref->set_number(nreferences);
767 if (label_in_text || label_in_reference) {
768 ref->pre_compute_label();
769 ref->immediate_compute_label();
776 static void immediately_output_references()
778 for (int i = 0; i < ncitations; i++) {
779 reference *ref = citation[i];
780 if (label_in_reference) {
781 fputs(".ds [F ", outfp);
782 const string &label = ref->get_label(NORMAL_LABEL);
783 if (label.length() > 0
784 && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
786 put_string(label, outfp);
795 static void output_citation_group(reference **v, int n, label_type type,
798 if (sort_adjacent_labels) {
799 // Do an insertion sort. Usually n will be very small.
800 for (int i = 1; i < n; i++) {
801 int num = v[i]->get_number();
802 reference *temp = v[i];
804 for (j = i - 1; j >= 0 && v[j]->get_number() > num; j--)
809 // This messes up if !accumulate.
810 if (accumulate && n > 1) {
813 for (int i = 1; i < n; i++)
814 if (v[i]->get_label(type) != v[i - 1]->get_label(type))
819 for (int i = 0; i < n; i++) {
820 int nmerged = v[i]->merge_labels(v + i + 1, n - i - 1, type, merged_label);
822 put_string(merged_label, fp);
826 put_string(v[i]->get_label(type), fp);
828 put_string(sep_label, fp);
833 label_processing_state::label_processing_state(reference **p, int n, FILE *f)
834 : state(NORMAL), count(0), rptr(p), rcount(n), fp(f)
838 label_processing_state::~label_processing_state()
840 int handled = handle_pending(EOF);
845 int label_processing_state::handle_pending(int c)
851 if (c == POST_LABEL_MARKER) {
852 state = PENDING_LABEL_POST;
856 output_citation_group(rptr, count, type, fp);
862 case PENDING_LABEL_POST:
863 if (c == PRE_LABEL_MARKER) {
864 state = PENDING_LABEL_POST_PRE;
868 output_citation_group(rptr, count, type, fp);
871 put_string(post_label, fp);
875 case PENDING_LABEL_POST_PRE:
876 if (c >= LABEL_MARKER
877 && c < LABEL_MARKER + N_LABEL_TYPES
878 && c - LABEL_MARKER == type) {
880 state = PENDING_LABEL;
884 output_citation_group(rptr, count, type, fp);
887 put_string(sep_label, fp);
892 if (c == PRE_LABEL_MARKER) {
893 put_string(sep_label, fp);
898 put_string(post_label, fp);
906 void label_processing_state::process(int c)
908 if (handle_pending(c))
910 assert(state == NORMAL);
912 case PRE_LABEL_MARKER:
913 put_string(pre_label, fp);
916 case POST_LABEL_MARKER:
917 state = PENDING_POST;
920 case LABEL_MARKER + 1:
922 state = PENDING_LABEL;
923 type = label_type(c - LABEL_MARKER);
934 int rcompare(const void *p1, const void *p2)
936 return compare_reference(**(reference **)p1, **(reference **)p2);
941 void output_references()
944 if (!hash_table_size) {
945 if (have_bibliography)
946 error("nothing to reference (probably 'bibliography' before 'sort')");
951 if (nreferences > 0) {
954 for (i = 0; i < hash_table_size; i++)
955 if (reference_hash_table[i] != 0)
956 reference_hash_table[j++] = reference_hash_table[i];
957 assert(j == nreferences);
958 for (; j < hash_table_size; j++)
959 reference_hash_table[j] = 0;
960 qsort(reference_hash_table, nreferences, sizeof(reference*), rcompare);
961 for (i = 0; i < nreferences; i++)
962 reference_hash_table[i]->set_number(i);
963 compute_labels(reference_hash_table, nreferences);
965 if (outfp != stdout) {
968 label_processing_state state(citation, ncitations, stdout);
970 while ((c = getc(outfp)) != EOF)
977 if (nreferences > 0) {
978 fputs(".]<\n", outfp);
979 for (int i = 0; i < nreferences; i++) {
980 if (sort_fields.length() > 0)
981 reference_hash_table[i]->print_sort_key_comment(outfp);
982 if (label_in_reference) {
983 fputs(".ds [F ", outfp);
984 const string &label = reference_hash_table[i]->get_label(NORMAL_LABEL);
985 if (label.length() > 0
986 && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
988 put_string(label, outfp);
991 reference_hash_table[i]->output(outfp);
992 delete reference_hash_table[i];
993 reference_hash_table[i] = 0;
995 fputs(".]>\n", outfp);
1001 static reference *find_reference(const char *query, int query_len)
1003 // This is so that error messages look better.
1004 while (query_len > 0 && csspace(query[query_len - 1]))
1007 for (int i = 0; i < query_len; i++)
1008 str += query[i] == '\n' ? ' ' : query[i];
1010 possibly_load_default_database();
1011 search_list_iterator iter(&database_list, str.contents());
1015 if (!iter.next(&start, &len, &rid)) {
1016 error("no matches for '%1'", str.contents());
1019 const char *end = start + len;
1020 while (start < end) {
1023 while (start < end && *start++ != '\n')
1027 error("found a reference for '%1' but it didn't contain any fields",
1031 reference *result = new reference(start, end - start, &rid);
1032 if (iter.next(&start, &len, &rid))
1033 warning("multiple matches for '%1'", str.contents());
1037 static reference *make_reference(const string &str, unsigned *flagsp)
1039 const char *start = str.contents();
1040 const char *end = start + str.length();
1041 const char *ptr = start;
1045 while (ptr < end && *ptr++ != '\n')
1049 for (; start < ptr; start++) {
1051 *flagsp = (SHORT_LABEL | (*flagsp & (FORCE_RIGHT_BRACKET
1052 | FORCE_LEFT_BRACKET)));
1053 else if (*start == '[')
1054 *flagsp |= FORCE_LEFT_BRACKET;
1055 else if (*start == ']')
1056 *flagsp |= FORCE_RIGHT_BRACKET;
1057 else if (!csspace(*start))
1061 error("empty reference");
1062 return new reference;
1064 reference *database_ref = 0;
1066 database_ref = find_reference(start, ptr - start);
1067 reference *inline_ref = 0;
1069 inline_ref = new reference(ptr, end - ptr);
1072 database_ref->merge(*inline_ref);
1074 return database_ref;
1079 else if (database_ref)
1080 return database_ref;
1082 return new reference;
1085 static void do_ref(const string &str)
1088 (void)store_reference(str);
1090 (void)immediately_handle_reference(str);
1091 immediately_output_references();
1095 static void trim_blanks(string &str)
1097 const char *start = str.contents();
1098 const char *end = start + str.length();
1099 while (end > start && end[-1] != '\n' && csspace(end[-1]))
1101 str.set_length(end - start);
1104 void do_bib(const char *filename)
1107 if (strcmp(filename, "-") == 0)
1111 fp = fopen(filename, "r");
1113 error("can't open '%1': %2", filename, strerror(errno));
1116 current_filename = filename;
1119 START, MIDDLE, BODY, BODY_START, BODY_BLANK, BODY_DOT
1126 if (invalid_input_char(c)) {
1127 error("invalid input character code %1", c);
1155 else if (csspace(c)) {
1170 else if (csspace(c))
1185 state = c == '\n' ? BODY_START : BODY;
1214 // from the Dragon Book
1216 unsigned hash_string(const char *s, int len)
1218 const char *end = s + len;
1223 if ((g = h & 0xf0000000) != 0) {
1231 int next_size(int n)
1233 static const int table_sizes[] = {
1234 101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009,
1235 80021, 160001, 500009, 1000003, 2000003, 4000037, 8000009,
1236 16000057, 32000011, 64000031, 128000003, 0
1240 for (p = table_sizes; *p <= n && *p != 0; p++)