808 lines
18 KiB
C++
808 lines
18 KiB
C++
|
// -*- C++ -*-
|
||
|
/* 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
|
||
|
version.
|
||
|
|
||
|
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
|
||
|
for more details.
|
||
|
|
||
|
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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
|
||
|
|
||
|
#include "refer.h"
|
||
|
#include "refid.h"
|
||
|
#include "search.h"
|
||
|
#include "command.h"
|
||
|
|
||
|
cset cs_field_name = csalpha;
|
||
|
|
||
|
class input_item {
|
||
|
input_item *next;
|
||
|
char *filename;
|
||
|
int first_lineno;
|
||
|
string buffer;
|
||
|
const char *ptr;
|
||
|
const char *end;
|
||
|
public:
|
||
|
input_item(string &, const char *, int = 1);
|
||
|
~input_item();
|
||
|
int get_char();
|
||
|
int peek_char();
|
||
|
void skip_char();
|
||
|
int get_location(const char **, int *);
|
||
|
|
||
|
friend class input_stack;
|
||
|
};
|
||
|
|
||
|
input_item::input_item(string &s, const char *fn, int ln)
|
||
|
: filename(strsave(fn)), first_lineno(ln)
|
||
|
{
|
||
|
buffer.move(s);
|
||
|
ptr = buffer.contents();
|
||
|
end = ptr + buffer.length();
|
||
|
}
|
||
|
|
||
|
input_item::~input_item()
|
||
|
{
|
||
|
a_delete filename;
|
||
|
}
|
||
|
|
||
|
inline int input_item::peek_char()
|
||
|
{
|
||
|
if (ptr >= end)
|
||
|
return EOF;
|
||
|
else
|
||
|
return (unsigned char)*ptr;
|
||
|
}
|
||
|
|
||
|
inline int input_item::get_char()
|
||
|
{
|
||
|
if (ptr >= end)
|
||
|
return EOF;
|
||
|
else
|
||
|
return (unsigned char)*ptr++;
|
||
|
}
|
||
|
|
||
|
inline void input_item::skip_char()
|
||
|
{
|
||
|
ptr++;
|
||
|
}
|
||
|
|
||
|
int input_item::get_location(const char **filenamep, int *linenop)
|
||
|
{
|
||
|
*filenamep = filename;
|
||
|
if (ptr == buffer.contents())
|
||
|
*linenop = first_lineno;
|
||
|
else {
|
||
|
int ln = first_lineno;
|
||
|
const char *e = ptr - 1;
|
||
|
for (const char *p = buffer.contents(); p < e; p++)
|
||
|
if (*p == '\n')
|
||
|
ln++;
|
||
|
*linenop = ln;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
class input_stack {
|
||
|
static input_item *top;
|
||
|
public:
|
||
|
static void init();
|
||
|
static int get_char();
|
||
|
static int peek_char();
|
||
|
static void skip_char() { top->skip_char(); }
|
||
|
static void push_file(const char *);
|
||
|
static void push_string(string &, const char *, int);
|
||
|
static void error(const char *format,
|
||
|
const errarg &arg1 = empty_errarg,
|
||
|
const errarg &arg2 = empty_errarg,
|
||
|
const errarg &arg3 = empty_errarg);
|
||
|
};
|
||
|
|
||
|
input_item *input_stack::top = 0;
|
||
|
|
||
|
void input_stack::init()
|
||
|
{
|
||
|
while (top) {
|
||
|
input_item *tem = top;
|
||
|
top = top->next;
|
||
|
delete tem;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int input_stack::get_char()
|
||
|
{
|
||
|
while (top) {
|
||
|
int c = top->get_char();
|
||
|
if (c >= 0)
|
||
|
return c;
|
||
|
input_item *tem = top;
|
||
|
top = top->next;
|
||
|
delete tem;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int input_stack::peek_char()
|
||
|
{
|
||
|
while (top) {
|
||
|
int c = top->peek_char();
|
||
|
if (c >= 0)
|
||
|
return c;
|
||
|
input_item *tem = top;
|
||
|
top = top->next;
|
||
|
delete tem;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
void input_stack::push_file(const char *fn)
|
||
|
{
|
||
|
FILE *fp;
|
||
|
if (strcmp(fn, "-") == 0) {
|
||
|
fp = stdin;
|
||
|
fn = "<standard input>";
|
||
|
}
|
||
|
else {
|
||
|
errno = 0;
|
||
|
fp = fopen(fn, "r");
|
||
|
if (fp == 0) {
|
||
|
error("can't open `%1': %2", fn, strerror(errno));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
string buf;
|
||
|
int bol = 1;
|
||
|
int lineno = 1;
|
||
|
for (;;) {
|
||
|
int c = getc(fp);
|
||
|
if (bol && c == '.') {
|
||
|
// replace lines beginning with .R1 or .R2 with a blank line
|
||
|
c = getc(fp);
|
||
|
if (c == 'R') {
|
||
|
c = getc(fp);
|
||
|
if (c == '1' || c == '2') {
|
||
|
int cc = c;
|
||
|
c = getc(fp);
|
||
|
if (compatible_flag || c == ' ' || c == '\n' || c == EOF) {
|
||
|
while (c != '\n' && c != EOF)
|
||
|
c = getc(fp);
|
||
|
}
|
||
|
else {
|
||
|
buf += '.';
|
||
|
buf += 'R';
|
||
|
buf += cc;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
buf += '.';
|
||
|
buf += 'R';
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
buf += '.';
|
||
|
}
|
||
|
if (c == EOF)
|
||
|
break;
|
||
|
if (illegal_input_char(c))
|
||
|
error_with_file_and_line(fn, lineno,
|
||
|
"illegal input character code %1", int(c));
|
||
|
else {
|
||
|
buf += c;
|
||
|
if (c == '\n') {
|
||
|
bol = 1;
|
||
|
lineno++;
|
||
|
}
|
||
|
else
|
||
|
bol = 0;
|
||
|
}
|
||
|
}
|
||
|
if (fp != stdin)
|
||
|
fclose(fp);
|
||
|
if (buf.length() > 0 && buf[buf.length() - 1] != '\n')
|
||
|
buf += '\n';
|
||
|
input_item *it = new input_item(buf, fn);
|
||
|
it->next = top;
|
||
|
top = it;
|
||
|
}
|
||
|
|
||
|
void input_stack::push_string(string &s, const char *filename, int lineno)
|
||
|
{
|
||
|
input_item *it = new input_item(s, filename, lineno);
|
||
|
it->next = top;
|
||
|
top = it;
|
||
|
}
|
||
|
|
||
|
void input_stack::error(const char *format, const errarg &arg1,
|
||
|
const errarg &arg2, const errarg &arg3)
|
||
|
{
|
||
|
const char *filename;
|
||
|
int lineno;
|
||
|
for (input_item *it = top; it; it = it->next)
|
||
|
if (it->get_location(&filename, &lineno)) {
|
||
|
error_with_file_and_line(filename, lineno, format, arg1, arg2, arg3);
|
||
|
return;
|
||
|
}
|
||
|
::error(format, arg1, arg2, arg3);
|
||
|
}
|
||
|
|
||
|
void command_error(const char *format, const errarg &arg1,
|
||
|
const errarg &arg2, const errarg &arg3)
|
||
|
{
|
||
|
input_stack::error(format, arg1, arg2, arg3);
|
||
|
}
|
||
|
|
||
|
// # not recognized in ""
|
||
|
// \<newline> is recognized in ""
|
||
|
// # does not conceal newline
|
||
|
// if missing closing quote, word extends to end of line
|
||
|
// no special treatment of \ other than before newline
|
||
|
// \<newline> not recognized after #
|
||
|
// ; allowed as alternative to newline
|
||
|
// ; not recognized in ""
|
||
|
// don't clear word_buffer; just append on
|
||
|
// return -1 for EOF, 0 for newline, 1 for word
|
||
|
|
||
|
int get_word(string &word_buffer)
|
||
|
{
|
||
|
int c = input_stack::get_char();
|
||
|
for (;;) {
|
||
|
if (c == '#') {
|
||
|
do {
|
||
|
c = input_stack::get_char();
|
||
|
} while (c != '\n' && c != EOF);
|
||
|
break;
|
||
|
}
|
||
|
if (c == '\\' && input_stack::peek_char() == '\n')
|
||
|
input_stack::skip_char();
|
||
|
else if (c != ' ' && c != '\t')
|
||
|
break;
|
||
|
c = input_stack::get_char();
|
||
|
}
|
||
|
if (c == EOF)
|
||
|
return -1;
|
||
|
if (c == '\n' || c == ';')
|
||
|
return 0;
|
||
|
if (c == '"') {
|
||
|
for (;;) {
|
||
|
c = input_stack::peek_char();
|
||
|
if (c == EOF || c == '\n')
|
||
|
break;
|
||
|
input_stack::skip_char();
|
||
|
if (c == '"') {
|
||
|
int d = input_stack::peek_char();
|
||
|
if (d == '"')
|
||
|
input_stack::skip_char();
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
else if (c == '\\') {
|
||
|
int d = input_stack::peek_char();
|
||
|
if (d == '\n')
|
||
|
input_stack::skip_char();
|
||
|
else
|
||
|
word_buffer += '\\';
|
||
|
}
|
||
|
else
|
||
|
word_buffer += c;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
word_buffer += c;
|
||
|
for (;;) {
|
||
|
c = input_stack::peek_char();
|
||
|
if (c == ' ' || c == '\t' || c == '\n' || c == '#' || c == ';')
|
||
|
break;
|
||
|
input_stack::skip_char();
|
||
|
if (c == '\\') {
|
||
|
int d = input_stack::peek_char();
|
||
|
if (d == '\n')
|
||
|
input_stack::skip_char();
|
||
|
else
|
||
|
word_buffer += '\\';
|
||
|
}
|
||
|
else
|
||
|
word_buffer += c;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
union argument {
|
||
|
const char *s;
|
||
|
int n;
|
||
|
};
|
||
|
|
||
|
// This is for debugging.
|
||
|
|
||
|
static void echo_command(int argc, argument *argv)
|
||
|
{
|
||
|
for (int i = 0; i < argc; i++)
|
||
|
fprintf(stderr, "%s\n", argv[i].s);
|
||
|
}
|
||
|
|
||
|
static void include_command(int argc, argument *argv)
|
||
|
{
|
||
|
assert(argc == 1);
|
||
|
input_stack::push_file(argv[0].s);
|
||
|
}
|
||
|
|
||
|
static void capitalize_command(int argc, argument *argv)
|
||
|
{
|
||
|
if (argc > 0)
|
||
|
capitalize_fields = argv[0].s;
|
||
|
else
|
||
|
capitalize_fields.clear();
|
||
|
}
|
||
|
|
||
|
static void accumulate_command(int, argument *)
|
||
|
{
|
||
|
accumulate = 1;
|
||
|
}
|
||
|
|
||
|
static void no_accumulate_command(int, argument *)
|
||
|
{
|
||
|
accumulate = 0;
|
||
|
}
|
||
|
|
||
|
static void move_punctuation_command(int, argument *)
|
||
|
{
|
||
|
move_punctuation = 1;
|
||
|
}
|
||
|
|
||
|
static void no_move_punctuation_command(int, argument *)
|
||
|
{
|
||
|
move_punctuation = 0;
|
||
|
}
|
||
|
|
||
|
static void sort_command(int argc, argument *argv)
|
||
|
{
|
||
|
if (argc == 0)
|
||
|
sort_fields = "AD";
|
||
|
else
|
||
|
sort_fields = argv[0].s;
|
||
|
accumulate = 1;
|
||
|
}
|
||
|
|
||
|
static void no_sort_command(int, argument *)
|
||
|
{
|
||
|
sort_fields.clear();
|
||
|
}
|
||
|
|
||
|
static void articles_command(int argc, argument *argv)
|
||
|
{
|
||
|
articles.clear();
|
||
|
int i;
|
||
|
for (i = 0; i < argc; i++) {
|
||
|
articles += argv[i].s;
|
||
|
articles += '\0';
|
||
|
}
|
||
|
int len = articles.length();
|
||
|
for (i = 0; i < len; i++)
|
||
|
articles[i] = cmlower(articles[i]);
|
||
|
}
|
||
|
|
||
|
static void database_command(int argc, argument *argv)
|
||
|
{
|
||
|
for (int i = 0; i < argc; i++)
|
||
|
database_list.add_file(argv[i].s);
|
||
|
}
|
||
|
|
||
|
static void default_database_command(int, argument *)
|
||
|
{
|
||
|
search_default = 1;
|
||
|
}
|
||
|
|
||
|
static void no_default_database_command(int, argument *)
|
||
|
{
|
||
|
search_default = 0;
|
||
|
}
|
||
|
|
||
|
static void bibliography_command(int argc, argument *argv)
|
||
|
{
|
||
|
const char *saved_filename = current_filename;
|
||
|
int saved_lineno = current_lineno;
|
||
|
int saved_label_in_text = label_in_text;
|
||
|
label_in_text = 0;
|
||
|
if (!accumulate)
|
||
|
fputs(".]<\n", stdout);
|
||
|
for (int i = 0; i < argc; i++)
|
||
|
do_bib(argv[i].s);
|
||
|
if (accumulate)
|
||
|
output_references();
|
||
|
else
|
||
|
fputs(".]>\n", stdout);
|
||
|
current_filename = saved_filename;
|
||
|
current_lineno = saved_lineno;
|
||
|
label_in_text = saved_label_in_text;
|
||
|
}
|
||
|
|
||
|
static void annotate_command(int argc, argument *argv)
|
||
|
{
|
||
|
if (argc > 0)
|
||
|
annotation_field = argv[0].s[0];
|
||
|
else
|
||
|
annotation_field = 'X';
|
||
|
if (argc == 2)
|
||
|
annotation_macro = argv[1].s;
|
||
|
else
|
||
|
annotation_macro = "AP";
|
||
|
}
|
||
|
|
||
|
static void no_annotate_command(int, argument *)
|
||
|
{
|
||
|
annotation_macro.clear();
|
||
|
annotation_field = -1;
|
||
|
}
|
||
|
|
||
|
static void reverse_command(int, argument *argv)
|
||
|
{
|
||
|
reverse_fields = argv[0].s;
|
||
|
}
|
||
|
|
||
|
static void no_reverse_command(int, argument *)
|
||
|
{
|
||
|
reverse_fields.clear();
|
||
|
}
|
||
|
|
||
|
static void abbreviate_command(int argc, argument *argv)
|
||
|
{
|
||
|
abbreviate_fields = argv[0].s;
|
||
|
period_before_initial = argc > 1 ? argv[1].s : ". ";
|
||
|
period_before_last_name = argc > 2 ? argv[2].s : ". ";
|
||
|
period_before_other = argc > 3 ? argv[3].s : ". ";
|
||
|
period_before_hyphen = argc > 4 ? argv[4].s : ".";
|
||
|
}
|
||
|
|
||
|
static void no_abbreviate_command(int, argument *)
|
||
|
{
|
||
|
abbreviate_fields.clear();
|
||
|
}
|
||
|
|
||
|
string search_ignore_fields;
|
||
|
|
||
|
static void search_ignore_command(int argc, argument *argv)
|
||
|
{
|
||
|
if (argc > 0)
|
||
|
search_ignore_fields = argv[0].s;
|
||
|
else
|
||
|
search_ignore_fields = "XYZ";
|
||
|
search_ignore_fields += '\0';
|
||
|
linear_ignore_fields = search_ignore_fields.contents();
|
||
|
}
|
||
|
|
||
|
static void no_search_ignore_command(int, argument *)
|
||
|
{
|
||
|
linear_ignore_fields = "";
|
||
|
}
|
||
|
|
||
|
static void search_truncate_command(int argc, argument *argv)
|
||
|
{
|
||
|
if (argc > 0)
|
||
|
linear_truncate_len = argv[0].n;
|
||
|
else
|
||
|
linear_truncate_len = 6;
|
||
|
}
|
||
|
|
||
|
static void no_search_truncate_command(int, argument *)
|
||
|
{
|
||
|
linear_truncate_len = -1;
|
||
|
}
|
||
|
|
||
|
static void discard_command(int argc, argument *argv)
|
||
|
{
|
||
|
if (argc == 0)
|
||
|
discard_fields = "XYZ";
|
||
|
else
|
||
|
discard_fields = argv[0].s;
|
||
|
accumulate = 1;
|
||
|
}
|
||
|
|
||
|
static void no_discard_command(int, argument *)
|
||
|
{
|
||
|
discard_fields.clear();
|
||
|
}
|
||
|
|
||
|
static void label_command(int, argument *argv)
|
||
|
{
|
||
|
set_label_spec(argv[0].s);
|
||
|
}
|
||
|
|
||
|
static void abbreviate_label_ranges_command(int argc, argument *argv)
|
||
|
{
|
||
|
abbreviate_label_ranges = 1;
|
||
|
label_range_indicator = argc > 0 ? argv[0].s : "-";
|
||
|
}
|
||
|
|
||
|
static void no_abbreviate_label_ranges_command(int, argument *)
|
||
|
{
|
||
|
abbreviate_label_ranges = 0;
|
||
|
}
|
||
|
|
||
|
static void label_in_reference_command(int, argument *)
|
||
|
{
|
||
|
label_in_reference = 1;
|
||
|
}
|
||
|
|
||
|
static void no_label_in_reference_command(int, argument *)
|
||
|
{
|
||
|
label_in_reference = 0;
|
||
|
}
|
||
|
|
||
|
static void label_in_text_command(int, argument *)
|
||
|
{
|
||
|
label_in_text = 1;
|
||
|
}
|
||
|
|
||
|
static void no_label_in_text_command(int, argument *)
|
||
|
{
|
||
|
label_in_text = 0;
|
||
|
}
|
||
|
|
||
|
static void sort_adjacent_labels_command(int, argument *)
|
||
|
{
|
||
|
sort_adjacent_labels = 1;
|
||
|
}
|
||
|
|
||
|
static void no_sort_adjacent_labels_command(int, argument *)
|
||
|
{
|
||
|
sort_adjacent_labels = 0;
|
||
|
}
|
||
|
|
||
|
static void date_as_label_command(int argc, argument *argv)
|
||
|
{
|
||
|
if (set_date_label_spec(argc > 0 ? argv[0].s : "D%a*"))
|
||
|
date_as_label = 1;
|
||
|
}
|
||
|
|
||
|
static void no_date_as_label_command(int, argument *)
|
||
|
{
|
||
|
date_as_label = 0;
|
||
|
}
|
||
|
|
||
|
static void short_label_command(int, argument *argv)
|
||
|
{
|
||
|
if (set_short_label_spec(argv[0].s))
|
||
|
short_label_flag = 1;
|
||
|
}
|
||
|
|
||
|
static void no_short_label_command(int, argument *)
|
||
|
{
|
||
|
short_label_flag = 0;
|
||
|
}
|
||
|
|
||
|
static void compatible_command(int, argument *)
|
||
|
{
|
||
|
compatible_flag = 1;
|
||
|
}
|
||
|
|
||
|
static void no_compatible_command(int, argument *)
|
||
|
{
|
||
|
compatible_flag = 0;
|
||
|
}
|
||
|
|
||
|
static void join_authors_command(int argc, argument *argv)
|
||
|
{
|
||
|
join_authors_exactly_two = argv[0].s;
|
||
|
join_authors_default = argc > 1 ? argv[1].s : argv[0].s;
|
||
|
join_authors_last_two = argc == 3 ? argv[2].s : argv[0].s;
|
||
|
}
|
||
|
|
||
|
static void bracket_label_command(int, argument *argv)
|
||
|
{
|
||
|
pre_label = argv[0].s;
|
||
|
post_label = argv[1].s;
|
||
|
sep_label = argv[2].s;
|
||
|
}
|
||
|
|
||
|
static void separate_label_second_parts_command(int, argument *argv)
|
||
|
{
|
||
|
separate_label_second_parts = argv[0].s;
|
||
|
}
|
||
|
|
||
|
static void et_al_command(int argc, argument *argv)
|
||
|
{
|
||
|
et_al = argv[0].s;
|
||
|
et_al_min_elide = argv[1].n;
|
||
|
if (et_al_min_elide < 1)
|
||
|
et_al_min_elide = 1;
|
||
|
et_al_min_total = argc >= 3 ? argv[2].n : 0;
|
||
|
}
|
||
|
|
||
|
static void no_et_al_command(int, argument *)
|
||
|
{
|
||
|
et_al.clear();
|
||
|
et_al_min_elide = 0;
|
||
|
}
|
||
|
|
||
|
typedef void (*command_t)(int, argument *);
|
||
|
|
||
|
/* arg_types is a string describing the numbers and types of arguments.
|
||
|
s means a string, i means an integer, f is a list of fields, F is
|
||
|
a single field,
|
||
|
? means that the previous argument is optional, * means that the
|
||
|
previous argument can occur any number of times. */
|
||
|
|
||
|
struct {
|
||
|
const char *name;
|
||
|
command_t func;
|
||
|
const char *arg_types;
|
||
|
} command_table[] = {
|
||
|
{ "include", include_command, "s" },
|
||
|
{ "echo", echo_command, "s*" },
|
||
|
{ "capitalize", capitalize_command, "f?" },
|
||
|
{ "accumulate", accumulate_command, "" },
|
||
|
{ "no-accumulate", no_accumulate_command, "" },
|
||
|
{ "move-punctuation", move_punctuation_command, "" },
|
||
|
{ "no-move-punctuation", no_move_punctuation_command, "" },
|
||
|
{ "sort", sort_command, "s?" },
|
||
|
{ "no-sort", no_sort_command, "" },
|
||
|
{ "articles", articles_command, "s*" },
|
||
|
{ "database", database_command, "ss*" },
|
||
|
{ "default-database", default_database_command, "" },
|
||
|
{ "no-default-database", no_default_database_command, "" },
|
||
|
{ "bibliography", bibliography_command, "ss*" },
|
||
|
{ "annotate", annotate_command, "F?s?" },
|
||
|
{ "no-annotate", no_annotate_command, "" },
|
||
|
{ "reverse", reverse_command, "s" },
|
||
|
{ "no-reverse", no_reverse_command, "" },
|
||
|
{ "abbreviate", abbreviate_command, "ss?s?s?s?" },
|
||
|
{ "no-abbreviate", no_abbreviate_command, "" },
|
||
|
{ "search-ignore", search_ignore_command, "f?" },
|
||
|
{ "no-search-ignore", no_search_ignore_command, "" },
|
||
|
{ "search-truncate", search_truncate_command, "i?" },
|
||
|
{ "no-search-truncate", no_search_truncate_command, "" },
|
||
|
{ "discard", discard_command, "f?" },
|
||
|
{ "no-discard", no_discard_command, "" },
|
||
|
{ "label", label_command, "s" },
|
||
|
{ "abbreviate-label-ranges", abbreviate_label_ranges_command, "s?" },
|
||
|
{ "no-abbreviate-label-ranges", no_abbreviate_label_ranges_command, "" },
|
||
|
{ "label-in-reference", label_in_reference_command, "" },
|
||
|
{ "no-label-in-reference", no_label_in_reference_command, "" },
|
||
|
{ "label-in-text", label_in_text_command, "" },
|
||
|
{ "no-label-in-text", no_label_in_text_command, "" },
|
||
|
{ "sort-adjacent-labels", sort_adjacent_labels_command, "" },
|
||
|
{ "no-sort-adjacent-labels", no_sort_adjacent_labels_command, "" },
|
||
|
{ "date-as-label", date_as_label_command, "s?" },
|
||
|
{ "no-date-as-label", no_date_as_label_command, "" },
|
||
|
{ "short-label", short_label_command, "s" },
|
||
|
{ "no-short-label", no_short_label_command, "" },
|
||
|
{ "compatible", compatible_command, "" },
|
||
|
{ "no-compatible", no_compatible_command, "" },
|
||
|
{ "join-authors", join_authors_command, "sss?" },
|
||
|
{ "bracket-label", bracket_label_command, "sss" },
|
||
|
{ "separate-label-second-parts", separate_label_second_parts_command, "s" },
|
||
|
{ "et-al", et_al_command, "sii?" },
|
||
|
{ "no-et-al", no_et_al_command, "" },
|
||
|
};
|
||
|
|
||
|
static int check_args(const char *types, const char *name,
|
||
|
int argc, argument *argv)
|
||
|
{
|
||
|
int argno = 0;
|
||
|
while (*types) {
|
||
|
if (argc == 0) {
|
||
|
if (types[1] == '?')
|
||
|
break;
|
||
|
else if (types[1] == '*') {
|
||
|
assert(types[2] == '\0');
|
||
|
break;
|
||
|
}
|
||
|
else {
|
||
|
input_stack::error("missing argument for command `%1'", name);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
switch (*types) {
|
||
|
case 's':
|
||
|
break;
|
||
|
case 'i':
|
||
|
{
|
||
|
char *ptr;
|
||
|
long n = strtol(argv->s, &ptr, 10);
|
||
|
if ((n == 0 && ptr == argv->s)
|
||
|
|| *ptr != '\0') {
|
||
|
input_stack::error("argument %1 for command `%2' must be an integer",
|
||
|
argno + 1, name);
|
||
|
return 0;
|
||
|
}
|
||
|
argv->n = (int)n;
|
||
|
break;
|
||
|
}
|
||
|
case 'f':
|
||
|
{
|
||
|
for (const char *ptr = argv->s; *ptr != '\0'; ptr++)
|
||
|
if (!cs_field_name(*ptr)) {
|
||
|
input_stack::error("argument %1 for command `%2' must be a list of fields",
|
||
|
argno + 1, name);
|
||
|
return 0;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case 'F':
|
||
|
if (argv->s[0] == '\0' || argv->s[1] != '\0'
|
||
|
|| !cs_field_name(argv->s[0])) {
|
||
|
input_stack::error("argument %1 for command `%2' must be a field name",
|
||
|
argno + 1, name);
|
||
|
return 0;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
assert(0);
|
||
|
}
|
||
|
if (types[1] == '?')
|
||
|
types += 2;
|
||
|
else if (types[1] != '*')
|
||
|
types += 1;
|
||
|
--argc;
|
||
|
++argv;
|
||
|
++argno;
|
||
|
}
|
||
|
if (argc > 0) {
|
||
|
input_stack::error("too many arguments for command `%1'", name);
|
||
|
return 0;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static void execute_command(const char *name, int argc, argument *argv)
|
||
|
{
|
||
|
for (int i = 0; i < sizeof(command_table)/sizeof(command_table[0]); i++)
|
||
|
if (strcmp(name, command_table[i].name) == 0) {
|
||
|
if (check_args(command_table[i].arg_types, name, argc, argv))
|
||
|
(*command_table[i].func)(argc, argv);
|
||
|
return;
|
||
|
}
|
||
|
input_stack::error("unknown command `%1'", name);
|
||
|
}
|
||
|
|
||
|
static void command_loop()
|
||
|
{
|
||
|
string command;
|
||
|
for (;;) {
|
||
|
command.clear();
|
||
|
int res = get_word(command);
|
||
|
if (res != 1) {
|
||
|
if (res == 0)
|
||
|
continue;
|
||
|
break;
|
||
|
}
|
||
|
int argc = 0;
|
||
|
command += '\0';
|
||
|
while ((res = get_word(command)) == 1) {
|
||
|
argc++;
|
||
|
command += '\0';
|
||
|
}
|
||
|
argument *argv = new argument[argc];
|
||
|
const char *ptr = command.contents();
|
||
|
for (int i = 0; i < argc; i++)
|
||
|
argv[i].s = ptr = strchr(ptr, '\0') + 1;
|
||
|
execute_command(command.contents(), argc, argv);
|
||
|
a_delete argv;
|
||
|
if (res == -1)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void process_commands(const char *file)
|
||
|
{
|
||
|
input_stack::init();
|
||
|
input_stack::push_file(file);
|
||
|
command_loop();
|
||
|
}
|
||
|
|
||
|
void process_commands(string &s, const char *file, int lineno)
|
||
|
{
|
||
|
input_stack::init();
|
||
|
input_stack::push_string(s, file, lineno);
|
||
|
command_loop();
|
||
|
}
|