groff: update vendor branch to v1.20.1
[dragonfly.git] / contrib / groff / src / preproc / refer / command.cpp
CommitLineData
92d0a6a6 1// -*- C++ -*-
4d3e9548 2/* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2004, 2006, 2009
92d0a6a6
JR
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
5
6This file is part of groff.
7
8groff is free software; you can redistribute it and/or modify it under
9the terms of the GNU General Public License as published by the Free
4d3e9548
JL
10Software Foundation, either version 3 of the License, or
11(at your option) any later version.
92d0a6a6
JR
12
13groff is distributed in the hope that it will be useful, but WITHOUT ANY
14WARRANTY; without even the implied warranty of MERCHANTABILITY or
15FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16for more details.
17
4d3e9548
JL
18You should have received a copy of the GNU General Public License
19along with this program. If not, see <http://www.gnu.org/licenses/>. */
92d0a6a6
JR
20
21#include "refer.h"
22#include "refid.h"
23#include "search.h"
24#include "command.h"
25
26cset cs_field_name = csalpha;
27
28class input_item {
29 input_item *next;
30 char *filename;
31 int first_lineno;
32 string buffer;
33 const char *ptr;
34 const char *end;
35public:
36 input_item(string &, const char *, int = 1);
37 ~input_item();
38 int get_char();
39 int peek_char();
40 void skip_char();
41 int get_location(const char **, int *);
42
43 friend class input_stack;
44};
45
46input_item::input_item(string &s, const char *fn, int ln)
47: filename(strsave(fn)), first_lineno(ln)
48{
49 buffer.move(s);
50 ptr = buffer.contents();
51 end = ptr + buffer.length();
52}
53
54input_item::~input_item()
55{
56 a_delete filename;
57}
58
59inline int input_item::peek_char()
60{
61 if (ptr >= end)
62 return EOF;
63 else
64 return (unsigned char)*ptr;
65}
66
67inline int input_item::get_char()
68{
69 if (ptr >= end)
70 return EOF;
71 else
72 return (unsigned char)*ptr++;
73}
74
75inline void input_item::skip_char()
76{
77 ptr++;
78}
79
80int input_item::get_location(const char **filenamep, int *linenop)
81{
82 *filenamep = filename;
83 if (ptr == buffer.contents())
84 *linenop = first_lineno;
85 else {
86 int ln = first_lineno;
87 const char *e = ptr - 1;
88 for (const char *p = buffer.contents(); p < e; p++)
89 if (*p == '\n')
90 ln++;
91 *linenop = ln;
92 }
93 return 1;
94}
95
96class input_stack {
97 static input_item *top;
98public:
99 static void init();
100 static int get_char();
101 static int peek_char();
102 static void skip_char() { top->skip_char(); }
103 static void push_file(const char *);
104 static void push_string(string &, const char *, int);
105 static void error(const char *format,
106 const errarg &arg1 = empty_errarg,
107 const errarg &arg2 = empty_errarg,
108 const errarg &arg3 = empty_errarg);
109};
110
111input_item *input_stack::top = 0;
112
113void input_stack::init()
114{
115 while (top) {
116 input_item *tem = top;
117 top = top->next;
118 delete tem;
119 }
120}
121
122int input_stack::get_char()
123{
124 while (top) {
125 int c = top->get_char();
126 if (c >= 0)
127 return c;
128 input_item *tem = top;
129 top = top->next;
130 delete tem;
131 }
132 return -1;
133}
134
135int input_stack::peek_char()
136{
137 while (top) {
138 int c = top->peek_char();
139 if (c >= 0)
140 return c;
141 input_item *tem = top;
142 top = top->next;
143 delete tem;
144 }
145 return -1;
146}
147
148void input_stack::push_file(const char *fn)
149{
150 FILE *fp;
151 if (strcmp(fn, "-") == 0) {
152 fp = stdin;
153 fn = "<standard input>";
154 }
155 else {
156 errno = 0;
157 fp = fopen(fn, "r");
158 if (fp == 0) {
159 error("can't open `%1': %2", fn, strerror(errno));
160 return;
161 }
162 }
163 string buf;
164 int bol = 1;
165 int lineno = 1;
166 for (;;) {
167 int c = getc(fp);
168 if (bol && c == '.') {
169 // replace lines beginning with .R1 or .R2 with a blank line
170 c = getc(fp);
171 if (c == 'R') {
172 c = getc(fp);
173 if (c == '1' || c == '2') {
174 int cc = c;
175 c = getc(fp);
176 if (compatible_flag || c == ' ' || c == '\n' || c == EOF) {
177 while (c != '\n' && c != EOF)
178 c = getc(fp);
179 }
180 else {
181 buf += '.';
182 buf += 'R';
183 buf += cc;
184 }
185 }
186 else {
187 buf += '.';
188 buf += 'R';
189 }
190 }
191 else
192 buf += '.';
193 }
194 if (c == EOF)
195 break;
196 if (invalid_input_char(c))
197 error_with_file_and_line(fn, lineno,
198 "invalid input character code %1", int(c));
199 else {
200 buf += c;
201 if (c == '\n') {
202 bol = 1;
203 lineno++;
204 }
205 else
206 bol = 0;
207 }
208 }
209 if (fp != stdin)
210 fclose(fp);
211 if (buf.length() > 0 && buf[buf.length() - 1] != '\n')
212 buf += '\n';
213 input_item *it = new input_item(buf, fn);
214 it->next = top;
215 top = it;
216}
217
218void input_stack::push_string(string &s, const char *filename, int lineno)
219{
220 input_item *it = new input_item(s, filename, lineno);
221 it->next = top;
222 top = it;
223}
224
225void input_stack::error(const char *format, const errarg &arg1,
226 const errarg &arg2, const errarg &arg3)
227{
228 const char *filename;
229 int lineno;
230 for (input_item *it = top; it; it = it->next)
231 if (it->get_location(&filename, &lineno)) {
232 error_with_file_and_line(filename, lineno, format, arg1, arg2, arg3);
233 return;
234 }
235 ::error(format, arg1, arg2, arg3);
236}
237
238void command_error(const char *format, const errarg &arg1,
239 const errarg &arg2, const errarg &arg3)
240{
241 input_stack::error(format, arg1, arg2, arg3);
242}
243
244// # not recognized in ""
245// \<newline> is recognized in ""
246// # does not conceal newline
247// if missing closing quote, word extends to end of line
248// no special treatment of \ other than before newline
249// \<newline> not recognized after #
250// ; allowed as alternative to newline
251// ; not recognized in ""
252// don't clear word_buffer; just append on
253// return -1 for EOF, 0 for newline, 1 for word
254
255int get_word(string &word_buffer)
256{
257 int c = input_stack::get_char();
258 for (;;) {
259 if (c == '#') {
260 do {
261 c = input_stack::get_char();
262 } while (c != '\n' && c != EOF);
263 break;
264 }
265 if (c == '\\' && input_stack::peek_char() == '\n')
266 input_stack::skip_char();
267 else if (c != ' ' && c != '\t')
268 break;
269 c = input_stack::get_char();
270 }
271 if (c == EOF)
272 return -1;
273 if (c == '\n' || c == ';')
274 return 0;
275 if (c == '"') {
276 for (;;) {
277 c = input_stack::peek_char();
278 if (c == EOF || c == '\n')
279 break;
280 input_stack::skip_char();
281 if (c == '"') {
282 int d = input_stack::peek_char();
283 if (d == '"')
284 input_stack::skip_char();
285 else
286 break;
287 }
288 else if (c == '\\') {
289 int d = input_stack::peek_char();
290 if (d == '\n')
291 input_stack::skip_char();
292 else
293 word_buffer += '\\';
294 }
295 else
296 word_buffer += c;
297 }
298 return 1;
299 }
300 word_buffer += c;
301 for (;;) {
302 c = input_stack::peek_char();
303 if (c == ' ' || c == '\t' || c == '\n' || c == '#' || c == ';')
304 break;
305 input_stack::skip_char();
306 if (c == '\\') {
307 int d = input_stack::peek_char();
308 if (d == '\n')
309 input_stack::skip_char();
310 else
311 word_buffer += '\\';
312 }
313 else
314 word_buffer += c;
315 }
316 return 1;
317}
318
319union argument {
320 const char *s;
321 int n;
322};
323
324// This is for debugging.
325
326static void echo_command(int argc, argument *argv)
327{
328 for (int i = 0; i < argc; i++)
329 fprintf(stderr, "%s\n", argv[i].s);
330}
331
332static void include_command(int argc, argument *argv)
333{
334 assert(argc == 1);
335 input_stack::push_file(argv[0].s);
336}
337
338static void capitalize_command(int argc, argument *argv)
339{
340 if (argc > 0)
341 capitalize_fields = argv[0].s;
342 else
343 capitalize_fields.clear();
344}
345
346static void accumulate_command(int, argument *)
347{
348 accumulate = 1;
349}
350
351static void no_accumulate_command(int, argument *)
352{
353 accumulate = 0;
354}
355
356static void move_punctuation_command(int, argument *)
357{
358 move_punctuation = 1;
359}
360
361static void no_move_punctuation_command(int, argument *)
362{
363 move_punctuation = 0;
364}
365
366static void sort_command(int argc, argument *argv)
367{
368 if (argc == 0)
369 sort_fields = "AD";
370 else
371 sort_fields = argv[0].s;
372 accumulate = 1;
373}
374
375static void no_sort_command(int, argument *)
376{
377 sort_fields.clear();
378}
379
380static void articles_command(int argc, argument *argv)
381{
382 articles.clear();
383 int i;
384 for (i = 0; i < argc; i++) {
385 articles += argv[i].s;
386 articles += '\0';
387 }
388 int len = articles.length();
389 for (i = 0; i < len; i++)
390 articles[i] = cmlower(articles[i]);
391}
392
393static void database_command(int argc, argument *argv)
394{
395 for (int i = 0; i < argc; i++)
396 database_list.add_file(argv[i].s);
397}
398
399static void default_database_command(int, argument *)
400{
401 search_default = 1;
402}
403
404static void no_default_database_command(int, argument *)
405{
406 search_default = 0;
407}
408
409static void bibliography_command(int argc, argument *argv)
410{
4d3e9548 411 have_bibliography = 1;
92d0a6a6
JR
412 const char *saved_filename = current_filename;
413 int saved_lineno = current_lineno;
414 int saved_label_in_text = label_in_text;
415 label_in_text = 0;
416 if (!accumulate)
417 fputs(".]<\n", stdout);
418 for (int i = 0; i < argc; i++)
419 do_bib(argv[i].s);
420 if (accumulate)
421 output_references();
422 else
423 fputs(".]>\n", stdout);
424 current_filename = saved_filename;
425 current_lineno = saved_lineno;
426 label_in_text = saved_label_in_text;
427}
428
429static void annotate_command(int argc, argument *argv)
430{
431 if (argc > 0)
432 annotation_field = argv[0].s[0];
433 else
434 annotation_field = 'X';
435 if (argc == 2)
436 annotation_macro = argv[1].s;
437 else
438 annotation_macro = "AP";
439}
440
441static void no_annotate_command(int, argument *)
442{
443 annotation_macro.clear();
444 annotation_field = -1;
445}
446
447static void reverse_command(int, argument *argv)
448{
449 reverse_fields = argv[0].s;
450}
451
452static void no_reverse_command(int, argument *)
453{
454 reverse_fields.clear();
455}
456
457static void abbreviate_command(int argc, argument *argv)
458{
459 abbreviate_fields = argv[0].s;
460 period_before_initial = argc > 1 ? argv[1].s : ". ";
461 period_before_last_name = argc > 2 ? argv[2].s : ". ";
462 period_before_other = argc > 3 ? argv[3].s : ". ";
463 period_before_hyphen = argc > 4 ? argv[4].s : ".";
464}
465
466static void no_abbreviate_command(int, argument *)
467{
468 abbreviate_fields.clear();
469}
470
471string search_ignore_fields;
472
473static void search_ignore_command(int argc, argument *argv)
474{
475 if (argc > 0)
476 search_ignore_fields = argv[0].s;
477 else
478 search_ignore_fields = "XYZ";
479 search_ignore_fields += '\0';
480 linear_ignore_fields = search_ignore_fields.contents();
481}
482
483static void no_search_ignore_command(int, argument *)
484{
485 linear_ignore_fields = "";
486}
487
488static void search_truncate_command(int argc, argument *argv)
489{
490 if (argc > 0)
491 linear_truncate_len = argv[0].n;
492 else
493 linear_truncate_len = 6;
494}
495
496static void no_search_truncate_command(int, argument *)
497{
498 linear_truncate_len = -1;
499}
500
501static void discard_command(int argc, argument *argv)
502{
503 if (argc == 0)
504 discard_fields = "XYZ";
505 else
506 discard_fields = argv[0].s;
507 accumulate = 1;
508}
509
510static void no_discard_command(int, argument *)
511{
512 discard_fields.clear();
513}
514
515static void label_command(int, argument *argv)
516{
517 set_label_spec(argv[0].s);
518}
519
520static void abbreviate_label_ranges_command(int argc, argument *argv)
521{
522 abbreviate_label_ranges = 1;
523 label_range_indicator = argc > 0 ? argv[0].s : "-";
524}
525
526static void no_abbreviate_label_ranges_command(int, argument *)
527{
528 abbreviate_label_ranges = 0;
529}
530
531static void label_in_reference_command(int, argument *)
532{
533 label_in_reference = 1;
534}
535
536static void no_label_in_reference_command(int, argument *)
537{
538 label_in_reference = 0;
539}
540
541static void label_in_text_command(int, argument *)
542{
543 label_in_text = 1;
544}
545
546static void no_label_in_text_command(int, argument *)
547{
548 label_in_text = 0;
549}
550
551static void sort_adjacent_labels_command(int, argument *)
552{
553 sort_adjacent_labels = 1;
554}
555
556static void no_sort_adjacent_labels_command(int, argument *)
557{
558 sort_adjacent_labels = 0;
559}
560
561static void date_as_label_command(int argc, argument *argv)
562{
563 if (set_date_label_spec(argc > 0 ? argv[0].s : "D%a*"))
564 date_as_label = 1;
565}
566
567static void no_date_as_label_command(int, argument *)
568{
569 date_as_label = 0;
570}
571
572static void short_label_command(int, argument *argv)
573{
574 if (set_short_label_spec(argv[0].s))
575 short_label_flag = 1;
576}
577
578static void no_short_label_command(int, argument *)
579{
580 short_label_flag = 0;
581}
582
583static void compatible_command(int, argument *)
584{
585 compatible_flag = 1;
586}
587
588static void no_compatible_command(int, argument *)
589{
590 compatible_flag = 0;
591}
592
593static void join_authors_command(int argc, argument *argv)
594{
595 join_authors_exactly_two = argv[0].s;
596 join_authors_default = argc > 1 ? argv[1].s : argv[0].s;
597 join_authors_last_two = argc == 3 ? argv[2].s : argv[0].s;
598}
599
600static void bracket_label_command(int, argument *argv)
601{
602 pre_label = argv[0].s;
603 post_label = argv[1].s;
604 sep_label = argv[2].s;
605}
606
607static void separate_label_second_parts_command(int, argument *argv)
608{
609 separate_label_second_parts = argv[0].s;
610}
611
612static void et_al_command(int argc, argument *argv)
613{
614 et_al = argv[0].s;
615 et_al_min_elide = argv[1].n;
616 if (et_al_min_elide < 1)
617 et_al_min_elide = 1;
618 et_al_min_total = argc >= 3 ? argv[2].n : 0;
619}
620
621static void no_et_al_command(int, argument *)
622{
623 et_al.clear();
624 et_al_min_elide = 0;
625}
626
627typedef void (*command_t)(int, argument *);
628
629/* arg_types is a string describing the numbers and types of arguments.
630s means a string, i means an integer, f is a list of fields, F is
631a single field,
632? means that the previous argument is optional, * means that the
633previous argument can occur any number of times. */
634
635struct S {
636 const char *name;
637 command_t func;
638 const char *arg_types;
639} command_table[] = {
640 { "include", include_command, "s" },
641 { "echo", echo_command, "s*" },
642 { "capitalize", capitalize_command, "f?" },
643 { "accumulate", accumulate_command, "" },
644 { "no-accumulate", no_accumulate_command, "" },
645 { "move-punctuation", move_punctuation_command, "" },
646 { "no-move-punctuation", no_move_punctuation_command, "" },
647 { "sort", sort_command, "s?" },
648 { "no-sort", no_sort_command, "" },
649 { "articles", articles_command, "s*" },
650 { "database", database_command, "ss*" },
651 { "default-database", default_database_command, "" },
652 { "no-default-database", no_default_database_command, "" },
653 { "bibliography", bibliography_command, "ss*" },
654 { "annotate", annotate_command, "F?s?" },
655 { "no-annotate", no_annotate_command, "" },
656 { "reverse", reverse_command, "s" },
657 { "no-reverse", no_reverse_command, "" },
658 { "abbreviate", abbreviate_command, "ss?s?s?s?" },
659 { "no-abbreviate", no_abbreviate_command, "" },
660 { "search-ignore", search_ignore_command, "f?" },
661 { "no-search-ignore", no_search_ignore_command, "" },
662 { "search-truncate", search_truncate_command, "i?" },
663 { "no-search-truncate", no_search_truncate_command, "" },
664 { "discard", discard_command, "f?" },
665 { "no-discard", no_discard_command, "" },
666 { "label", label_command, "s" },
667 { "abbreviate-label-ranges", abbreviate_label_ranges_command, "s?" },
668 { "no-abbreviate-label-ranges", no_abbreviate_label_ranges_command, "" },
669 { "label-in-reference", label_in_reference_command, "" },
670 { "no-label-in-reference", no_label_in_reference_command, "" },
671 { "label-in-text", label_in_text_command, "" },
672 { "no-label-in-text", no_label_in_text_command, "" },
673 { "sort-adjacent-labels", sort_adjacent_labels_command, "" },
674 { "no-sort-adjacent-labels", no_sort_adjacent_labels_command, "" },
675 { "date-as-label", date_as_label_command, "s?" },
676 { "no-date-as-label", no_date_as_label_command, "" },
677 { "short-label", short_label_command, "s" },
678 { "no-short-label", no_short_label_command, "" },
679 { "compatible", compatible_command, "" },
680 { "no-compatible", no_compatible_command, "" },
681 { "join-authors", join_authors_command, "sss?" },
682 { "bracket-label", bracket_label_command, "sss" },
683 { "separate-label-second-parts", separate_label_second_parts_command, "s" },
684 { "et-al", et_al_command, "sii?" },
685 { "no-et-al", no_et_al_command, "" },
686};
687
688static int check_args(const char *types, const char *name,
689 int argc, argument *argv)
690{
691 int argno = 0;
692 while (*types) {
693 if (argc == 0) {
694 if (types[1] == '?')
695 break;
696 else if (types[1] == '*') {
697 assert(types[2] == '\0');
698 break;
699 }
700 else {
701 input_stack::error("missing argument for command `%1'", name);
702 return 0;
703 }
704 }
705 switch (*types) {
706 case 's':
707 break;
708 case 'i':
709 {
710 char *ptr;
711 long n = strtol(argv->s, &ptr, 10);
712 if ((n == 0 && ptr == argv->s)
713 || *ptr != '\0') {
714 input_stack::error("argument %1 for command `%2' must be an integer",
715 argno + 1, name);
716 return 0;
717 }
718 argv->n = (int)n;
719 break;
720 }
721 case 'f':
722 {
723 for (const char *ptr = argv->s; *ptr != '\0'; ptr++)
724 if (!cs_field_name(*ptr)) {
725 input_stack::error("argument %1 for command `%2' must be a list of fields",
726 argno + 1, name);
727 return 0;
728 }
729 break;
730 }
731 case 'F':
732 if (argv->s[0] == '\0' || argv->s[1] != '\0'
733 || !cs_field_name(argv->s[0])) {
734 input_stack::error("argument %1 for command `%2' must be a field name",
735 argno + 1, name);
736 return 0;
737 }
738 break;
739 default:
740 assert(0);
741 }
742 if (types[1] == '?')
743 types += 2;
744 else if (types[1] != '*')
745 types += 1;
746 --argc;
747 ++argv;
748 ++argno;
749 }
750 if (argc > 0) {
751 input_stack::error("too many arguments for command `%1'", name);
752 return 0;
753 }
754 return 1;
755}
756
757static void execute_command(const char *name, int argc, argument *argv)
758{
759 for (unsigned int i = 0;
760 i < sizeof(command_table)/sizeof(command_table[0]); i++)
761 if (strcmp(name, command_table[i].name) == 0) {
762 if (check_args(command_table[i].arg_types, name, argc, argv))
763 (*command_table[i].func)(argc, argv);
764 return;
765 }
766 input_stack::error("unknown command `%1'", name);
767}
768
769static void command_loop()
770{
771 string command;
772 for (;;) {
773 command.clear();
774 int res = get_word(command);
775 if (res != 1) {
776 if (res == 0)
777 continue;
778 break;
779 }
780 int argc = 0;
781 command += '\0';
782 while ((res = get_word(command)) == 1) {
783 argc++;
784 command += '\0';
785 }
786 argument *argv = new argument[argc];
787 const char *ptr = command.contents();
788 for (int i = 0; i < argc; i++)
789 argv[i].s = ptr = strchr(ptr, '\0') + 1;
790 execute_command(command.contents(), argc, argv);
791 a_delete argv;
792 if (res == -1)
793 break;
794 }
795}
796
797void process_commands(const char *file)
798{
799 input_stack::init();
800 input_stack::push_file(file);
801 command_loop();
802}
803
804void process_commands(string &s, const char *file, int lineno)
805{
806 input_stack::init();
807 input_stack::push_string(s, file, lineno);
808 command_loop();
809}