1/* Parse command line arguments for Bison.
2
3 Copyright (C) 1984, 1986, 1989, 1992, 2000-2015, 2018-2019 Free
4 Software Foundation, Inc.
5
6 This file is part of Bison, the GNU Compiler Compiler.
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>. */
20
21#include <config.h>
22#include "getargs.h"
23
24#include "system.h"
25
26#include <argmatch.h>
27#include <c-strcase.h>
28#include <configmake.h>
29#include <error.h>
30#include <getopt.h>
31#include <progname.h>
32#include <quote.h>
33#include <textstyle.h>
34
35#include "complain.h"
36#include "files.h"
37#include "muscle-tab.h"
38#include "output.h"
39#include "uniqstr.h"
40
41bool defines_flag = false;
42bool graph_flag = false;
43bool xml_flag = false;
44bool no_lines_flag = false;
45bool token_table_flag = false;
46location yacc_loc = EMPTY_LOCATION_INIT;
47bool update_flag = false; /* for -u */
48bool color_debug = false;
49
50bool nondeterministic_parser = false;
51bool glr_parser = false;
52
53int feature_flag = feature_caret;
54int report_flag = report_none;
55int trace_flag = trace_none;
56
57static struct bison_language const valid_languages[] = {
58 /* lang, skeleton, ext, hdr, add_tab */
59 { "c", "c-skel.m4", ".c", ".h", true },
60 { "c++", "c++-skel.m4", ".cc", ".hh", true },
61 { "d", "d-skel.m4", ".d", ".d", false },
62 { "java", "java-skel.m4", ".java", ".java", false },
63 { "", "", "", "", false }
64};
65
66int skeleton_prio = default_prio;
67const char *skeleton = NULL;
68int language_prio = default_prio;
69struct bison_language const *language = &valid_languages[0];
70
71/** Decode an option's key.
72 *
73 * \param opt option being decoded.
74 * \param keys array of valid subarguments.
75 * \param values array of corresponding (int) values.
76 * \param all the all value.
77 * \param flags the flags to update
78 * \param arg the subarguments to decode.
79 * If null, then activate all the flags.
80 * \param no length of the potential "no-" prefix.
81 * Can be 0 or 3. If 3, negate the action of the subargument.
82 *
83 * If VALUE != 0 then KEY sets flags and no-KEY clears them.
84 * If VALUE == 0 then KEY clears all flags from \c all and no-KEY sets all
85 * flags from \c all. Thus no-none = all and no-all = none.
86 */
87static void
88flag_argmatch (const char *opt,
89 const char *const keys[], const int values[],
90 int all, int *flags, char *arg, size_t no)
91{
92 int value = XARGMATCH (opt, arg + no, keys, values);
93
94 /* -rnone == -rno-all, and -rno-none == -rall. */
95 if (!value)
96 {
97 value = all;
98 no = !no;
99 }
100
101 if (no)
102 *flags &= ~value;
103 else
104 *flags |= value;
105}
106
107/** Decode an option's set of keys.
108 *
109 * \param opt option being decoded (e.g., --report).
110 * \param keys array of valid subarguments.
111 * \param values array of corresponding (int) values.
112 * \param all the all value.
113 * \param flags the flags to update
114 * \param args comma separated list of effective subarguments to decode.
115 * If 0, then activate all the flags.
116 */
117static void
118flags_argmatch (const char *opt,
119 const char * const keys[], const int values[],
120 int all, int *flags, char *args)
121{
122 if (args)
123 for (args = strtok (args, ","); args; args = strtok (NULL, ","))
124 {
125 size_t no = STRPREFIX_LIT ("no-", args) ? 3 : 0;
126 flag_argmatch (opt, keys,
127 values, all, flags, args, no);
128 }
129 else
130 *flags |= all;
131}
132
133
134/** Decode a set of sub arguments.
135 *
136 * \param FlagName the flag family to update.
137 * \param Args the effective sub arguments to decode.
138 * \param All the "all" value.
139 *
140 * \arg FlagName_args the list of keys.
141 * \arg FlagName_types the list of values.
142 * \arg FlagName_flag the flag to update.
143 */
144#define FLAGS_ARGMATCH(FlagName, Args, All) \
145 flags_argmatch ("--" #FlagName, FlagName ## _args, FlagName ## _types, \
146 All, &FlagName ## _flag, Args)
147
148
149/*----------------------.
150| --report's handling. |
151`----------------------*/
152
153static const char * const report_args[] =
154{
155 /* In a series of synonyms, present the most meaningful first, so
156 that argmatch_valid be more readable. */
157 "none",
158 "state", "states",
159 "itemset", "itemsets",
160 "lookahead", "lookaheads", "look-ahead",
161 "solved",
162 "all",
163 0
164};
165
166static const int report_types[] =
167{
168 report_none,
169 report_states, report_states,
170 report_states | report_itemsets, report_states | report_itemsets,
171 report_states | report_lookahead_tokens,
172 report_states | report_lookahead_tokens,
173 report_states | report_lookahead_tokens,
174 report_states | report_solved_conflicts,
175 report_all
176};
177
178ARGMATCH_VERIFY (report_args, report_types);
179
180
181/*---------------------.
182| --trace's handling. |
183`---------------------*/
184
185static const char * const trace_args[] =
186{
187 "none - no traces",
188 "locations - full display of the locations",
189 "scan - grammar scanner traces",
190 "parse - grammar parser traces",
191 "automaton - construction of the automaton",
192 "bitsets - use of bitsets",
193 "closure - input/output of closure",
194 "grammar - reading, reducing the grammar",
195 "resource - memory consumption (where available)",
196 "sets - grammar sets: firsts, nullable etc.",
197 "muscles - m4 definitions passed to the skeleton",
198 "tools - m4 invocation",
199 "m4 - m4 traces",
200 "skeleton - skeleton postprocessing",
201 "time - time consumption",
202 "ielr - IELR conversion",
203 "all - all of the above",
204 0
205};
206
207static const int trace_types[] =
208{
209 trace_none,
210 trace_locations,
211 trace_scan,
212 trace_parse,
213 trace_automaton,
214 trace_bitsets,
215 trace_closure,
216 trace_grammar,
217 trace_resource,
218 trace_sets,
219 trace_muscles,
220 trace_tools,
221 trace_m4,
222 trace_skeleton,
223 trace_time,
224 trace_ielr,
225 trace_all
226};
227
228ARGMATCH_VERIFY (trace_args, trace_types);
229
230
231/*-----------------------.
232| --feature's handling. |
233`-----------------------*/
234
235static const char * const feature_args[] =
236{
237 "none",
238 "caret", "diagnostics-show-caret",
239 "fixit", "diagnostics-parseable-fixits",
240 "syntax-only",
241 "all",
242 0
243};
244
245static const int feature_types[] =
246{
247 feature_none,
248 feature_caret, feature_caret,
249 feature_fixit_parsable, feature_fixit_parsable,
250 feature_syntax_only,
251 feature_all
252};
253
254ARGMATCH_VERIFY (feature_args, feature_types);
255
256/*-------------------------------------------.
257| Display the help message and exit STATUS. |
258`-------------------------------------------*/
259
260static void usage (int) ATTRIBUTE_NORETURN;
261
262static void
263usage (int status)
264{
265 if (status != 0)
266 fprintf (stderr, _("Try '%s --help' for more information.\n"),
267 program_name);
268 else
269 {
270 /* For ../build-aux/cross-options.pl to work, use the format:
271 ^ -S, --long[=ARGS] (whitespace)
272 A --long option is required.
273 Otherwise, add exceptions to ../build-aux/cross-options.pl. */
274
275 printf (_("Usage: %s [OPTION]... FILE\n"), program_name);
276 fputs (_("\
277Generate a deterministic LR or generalized LR (GLR) parser employing\n\
278LALR(1), IELR(1), or canonical LR(1) parser tables. IELR(1) and\n\
279canonical LR(1) support is experimental.\n\
280\n\
281"), stdout);
282
283 fputs (_("\
284Mandatory arguments to long options are mandatory for short options too.\n\
285"), stdout);
286 fputs (_("\
287The same is true for optional arguments.\n\
288"), stdout);
289
290 fputs (_("\
291\n\
292Operation modes:\n\
293 -h, --help display this help and exit\n\
294 -V, --version output version information and exit\n\
295 --print-localedir output directory containing locale-dependent data\n\
296 and exit\n\
297 --print-datadir output directory containing skeletons and XSLT\n\
298 and exit\n\
299 -u, --update apply fixes to the source grammar file and exit\n\
300 -y, --yacc emulate POSIX Yacc\n\
301 -W, --warnings[=CATEGORY] report the warnings falling in CATEGORY\n\
302 -f, --feature[=FEATURES] activate miscellaneous features\n\
303\n\
304"), stdout);
305
306 fputs (_("\
307Parser:\n\
308 -L, --language=LANGUAGE specify the output programming language\n\
309 -S, --skeleton=FILE specify the skeleton to use\n\
310 -t, --debug instrument the parser for tracing\n\
311 same as '-Dparse.trace'\n\
312 --locations enable location support\n\
313 -D, --define=NAME[=VALUE] similar to '%define NAME \"VALUE\"'\n\
314 -F, --force-define=NAME[=VALUE] override '%define NAME \"VALUE\"'\n\
315 -p, --name-prefix=PREFIX prepend PREFIX to the external symbols\n\
316 deprecated by '-Dapi.prefix=PREFIX'\n\
317 -l, --no-lines don't generate '#line' directives\n\
318 -k, --token-table include a table of token names\n\
319"), stdout);
320 putc ('\n', stdout);
321
322 /* Keep -d and --defines separate so that ../build-aux/cross-options.pl
323 * won't assume that -d also takes an argument. */
324 fputs (_("\
325Output:\n\
326 --defines[=FILE] also produce a header file\n\
327 -d likewise but cannot specify FILE (for POSIX Yacc)\n\
328 -r, --report=THINGS also produce details on the automaton\n\
329 --report-file=FILE write report to FILE\n\
330 -v, --verbose same as '--report=state'\n\
331 -b, --file-prefix=PREFIX specify a PREFIX for output files\n\
332 -o, --output=FILE leave output to FILE\n\
333 -g, --graph[=FILE] also output a graph of the automaton\n\
334 -x, --xml[=FILE] also output an XML report of the automaton\n\
335 (the XML schema is experimental)\n\
336"), stdout);
337 putc ('\n', stdout);
338
339 fputs (_("\
340Warning categories include:\n\
341 'conflicts-sr' S/R conflicts (enabled by default)\n\
342 'conflicts-rr' R/R conflicts (enabled by default)\n\
343 'deprecated' obsolete constructs\n\
344 'empty-rule' empty rules without %empty\n\
345 'midrule-values' unset or unused midrule values\n\
346 'precedence' useless precedence and associativity\n\
347 'yacc' incompatibilities with POSIX Yacc\n\
348 'other' all other warnings (enabled by default)\n\
349 'all' all the warnings except 'yacc'\n\
350 'no-CATEGORY' turn off warnings in CATEGORY\n\
351 'none' turn off all the warnings\n\
352 'error[=CATEGORY]' treat warnings as errors\n\
353"), stdout);
354 putc ('\n', stdout);
355
356 fputs (_("\
357THINGS is a list of comma separated words that can include:\n\
358 'state' describe the states\n\
359 'itemset' complete the core item sets with their closure\n\
360 'lookahead' explicitly associate lookahead tokens to items\n\
361 'solved' describe shift/reduce conflicts solving\n\
362 'all' include all the above information\n\
363 'none' disable the report\n\
364"), stdout);
365 putc ('\n', stdout);
366
367 fputs (_("\
368FEATURES is a list of comma separated words that can include:\n\
369 'caret', 'diagnostics-show-caret'\n\
370 show errors with carets\n\
371 'fixit', 'diagnostics-parseable-fixits'\n\
372 show machine-readable fixes\n\
373 'syntax-only'\n\
374 do not generate any file\n\
375 'all'\n\
376 all of the above\n\
377 'none'\n\
378 disable all of the above\n\
379 "), stdout);
380
381 putc ('\n', stdout);
382 printf (_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
383 printf (_("%s home page: <%s>.\n"), PACKAGE_NAME, PACKAGE_URL);
384 fputs (_("General help using GNU software: "
385 "<http://www.gnu.org/gethelp/>.\n"),
386 stdout);
387
388#if (defined __GLIBC__ && __GLIBC__ >= 2) && !defined __UCLIBC__
389 /* Don't output this redundant message for English locales.
390 Note we still output for 'C' so that it gets included in the
391 man page. */
392 const char *lc_messages = setlocale (LC_MESSAGES, NULL);
393 if (lc_messages && !STREQ (lc_messages, "en_"))
394 /* TRANSLATORS: Replace LANG_CODE in this URL with your language
395 code <http://translationproject.org/team/LANG_CODE.html> to
396 form one of the URLs at http://translationproject.org/team/.
397 Otherwise, replace the entire URL with your translation team's
398 email address. */
399 fputs (_("Report translation bugs to "
400 "<http://translationproject.org/team/>.\n"), stdout);
401#endif
402 fputs (_("For complete documentation, run: info bison.\n"), stdout);
403 }
404
405 exit (status);
406}
407
408
409/*------------------------------.
410| Display the version message. |
411`------------------------------*/
412
413static void
414version (void)
415{
416 /* Some efforts were made to ease the translators' task, please
417 continue. */
418 printf (_("bison (GNU Bison) %s"), VERSION);
419 putc ('\n', stdout);
420 fputs (_("Written by Robert Corbett and Richard Stallman.\n"), stdout);
421 putc ('\n', stdout);
422
423 fprintf (stdout,
424 _("Copyright (C) %d Free Software Foundation, Inc.\n"),
425 PACKAGE_COPYRIGHT_YEAR);
426
427 fputs (_("\
428This is free software; see the source for copying conditions. There is NO\n\
429warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
430"),
431 stdout);
432}
433
434
435/*-------------------------------------.
436| --skeleton and --language handling. |
437`--------------------------------------*/
438
439void
440skeleton_arg (char const *arg, int prio, location loc)
441{
442 if (prio < skeleton_prio)
443 {
444 skeleton_prio = prio;
445 skeleton = arg;
446 }
447 else if (prio == skeleton_prio)
448 complain (&loc, complaint,
449 _("multiple skeleton declarations are invalid"));
450}
451
452void
453language_argmatch (char const *arg, int prio, location loc)
454{
455 char const *msg;
456
457 if (prio < language_prio)
458 {
459 for (int i = 0; valid_languages[i].language[0]; ++i)
460 if (c_strcasecmp (arg, valid_languages[i].language) == 0)
461 {
462 language_prio = prio;
463 language = &valid_languages[i];
464 return;
465 }
466 msg = _("%s: invalid language");
467 }
468 else if (language_prio == prio)
469 msg = _("multiple language declarations are invalid");
470 else
471 return;
472
473 complain (&loc, complaint, msg, quotearg_colon (arg));
474}
475
476/*----------------------.
477| Process the options. |
478`----------------------*/
479
480/* Shorts options.
481 Should be computed from long_options. */
482static char const short_options[] =
483 "D:"
484 "F:"
485 "L:"
486 "S:"
487 "T::"
488 "V"
489 "W::"
490 "b:"
491 "d"
492 "f::"
493 "g::"
494 "h"
495 "k"
496 "l"
497 "o:"
498 "p:"
499 "r:"
500 "t"
501 "u" /* --update */
502 "v"
503 "x::"
504 "y"
505 ;
506
507/* Values for long options that do not have single-letter equivalents. */
508enum
509{
510 COLOR_OPTION = CHAR_MAX + 1,
511 LOCATIONS_OPTION,
512 PRINT_DATADIR_OPTION,
513 PRINT_LOCALEDIR_OPTION,
514 REPORT_FILE_OPTION,
515 STYLE_OPTION
516};
517
518static struct option const long_options[] =
519{
520 /* Operation modes. */
521 { "help", no_argument, 0, 'h' },
522 { "version", no_argument, 0, 'V' },
523 { "print-localedir", no_argument, 0, PRINT_LOCALEDIR_OPTION },
524 { "print-datadir", no_argument, 0, PRINT_DATADIR_OPTION },
525 { "update", no_argument, 0, 'u' },
526 { "warnings", optional_argument, 0, 'W' },
527
528 /* Parser. */
529 { "name-prefix", required_argument, 0, 'p' },
530
531 /* Output. */
532 { "file-prefix", required_argument, 0, 'b' },
533 { "output", required_argument, 0, 'o' },
534 { "output-file", required_argument, 0, 'o' },
535 { "graph", optional_argument, 0, 'g' },
536 { "xml", optional_argument, 0, 'x' },
537 { "report", required_argument, 0, 'r' },
538 { "report-file", required_argument, 0, REPORT_FILE_OPTION },
539 { "verbose", no_argument, 0, 'v' },
540
541 /* Hidden. */
542 { "trace", optional_argument, 0, 'T' },
543 { "color", optional_argument, 0, COLOR_OPTION },
544 { "style", optional_argument, 0, STYLE_OPTION },
545
546 /* Output. */
547 { "defines", optional_argument, 0, 'd' },
548 { "feature", optional_argument, 0, 'f' },
549
550 /* Operation modes. */
551 { "fixed-output-files", no_argument, 0, 'y' },
552 { "yacc", no_argument, 0, 'y' },
553
554 /* Parser. */
555 { "debug", no_argument, 0, 't' },
556 { "define", required_argument, 0, 'D' },
557 { "force-define", required_argument, 0, 'F' },
558 { "locations", no_argument, 0, LOCATIONS_OPTION },
559 { "no-lines", no_argument, 0, 'l' },
560 { "skeleton", required_argument, 0, 'S' },
561 { "language", required_argument, 0, 'L' },
562 { "token-table", no_argument, 0, 'k' },
563
564 {0, 0, 0, 0}
565};
566
567/* Under DOS, there is no difference on the case. This can be
568 troublesome when looking for '.tab' etc. */
569#ifdef MSDOS
570# define AS_FILE_NAME(File) (strlwr (File), (File))
571#else
572# define AS_FILE_NAME(File) (File)
573#endif
574
575/* Build a location for the current command line argument. */
576static
577location
578command_line_location (void)
579{
580 location res;
581 /* "<command line>" is used in GCC's messages about -D. */
582 boundary_set (&res.start, uniqstr_new ("<command line>"), optind - 1, -1, -1);
583 res.end = res.start;
584 return res;
585}
586
587
588/* Handle the command line options for color support. Do it early, so
589 that error messages from getargs be also colored as per the user's
590 request. This is consistent with the way GCC and Clang behave. */
591
592static void
593getargs_colors (int argc, char *argv[])
594{
595 for (int i = 1; i < argc; i++)
596 {
597 const char *arg = argv[i];
598 if (STRPREFIX_LIT ("--color=", arg))
599 {
600 const char *color = arg + strlen ("--color=");
601 if (STREQ (color, "debug"))
602 color_debug = true;
603 else
604 handle_color_option (color);
605 }
606 else if (STRPREFIX_LIT ("--style=", arg))
607 {
608 const char *style = arg + strlen ("--style=");
609 handle_style_option (style);
610 }
611 }
612 complain_init_color ();
613}
614
615
616void
617getargs (int argc, char *argv[])
618{
619 getargs_colors (argc, argv);
620
621 int c;
622 while ((c = getopt_long (argc, argv, short_options, long_options, NULL))
623 != -1)
624 switch (c)
625 {
626 /* ASCII Sorting for short options (i.e., upper case then
627 lower case), and then long-only options. */
628
629 case 0:
630 /* Certain long options cause getopt_long to return 0. */
631 break;
632
633 case 'D': /* -DNAME[=(VALUE|"VALUE"|{VALUE})]. */
634 case 'F': /* -FNAME[=(VALUE|"VALUE"|{VALUE})]. */
635 {
636 char *name = optarg;
637 char *value = strchr (optarg, '=');
638 muscle_kind kind = muscle_keyword;
639 if (value)
640 {
641 char *end = value + strlen (value) - 1;
642 *value++ = 0;
643 if (*value == '{' && *end == '}')
644 {
645 kind = muscle_code;
646 ++value;
647 *end = 0;
648 }
649 else if (*value == '"' && *end == '"')
650 {
651 kind = muscle_string;
652 ++value;
653 *end = 0;
654 }
655 }
656 muscle_percent_define_insert (name, command_line_location (),
657 kind, value ? value : "",
658 c == 'D' ? MUSCLE_PERCENT_DEFINE_D
659 : MUSCLE_PERCENT_DEFINE_F);
660 }
661 break;
662
663 case 'L':
664 language_argmatch (optarg, command_line_prio,
665 command_line_location ());
666 break;
667
668 case 'S':
669 skeleton_arg (AS_FILE_NAME (optarg), command_line_prio,
670 command_line_location ());
671 break;
672
673 case 'T':
674 FLAGS_ARGMATCH (trace, optarg, trace_all);
675 break;
676
677 case 'V':
678 version ();
679 exit (EXIT_SUCCESS);
680
681 case 'f':
682 FLAGS_ARGMATCH (feature, optarg, feature_all);
683 break;
684
685 case 'W':
686 warnings_argmatch (optarg);
687 break;
688
689 case 'b':
690 spec_file_prefix = AS_FILE_NAME (optarg);
691 break;
692
693 case 'd':
694 /* Here, the -d and --defines options are differentiated. */
695 defines_flag = true;
696 if (optarg)
697 {
698 free (spec_header_file);
699 spec_header_file = xstrdup (AS_FILE_NAME (optarg));
700 }
701 break;
702
703 case 'g':
704 graph_flag = true;
705 if (optarg)
706 {
707 free (spec_graph_file);
708 spec_graph_file = xstrdup (AS_FILE_NAME (optarg));
709 }
710 break;
711
712 case 'h':
713 usage (EXIT_SUCCESS);
714
715 case 'k':
716 token_table_flag = true;
717 break;
718
719 case 'l':
720 no_lines_flag = true;
721 break;
722
723 case 'o':
724 spec_outfile = AS_FILE_NAME (optarg);
725 break;
726
727 case 'p':
728 spec_name_prefix = optarg;
729 break;
730
731 case 'r':
732 FLAGS_ARGMATCH (report, optarg, report_all);
733 break;
734
735 case 't':
736 muscle_percent_define_insert ("parse.trace",
737 command_line_location (),
738 muscle_keyword, "",
739 MUSCLE_PERCENT_DEFINE_D);
740 break;
741
742 case 'u':
743 update_flag = true;
744 feature_flag |= feature_syntax_only;
745 break;
746
747 case 'v':
748 report_flag |= report_states;
749 break;
750
751 case 'x':
752 xml_flag = true;
753 if (optarg)
754 {
755 free (spec_xml_file);
756 spec_xml_file = xstrdup (AS_FILE_NAME (optarg));
757 }
758 break;
759
760 case 'y':
761 warning_argmatch ("yacc", 0, 0);
762 yacc_loc = command_line_location ();
763 break;
764
765 case COLOR_OPTION:
766 /* Handled in getargs_colors. */
767 break;
768
769 case LOCATIONS_OPTION:
770 muscle_percent_define_ensure ("locations",
771 command_line_location (), true);
772 break;
773
774 case PRINT_LOCALEDIR_OPTION:
775 printf ("%s\n", LOCALEDIR);
776 exit (EXIT_SUCCESS);
777
778 case PRINT_DATADIR_OPTION:
779 printf ("%s\n", pkgdatadir ());
780 exit (EXIT_SUCCESS);
781
782 case REPORT_FILE_OPTION:
783 free (spec_verbose_file);
784 spec_verbose_file = xstrdup (AS_FILE_NAME (optarg));
785 break;
786
787 case STYLE_OPTION:
788 /* Handled in getargs_colors. */
789 break;
790
791 default:
792 usage (EXIT_FAILURE);
793 }
794
795 if (argc - optind != 1)
796 {
797 if (argc - optind < 1)
798 error (0, 0, _("missing operand"));
799 else
800 error (0, 0, _("extra operand %s"), quote (argv[optind + 1]));
801 usage (EXIT_FAILURE);
802 }
803
804 current_file = grammar_file = uniqstr_new (argv[optind]);
805 MUSCLE_INSERT_C_STRING ("file_name", grammar_file);
806}
807
808void
809tr (char *s, char from, char to)
810{
811 for (; *s; s++)
812 if (*s == from)
813 *s = to;
814}
815