/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
Written by James Clark (jjc@jclark.com)
This file is part of groff.
groff is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2, or (at your option) any later
groff is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
You should have received a copy of the GNU General Public License along
with groff; see the file COPYING. If not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
const char PRE_LABEL_MARKER
= '\013';
const char POST_LABEL_MARKER
= '\014';
const char LABEL_MARKER
= '\015'; // label_type is added on
#define FORCE_LEFT_BRACKET 04
#define FORCE_RIGHT_BRACKET 010
static FILE *outfp
= stdout
;
string capitalize_fields
;
string abbreviate_fields
;
string period_before_last_name
= ". ";
string period_before_initial
= ".";
string period_before_hyphen
= "";
string period_before_other
= ". ";
int annotation_field
= -1;
string discard_fields
= "XYZ";
string pre_label
= "\\*([.";
string post_label
= "\\*(.]";
int move_punctuation
= 0;
int abbreviate_label_ranges
= 0;
string label_range_indicator
;
int label_in_reference
= 1;
int sort_adjacent_labels
= 0;
// Join exactly two authors with this.
string join_authors_exactly_two
= " and ";
// When there are more than two authors join the last two with this.
string join_authors_last_two
= ", and ";
// Otherwise join authors with this.
string join_authors_default
= ", ";
string separate_label_second_parts
= ", ";
// Use this string to represent that there are other authors.
// Use et al only if it can replace at least this many authors.
// Use et al only if the total number of authors is at least this.
int short_label_flag
= 0;
static int recognize_R1_R2
= 1;
search_list database_list
;
static int default_database_loaded
= 0;
static reference
**citation
= 0;
static int ncitations
= 0;
static int citation_max
= 0;
static reference
**reference_hash_table
= 0;
static int hash_table_size
;
static int nreferences
= 0;
static int need_syncing
= 0;
static void output_pending_line();
static unsigned immediately_handle_reference(const string
&);
static void immediately_output_references();
static unsigned store_reference(const string
&);
static void divert_to_temporary_file();
static reference
*make_reference(const string
&, unsigned *);
static void do_file(const char *);
static void split_punct(string
&line
, string
&punct
);
static void output_citation_group(reference
**v
, int n
, label_type
, FILE *fp
);
static void possibly_load_default_database();
int main(int argc
, char **argv
)
static char stderr_buf
[BUFSIZ
];
setbuf(stderr
, stderr_buf
);
int finished_options
= 0;
!finished_options
&& argc
> 0 && argv
[0][0] == '-'
const char *opt
= argv
[0] + 1;
while (opt
!= 0 && *opt
!= '\0') {
else if (csalnum(opt
[0]) && opt
[1] == '.' && opt
[2] != '\0') {
annotation_field
= opt
[0];
annotation_macro
= opt
+ 2;
// Not a very useful spec.
set_label_spec("(A.n|Q)', '(D.y|D)");
error("option `f' requires an argument");
for (const char *ptr
= num
; *ptr
; ptr
++)
error("bad character `%1' in argument to -f option", *ptr
);
set_label_spec(spec
.contents());
capitalize_fields
= ++opt
;
error("bad field name `%1'", *opt
++);
for (const char *ptr
= ++opt
; *ptr
; ptr
++)
error("argument to `a' option not a number");
linear_ignore_fields
= ++opt
;
char buf
[INT_DIGITS
*2 + 11]; // A.n+2D.y-3%a
if (*++opt
!= '\0' && *opt
!= ',') {
long n
= strtol(opt
, &ptr
, 10);
if (n
== 0 && ptr
== opt
) {
error("bad integer `%1' in `l' option", opt
);
sprintf(strchr(buf
, '\0'), "+%d", n
);
long n
= strtol(opt
, &ptr
, 10);
if (n
== 0 && ptr
== opt
) {
error("bad integer `%1' in `l' option", opt
);
sprintf(strchr(buf
, '\0'), "-%d", n
);
error("argument to `l' option not of form `m,n'");
if (!set_label_spec(buf
))
const char *filename
= 0;
error("option `p' requires an argument");
database_list
.add_file(filename
);
long n
= strtol(opt
, &ptr
, 10);
if (n
== 0 && ptr
== opt
) {
error("bad integer `%1' in `t' option", opt
);
linear_truncate_len
= int(n
);
extern const char *version_string
;
fprintf(stderr
, "GNU refer version %s\n", version_string
);
error("unrecognized option `%1'", *opt
);
for (int i
= 0; i
< argc
; i
++) {
"usage: %s [-benvCPRS] [-aN] [-cXYZ] [-fN] [-iXYZ] [-kX] [-lM,N] [-p file]\n"
" [-sXYZ] [-tN] [-BL.M] [files ...]\n",
static void possibly_load_default_database()
if (search_default
&& !default_database_loaded
) {
char *filename
= getenv("REFER");
database_list
.add_file(filename
);
database_list
.add_file(DEFAULT_INDEX
, 1);
default_database_loaded
= 1;
static int is_list(const string
&str
)
const char *start
= str
.contents();
const char *end
= start
+ str
.length();
while (end
> start
&& csspace(end
[-1]))
while (start
< end
&& csspace(*start
))
return end
- start
== 6 && memcmp(start
, "$LIST$", 6) == 0;
static void do_file(const char *filename
)
if (strcmp(filename
, "-") == 0) {
fp
= fopen(filename
, "r");
error("can't open `%1': %2", filename
, strerror(errno
));
current_filename
= filename
;
fprintf(outfp
, ".lf 1 %s\n", filename
);
if (illegal_input_char(c
))
error("illegal input character code %1", c
);
if (len
>= 2 && line
[0] == '.' && line
[1] == '[') {
int start_lineno
= current_lineno
;
string
pre(line
.contents() + 2, line
.length() - 3);
error_with_file_and_line(current_filename
, start_lineno
,
if (start_of_line
&& c
== '.') {
while ((d
= getc(fp
)) != '\n' && d
!= EOF
) {
if (illegal_input_char(d
))
error("illegal input character code %1", d
);
if (illegal_input_char(c
))
error("illegal input character code %1", c
);
start_of_line
= (c
== '\n');
error("found `$LIST$' but not accumulating references");
unsigned flags
= (accumulate
: immediately_handle_reference(str
));
if (accumulate
&& outfp
== stdout
)
divert_to_temporary_file();
if (pending_line
.length() == 0) {
warning("can't attach citation to previous line");
pending_line
.set_length(pending_line
.length() - 1);
split_punct(pending_line
, punct
);
int have_text
= pre
.length() > 0 || post
.length() > 0;
label_type lt
= label_type(flags
& ~(FORCE_LEFT_BRACKET
if ((flags
& FORCE_LEFT_BRACKET
) || !have_text
)
pending_line
+= PRE_LABEL_MARKER
;
pending_line
+= LABEL_MARKER
+ lt
;
if ((flags
& FORCE_RIGHT_BRACKET
) || !have_text
)
pending_line
+= POST_LABEL_MARKER
;
&& line
[0] == '.' && line
[1] == 'l' && line
[2] == 'f'
&& (compatible_flag
|| line
[3] == '\n' || line
[3] == ' ')) {
pending_lf_lines
+= line
;
if (interpret_lf_args(line
.contents() + 3))
&& line
[0] == '.' && line
[1] == 'R' && line
[2] == '1'
&& (compatible_flag
|| line
[3] == '\n' || line
[3] == ' ')) {
int start_lineno
= current_lineno
;
if (c
!= EOF
&& start_of_line
)
if (start_of_line
&& c
== '.') {
if (compatible_flag
|| c
== ' ' || c
== '\n' || c
== EOF
) {
while (c
!= EOF
&& c
!= '\n')
error_with_file_and_line(current_filename
, start_lineno
,
if (illegal_input_char(c
))
error("illegal input character code %1", int(c
));
start_of_line
= c
== '\n';
process_commands(line
, current_filename
, start_lineno
+ 1);
class label_processing_state
{
label_type type
; // type of pending labels
int count
; // number of pending labels
reference
**rptr
; // pointer to next reference
int rcount
; // number of references left
int handle_pending(int c
);
label_processing_state(reference
**, int, FILE *);
~label_processing_state();
static void output_pending_line()
if (label_in_text
&& !accumulate
&& ncitations
> 0) {
label_processing_state
state(citation
, ncitations
, outfp
);
int len
= pending_line
.length();
for (int i
= 0; i
< len
; i
++)
state
.process((unsigned char)(pending_line
[i
]));
put_string(pending_line
, outfp
);
if (pending_lf_lines
.length() > 0) {
put_string(pending_lf_lines
, outfp
);
pending_lf_lines
.clear();
immediately_output_references();
fprintf(outfp
, ".lf %d %s\n", current_lineno
, current_filename
);
static void split_punct(string
&line
, string
&punct
)
const char *start
= line
.contents();
const char *end
= start
+ line
.length();
const char *last_token_start
= 0;
if (*ptr
== PRE_LABEL_MARKER
|| *ptr
== POST_LABEL_MARKER
|| (*ptr
>= LABEL_MARKER
&& *ptr
< LABEL_MARKER
+ N_LABEL_TYPES
))
else if (!get_token(&ptr
, end
))
const token_info
*ti
= lookup_token(last_token_start
, end
);
punct
.append(last_token_start
, end
- last_token_start
);
line
.set_length(last_token_start
- start
);
static void divert_to_temporary_file()
static void store_citation(reference
*ref
)
if (ncitations
>= citation_max
) {
citation
= new reference
*[citation_max
= 100];
reference
**old_citation
= citation
;
citation
= new reference
*[citation_max
];
memcpy(citation
, old_citation
, ncitations
*sizeof(reference
*));
citation
[ncitations
++] = ref
;
static unsigned store_reference(const string
&str
)
if (reference_hash_table
== 0) {
reference_hash_table
= new reference
*[17];
for (int i
= 0; i
< hash_table_size
; i
++)
reference_hash_table
[i
] = 0;
reference
*ref
= make_reference(str
, &flags
);
ref
->compute_hash_code();
unsigned h
= ref
->hash();
for (reference
**ptr
= reference_hash_table
+ (h
% hash_table_size
);
((ptr
== reference_hash_table
)
? (ptr
= reference_hash_table
+ hash_table_size
- 1)
if (same_reference(**ptr
, *ref
))
warning("fields ignored because reference already used");
ref
->set_number(nreferences
);
ref
->pre_compute_label();
if (nreferences
*2 >= hash_table_size
) {
reference
**old_table
= reference_hash_table
;
int old_size
= hash_table_size
;
hash_table_size
= next_size(hash_table_size
);
reference_hash_table
= new reference
*[hash_table_size
];
for (i
= 0; i
< hash_table_size
; i
++)
reference_hash_table
[i
] = 0;
for (i
= 0; i
< old_size
; i
++)
for (reference
**p
= (reference_hash_table
+ (old_table
[i
]->hash() % hash_table_size
));
((p
== reference_hash_table
)
? (p
= reference_hash_table
+ hash_table_size
- 1)
unsigned immediately_handle_reference(const string
&str
)
reference
*ref
= make_reference(str
, &flags
);
ref
->set_number(nreferences
);
if (label_in_text
|| label_in_reference
) {
ref
->pre_compute_label();
ref
->immediate_compute_label();
static void immediately_output_references()
for (int i
= 0; i
< ncitations
; i
++) {
reference
*ref
= citation
[i
];
if (label_in_reference
) {
const string
&label
= ref
->get_label(NORMAL_LABEL
);
&& (label
[0] == ' ' || label
[0] == '\\' || label
[0] == '"'))
put_string(label
, outfp
);
static void output_citation_group(reference
**v
, int n
, label_type type
,
if (sort_adjacent_labels
) {
// Do an insertion sort. Usually n will be very small.
for (int i
= 1; i
< n
; i
++) {
int num
= v
[i
]->get_number();
for (int j
= i
- 1; j
>= 0 && v
[j
]->get_number() > num
; j
--)
// This messes up if !accumulate.
if (accumulate
&& n
> 1) {
for (int i
= 1; i
< n
; i
++)
if (v
[i
]->get_label(type
) != v
[i
- 1]->get_label(type
))
for (int i
= 0; i
< n
; i
++) {
int nmerged
= v
[i
]->merge_labels(v
+ i
+ 1, n
- i
- 1, type
, merged_label
);
put_string(merged_label
, fp
);
put_string(v
[i
]->get_label(type
), fp
);
put_string(sep_label
, fp
);
label_processing_state::label_processing_state(reference
**p
, int n
, FILE *f
)
: state(NORMAL
), count(0), rptr(p
), rcount(n
), fp(f
)
label_processing_state::~label_processing_state()
int handled
= handle_pending(EOF
);
int label_processing_state::handle_pending(int c
)
if (c
== POST_LABEL_MARKER
) {
state
= PENDING_LABEL_POST
;
output_citation_group(rptr
, count
, type
, fp
);
if (c
== PRE_LABEL_MARKER
) {
state
= PENDING_LABEL_POST_PRE
;
output_citation_group(rptr
, count
, type
, fp
);
put_string(post_label
, fp
);
case PENDING_LABEL_POST_PRE
:
&& c
< LABEL_MARKER
+ N_LABEL_TYPES
&& c
- LABEL_MARKER
== type
) {
output_citation_group(rptr
, count
, type
, fp
);
put_string(sep_label
, fp
);
if (c
== PRE_LABEL_MARKER
) {
put_string(sep_label
, fp
);
put_string(post_label
, fp
);
void label_processing_state::process(int c
)
put_string(pre_label
, fp
);
type
= label_type(c
- LABEL_MARKER
);
static int rcompare(const void *p1
, const void *p2
)
return compare_reference(**(reference
**)p1
, **(reference
**)p2
);
for (i
= 0; i
< hash_table_size
; i
++)
if (reference_hash_table
[i
] != 0)
reference_hash_table
[j
++] = reference_hash_table
[i
];
assert(j
== nreferences
);
for (; j
< hash_table_size
; j
++)
reference_hash_table
[j
] = 0;
qsort(reference_hash_table
, nreferences
, sizeof(reference
*), rcompare
);
for (i
= 0; i
< nreferences
; i
++)
reference_hash_table
[i
]->set_number(i
);
compute_labels(reference_hash_table
, nreferences
);
label_processing_state
state(citation
, ncitations
, stdout
);
while ((c
= getc(outfp
)) != EOF
)
for (int i
= 0; i
< nreferences
; i
++) {
if (sort_fields
.length() > 0)
reference_hash_table
[i
]->print_sort_key_comment(outfp
);
if (label_in_reference
) {
const string
&label
= reference_hash_table
[i
]->get_label(NORMAL_LABEL
);
&& (label
[0] == ' ' || label
[0] == '\\' || label
[0] == '"'))
put_string(label
, outfp
);
reference_hash_table
[i
]->output(outfp
);
delete reference_hash_table
[i
];
reference_hash_table
[i
] = 0;
static reference
*find_reference(const char *query
, int query_len
)
// This is so that error messages look better.
while (query_len
> 0 && csspace(query
[query_len
- 1]))
for (int i
= 0; i
< query_len
; i
++)
str
+= query
[i
] == '\n' ? ' ' : query
[i
];
possibly_load_default_database();
search_list_iterator
iter(&database_list
, str
.contents());
if (!iter
.next(&start
, &len
, &rid
)) {
error("no matches for `%1'", str
.contents());
const char *end
= start
+ len
;
while (start
< end
&& *start
++ != '\n')
error("found a reference for `%1' but it didn't contain any fields",
reference
*result
= new reference(start
, end
- start
, &rid
);
if (iter
.next(&start
, &len
, &rid
))
warning("multiple matches for `%1'", str
.contents());
static reference
*make_reference(const string
&str
, unsigned *flagsp
)
const char *start
= str
.contents();
const char *end
= start
+ str
.length();
while (ptr
< end
&& *ptr
++ != '\n')
for (; start
< ptr
; start
++) {
*flagsp
= (SHORT_LABEL
| (*flagsp
& (FORCE_RIGHT_BRACKET
*flagsp
|= FORCE_LEFT_BRACKET
;
*flagsp
|= FORCE_RIGHT_BRACKET
;
else if (!csspace(*start
))
error("empty reference");
reference
*database_ref
= 0;
database_ref
= find_reference(start
, ptr
- start
);
reference
*inline_ref
= 0;
inline_ref
= new reference(ptr
, end
- ptr
);
database_ref
->merge(*inline_ref
);
static void do_ref(const string
&str
)
(void)store_reference(str
);
(void)immediately_handle_reference(str
);
immediately_output_references();
static void trim_blanks(string
&str
)
const char *start
= str
.contents();
const char *end
= start
+ str
.length();
while (end
> start
&& end
[-1] != '\n' && csspace(end
[-1]))
str
.set_length(end
- start
);
void do_bib(const char *filename
)
if (strcmp(filename
, "-") == 0)
fp
= fopen(filename
, "r");
error("can't open `%1': %2", filename
, strerror(errno
));
current_filename
= filename
;
START
, MIDDLE
, BODY
, BODY_START
, BODY_BLANK
, BODY_DOT
if (illegal_input_char(c
)) {
error("illegal input character code %1", c
);
state
= c
== '\n' ? BODY_START
: BODY
;
unsigned hash_string(const char *s
, int len
)
const char *end
= s
+ len
;
if ((g
= h
& 0xf0000000) != 0) {
static const int table_sizes
[] = {
101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009,
80021, 160001, 500009, 1000003, 2000003, 4000037, 8000009,
16000057, 32000011, 64000031, 128000003, 0
for (const int *p
= table_sizes
; *p
<= n
&& *p
!= 0; p
++)