Serenity Operating System
at master 128 lines 4.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/CharacterTypes.h> 8#include <AK/GenericLexer.h> 9#include <LibCore/ArgsParser.h> 10#include <LibCore/System.h> 11#include <LibMain/Main.h> 12#include <stdio.h> 13#include <unistd.h> 14 15static u8 parse_octal_number(GenericLexer& lexer) 16{ 17 u32 value = 0; 18 for (size_t count = 0; count < 3; ++count) { 19 auto c = lexer.peek(); 20 if (!(c >= '0' && c <= '7')) 21 break; 22 value = value * 8 + (c - '0'); 23 lexer.consume(); 24 } 25 clamp(value, 0, 255); 26 return value; 27} 28 29static Optional<u8> parse_hex_number(GenericLexer& lexer) 30{ 31 u8 value = 0; 32 for (size_t count = 0; count < 2; ++count) { 33 auto c = lexer.peek(); 34 if (!is_ascii_hex_digit(c)) 35 return {}; 36 value = value * 16 + parse_ascii_hex_digit(c); 37 lexer.consume(); 38 } 39 return value; 40} 41 42static DeprecatedString interpret_backslash_escapes(StringView string, bool& no_trailing_newline) 43{ 44 static constexpr auto escape_map = "a\ab\be\ef\fn\nr\rt\tv\v"sv; 45 static constexpr auto unescaped_chars = "\a\b\e\f\n\r\t\v\\"sv; 46 47 StringBuilder builder; 48 GenericLexer lexer { string }; 49 50 while (!lexer.is_eof()) { 51 auto this_index = lexer.tell(); 52 auto this_char = lexer.consume(); 53 if (this_char == '\\') { 54 if (lexer.is_eof()) { 55 builder.append('\\'); 56 break; 57 } 58 auto next_char = lexer.peek(); 59 if (next_char == 'c') { 60 no_trailing_newline = true; 61 break; 62 } 63 if (next_char == '0') { 64 lexer.consume(); 65 auto octal_number = parse_octal_number(lexer); 66 builder.append(octal_number); 67 } else if (next_char == 'x') { 68 lexer.consume(); 69 auto maybe_hex_number = parse_hex_number(lexer); 70 if (!maybe_hex_number.has_value()) { 71 auto bad_substring = string.substring_view(this_index, lexer.tell() - this_index); 72 builder.append(bad_substring); 73 } else { 74 builder.append(maybe_hex_number.release_value()); 75 } 76 } else if (next_char == 'u') { 77 lexer.retreat(); 78 auto maybe_code_point = lexer.consume_escaped_code_point(); 79 if (maybe_code_point.is_error()) { 80 auto bad_substring = string.substring_view(this_index, lexer.tell() - this_index); 81 builder.append(bad_substring); 82 } else { 83 builder.append_code_point(maybe_code_point.release_value()); 84 } 85 } else { 86 lexer.retreat(); 87 auto consumed_char = lexer.consume_escaped_character('\\', escape_map); 88 if (!unescaped_chars.contains(consumed_char)) 89 builder.append('\\'); 90 builder.append(consumed_char); 91 } 92 } else { 93 builder.append(this_char); 94 } 95 } 96 97 return builder.to_deprecated_string(); 98} 99 100ErrorOr<int> serenity_main(Main::Arguments arguments) 101{ 102 TRY(Core::System::pledge("stdio")); 103 104 Vector<DeprecatedString> text; 105 bool no_trailing_newline = false; 106 bool should_interpret_backslash_escapes = false; 107 108 Core::ArgsParser args_parser; 109 args_parser.add_option(no_trailing_newline, "Do not output a trailing newline", nullptr, 'n'); 110 args_parser.add_option(should_interpret_backslash_escapes, "Interpret backslash escapes", nullptr, 'e'); 111 args_parser.add_positional_argument(text, "Text to print out", "text", Core::ArgsParser::Required::No); 112 args_parser.set_stop_on_first_non_option(true); 113 args_parser.parse(arguments); 114 115 if (text.is_empty()) { 116 if (!no_trailing_newline) 117 outln(); 118 return 0; 119 } 120 121 auto output = DeprecatedString::join(' ', text); 122 if (should_interpret_backslash_escapes) 123 output = interpret_backslash_escapes(output, no_trailing_newline); 124 out("{}", output); 125 if (!no_trailing_newline) 126 outln(); 127 return 0; 128}