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