Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/DeprecatedString.h>
8#include <AK/GenericLexer.h>
9#include <AK/Optional.h>
10#include <LibCore/ArgsParser.h>
11#include <LibMain/Main.h>
12#include <ctype.h>
13#include <stdio.h>
14
15static bool is_octal(int c)
16{
17 return c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7';
18}
19
20static ErrorOr<void> generate_character_class(Function<int(int)> oracle, StringBuilder& out)
21{
22 for (int i = 0; i < 128; i++) {
23 if (oracle(i))
24 TRY(out.try_append(static_cast<char>(i)));
25 }
26
27 return {};
28}
29
30static ErrorOr<DeprecatedString> build_set(StringView specification)
31{
32 StringBuilder out;
33 GenericLexer lexer(specification);
34
35 while (!lexer.is_eof()) {
36 if (lexer.consume_specific("[:alnum:]"sv))
37 TRY(generate_character_class(isalnum, out));
38 else if (lexer.consume_specific("[:blank:]"sv))
39 TRY(generate_character_class(isblank, out));
40 else if (lexer.consume_specific("[:digit:]"sv))
41 TRY(generate_character_class(isdigit, out));
42 else if (lexer.consume_specific("[:lower:]"sv))
43 TRY(generate_character_class(islower, out));
44 else if (lexer.consume_specific("[:punct:]"sv))
45 TRY(generate_character_class(ispunct, out));
46 else if (lexer.consume_specific("[:upper:]"sv))
47 TRY(generate_character_class(isupper, out));
48 else if (lexer.consume_specific("[:alpha:]"sv))
49 TRY(generate_character_class(isalpha, out));
50 else if (lexer.consume_specific("[:cntrl:]"sv))
51 TRY(generate_character_class(iscntrl, out));
52 else if (lexer.consume_specific("[:graph:]"sv))
53 TRY(generate_character_class(isgraph, out));
54 else if (lexer.consume_specific("[:print:]"sv))
55 TRY(generate_character_class(isprint, out));
56 else if (lexer.consume_specific("[:space:]"sv))
57 TRY(generate_character_class(isspace, out));
58 else if (lexer.consume_specific("[:xdigit:]"sv))
59 TRY(generate_character_class(isxdigit, out));
60 else if (lexer.consume_specific("\\\\"sv))
61 TRY(out.try_append('\\'));
62 else if (lexer.consume_specific("\\a"sv))
63 TRY(out.try_append('\a'));
64 else if (lexer.consume_specific("\\b"sv))
65 TRY(out.try_append('\b'));
66 else if (lexer.consume_specific("\\f"sv))
67 TRY(out.try_append('\f'));
68 else if (lexer.consume_specific("\\n"sv))
69 TRY(out.try_append('\n'));
70 else if (lexer.consume_specific("\\r"sv))
71 TRY(out.try_append('\r'));
72 else if (lexer.consume_specific("\\t"sv))
73 TRY(out.try_append('\t'));
74 else if (lexer.consume_specific("\\v"sv))
75 TRY(out.try_append('\v'));
76 else if (lexer.next_is('\\') && is_octal(lexer.peek(1))) {
77 lexer.consume_specific('\\');
78 int max_left_over = 3;
79 auto octal_digits = lexer.consume_while([&](char i) -> bool {
80 return is_octal(i) && max_left_over--;
81 });
82
83 int value = 0;
84 for (char ch : octal_digits)
85 value = value * 8 + (ch - '0');
86 TRY(out.try_append(static_cast<char>(value)));
87 } else
88 TRY(out.try_append(lexer.consume(1)));
89 }
90
91 return out.to_deprecated_string();
92}
93
94ErrorOr<int> serenity_main(Main::Arguments arguments)
95{
96 bool complement_flag = false;
97 bool delete_flag = false;
98 bool squeeze_flag = false;
99 StringView from_chars;
100 StringView to_chars;
101
102 Core::ArgsParser args_parser;
103 args_parser.add_option(complement_flag, "Take the complement of the first set", "complement", 'c');
104 args_parser.add_option(delete_flag, "Delete characters instead of replacing", "delete", 'd');
105 args_parser.add_option(squeeze_flag, "Omit repeated characters listed in the last given set from the output", "squeeze-repeats", 's');
106 args_parser.add_positional_argument(from_chars, "Set of characters to translate from", "from");
107 args_parser.add_positional_argument(to_chars, "Set of characters to translate to", "to", Core::ArgsParser::Required::No);
108 args_parser.parse(arguments);
109
110 bool transform_flag = !to_chars.is_empty() && !delete_flag;
111
112 if (!transform_flag && !delete_flag && !squeeze_flag) {
113 warnln("tr: Missing operand");
114 args_parser.print_usage(stderr, arguments.strings[0]);
115 return 1;
116 }
117
118 if (delete_flag && squeeze_flag && to_chars.is_empty()) {
119 warnln("tr: Combined delete and squeeze operations need two sets of characters");
120 args_parser.print_usage(stderr, arguments.strings[0]);
121 return 1;
122 }
123
124 if (delete_flag && !squeeze_flag && !to_chars.is_empty()) {
125 warnln("tr: Only one set of characters may be given when deleting without squeezing");
126 args_parser.print_usage(stderr, arguments.strings[0]);
127 return 1;
128 }
129
130 auto from_str = TRY(build_set(from_chars));
131 if (complement_flag) {
132 StringBuilder complement_set;
133 for (int ch = 0; ch < 256; ch++) {
134 if (!from_str.contains(static_cast<char>(ch)))
135 TRY(complement_set.try_append(static_cast<char>(ch)));
136 }
137 from_str = complement_set.to_deprecated_string();
138 }
139
140 auto to_str = TRY(build_set(to_chars));
141 auto squeeze_string = TRY(build_set(!to_chars.is_empty() ? to_chars : from_chars));
142 Optional<char> last_char;
143
144 for (;;) {
145 char ch = fgetc(stdin);
146 if (feof(stdin))
147 break;
148
149 if (delete_flag) {
150 if (from_str.contains(ch))
151 continue;
152 }
153
154 if (transform_flag) {
155 auto match = from_str.find_last(ch);
156 if (match.has_value())
157 ch = to_str[min(match.value(), to_str.length() - 1)];
158 }
159
160 if (squeeze_flag && last_char.has_value() && last_char.value() == ch && squeeze_string.contains(ch))
161 continue;
162
163 last_char = ch;
164 putchar(ch);
165 }
166
167 return 0;
168}