Serenity Operating System
at master 168 lines 6.2 kB view raw
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}