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/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}