/src/serenity/Userland/Libraries/LibShell/Shell.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020-2022, the SerenityOS developers. |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #pragma once |
8 | | |
9 | | #include "Job.h" |
10 | | #include "Parser.h" |
11 | | #include <AK/Array.h> |
12 | | #include <AK/ByteString.h> |
13 | | #include <AK/CircularQueue.h> |
14 | | #include <AK/HashMap.h> |
15 | | #include <AK/IntrusiveList.h> |
16 | | #include <AK/StackInfo.h> |
17 | | #include <AK/StringBuilder.h> |
18 | | #include <AK/StringView.h> |
19 | | #include <AK/Types.h> |
20 | | #include <AK/Vector.h> |
21 | | #include <LibCore/EventReceiver.h> |
22 | | #include <LibCore/Notifier.h> |
23 | | #include <LibLine/Editor.h> |
24 | | #include <LibMain/Main.h> |
25 | | #include <termios.h> |
26 | | |
27 | | #define ENUMERATE_SHELL_BUILTINS() \ |
28 | 0 | __ENUMERATE_SHELL_BUILTIN(alias, InAllModes) \ |
29 | 0 | __ENUMERATE_SHELL_BUILTIN(where, InAllModes) \ |
30 | 0 | __ENUMERATE_SHELL_BUILTIN(cd, InAllModes) \ |
31 | 0 | __ENUMERATE_SHELL_BUILTIN(cdh, InAllModes) \ |
32 | 0 | __ENUMERATE_SHELL_BUILTIN(command, InAllModes) \ |
33 | 0 | __ENUMERATE_SHELL_BUILTIN(pwd, InAllModes) \ |
34 | 0 | __ENUMERATE_SHELL_BUILTIN(type, InAllModes) \ |
35 | 0 | __ENUMERATE_SHELL_BUILTIN(exec, InAllModes) \ |
36 | 0 | __ENUMERATE_SHELL_BUILTIN(eval, OnlyInPOSIXMode) \ |
37 | 0 | __ENUMERATE_SHELL_BUILTIN(exit, InAllModes) \ |
38 | 0 | __ENUMERATE_SHELL_BUILTIN(export, InAllModes) \ |
39 | 0 | __ENUMERATE_SHELL_BUILTIN(glob, InAllModes) \ |
40 | 0 | __ENUMERATE_SHELL_BUILTIN(unalias, InAllModes) \ |
41 | 0 | __ENUMERATE_SHELL_BUILTIN(unset, InAllModes) \ |
42 | 0 | __ENUMERATE_SHELL_BUILTIN(set, InAllModes) \ |
43 | 0 | __ENUMERATE_SHELL_BUILTIN(history, InAllModes) \ |
44 | 0 | __ENUMERATE_SHELL_BUILTIN(umask, InAllModes) \ |
45 | 0 | __ENUMERATE_SHELL_BUILTIN(not, InAllModes) \ |
46 | 0 | __ENUMERATE_SHELL_BUILTIN(dirs, InAllModes) \ |
47 | 0 | __ENUMERATE_SHELL_BUILTIN(pushd, InAllModes) \ |
48 | 0 | __ENUMERATE_SHELL_BUILTIN(popd, InAllModes) \ |
49 | 0 | __ENUMERATE_SHELL_BUILTIN(setopt, InAllModes) \ |
50 | 0 | __ENUMERATE_SHELL_BUILTIN(shift, InAllModes) \ |
51 | 0 | __ENUMERATE_SHELL_BUILTIN(source, InAllModes) \ |
52 | 0 | __ENUMERATE_SHELL_BUILTIN(time, InAllModes) \ |
53 | 0 | __ENUMERATE_SHELL_BUILTIN(jobs, InAllModes) \ |
54 | 0 | __ENUMERATE_SHELL_BUILTIN(disown, InAllModes) \ |
55 | 0 | __ENUMERATE_SHELL_BUILTIN(fg, InAllModes) \ |
56 | 0 | __ENUMERATE_SHELL_BUILTIN(bg, InAllModes) \ |
57 | 0 | __ENUMERATE_SHELL_BUILTIN(wait, InAllModes) \ |
58 | 0 | __ENUMERATE_SHELL_BUILTIN(dump, InAllModes) \ |
59 | 0 | __ENUMERATE_SHELL_BUILTIN(kill, InAllModes) \ |
60 | 0 | __ENUMERATE_SHELL_BUILTIN(reset, InAllModes) \ |
61 | 0 | __ENUMERATE_SHELL_BUILTIN(noop, InAllModes) \ |
62 | 0 | __ENUMERATE_SHELL_BUILTIN(break, OnlyInPOSIXMode) \ |
63 | 0 | __ENUMERATE_SHELL_BUILTIN(continue, OnlyInPOSIXMode) \ |
64 | 0 | __ENUMERATE_SHELL_BUILTIN(return, InAllModes) \ |
65 | 0 | __ENUMERATE_SHELL_BUILTIN(read, OnlyInPOSIXMode) \ |
66 | 0 | __ENUMERATE_SHELL_BUILTIN(run_with_env, OnlyInPOSIXMode) \ |
67 | 0 | __ENUMERATE_SHELL_BUILTIN(argsparser_parse, InAllModes) \ |
68 | 0 | __ENUMERATE_SHELL_BUILTIN(in_parallel, InAllModes) \ |
69 | 0 | __ENUMERATE_SHELL_BUILTIN(shell_set_active_prompt, InAllModes) |
70 | | |
71 | | #define ENUMERATE_SHELL_OPTIONS() \ |
72 | 0 | __ENUMERATE_SHELL_OPTION(inline_exec_keep_empty_segments, false, "Keep empty segments in inline execute $(...)") \ |
73 | 0 | __ENUMERATE_SHELL_OPTION(verbose, false, "Announce every command that is about to be executed") \ |
74 | 0 | __ENUMERATE_SHELL_OPTION(invoke_program_for_autocomplete, false, "Attempt to use the program being completed itself for autocompletion via --complete") |
75 | | |
76 | | #define ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS() \ |
77 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(concat_lists) \ |
78 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(length) \ |
79 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(length_across) \ |
80 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(remove_suffix) \ |
81 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(remove_prefix) \ |
82 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(regex_replace) \ |
83 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(filter_glob) \ |
84 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(split) \ |
85 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(join) \ |
86 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(value_or_default) \ |
87 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(assign_default) \ |
88 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(error_if_empty) \ |
89 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(null_or_alternative) \ |
90 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(defined_value_or_default) \ |
91 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(assign_defined_default) \ |
92 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(error_if_unset) \ |
93 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(null_if_unset_or_alternative) \ |
94 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(length_of_variable) \ |
95 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(reexpand) \ |
96 | 0 | __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(math) |
97 | | |
98 | | namespace Shell { |
99 | | |
100 | | class Shell; |
101 | | |
102 | | enum class POSIXModeRequirement { |
103 | | OnlyInPOSIXMode, |
104 | | InAllModes, |
105 | | }; |
106 | | |
107 | | class Shell : public Core::EventReceiver { |
108 | | C_OBJECT(Shell); |
109 | | |
110 | | public: |
111 | | constexpr static auto local_init_file_path = "~/.shellrc"; |
112 | | constexpr static auto global_init_file_path = "/etc/shellrc"; |
113 | | constexpr static auto local_posix_init_file_path = "~/.posixshrc"; |
114 | | constexpr static auto global_posix_init_file_path = "/etc/posixshrc"; |
115 | | |
116 | 0 | bool should_format_live() const { return m_should_format_live; } |
117 | 0 | void set_live_formatting(bool value) { m_should_format_live = value; } |
118 | | |
119 | | void setup_signals(); |
120 | | void setup_keybinds(); |
121 | | |
122 | | struct SourcePosition { |
123 | | Optional<ByteString> source_file; |
124 | | ByteString literal_source_text; |
125 | | Optional<AST::Position> position; |
126 | | }; |
127 | | |
128 | | struct RunnablePath { |
129 | | enum class Kind { |
130 | | Builtin, |
131 | | Function, |
132 | | Alias, |
133 | | Executable, |
134 | | }; |
135 | | |
136 | | Kind kind; |
137 | | ByteString path; |
138 | | |
139 | | bool operator<(RunnablePath const& other) const |
140 | 0 | { |
141 | 0 | return path < other.path; |
142 | 0 | } |
143 | | |
144 | 0 | bool operator==(RunnablePath const&) const = default; |
145 | | }; |
146 | | |
147 | | struct RunnablePathComparator { |
148 | | int operator()(RunnablePath const& lhs, RunnablePath const& rhs) |
149 | 0 | { |
150 | 0 | if (lhs.path > rhs.path) |
151 | 0 | return 1; |
152 | | |
153 | 0 | if (lhs.path < rhs.path) |
154 | 0 | return -1; |
155 | | |
156 | 0 | return 0; |
157 | 0 | } |
158 | | |
159 | | int operator()(StringView lhs, RunnablePath const& rhs) |
160 | 0 | { |
161 | 0 | if (lhs > rhs.path) |
162 | 0 | return 1; |
163 | | |
164 | 0 | if (lhs < rhs.path) |
165 | 0 | return -1; |
166 | | |
167 | 0 | return 0; |
168 | 0 | } |
169 | | }; |
170 | | |
171 | | int run_command(StringView, Optional<SourcePosition> = {}); |
172 | | Optional<RunnablePath> runnable_path_for(StringView); |
173 | | Optional<ByteString> help_path_for(Vector<RunnablePath> visited, RunnablePath const& runnable_path); |
174 | | ErrorOr<RefPtr<Job>> run_command(const AST::Command&); |
175 | | Vector<NonnullRefPtr<Job>> run_commands(Vector<AST::Command>&); |
176 | | bool run_file(ByteString const&, bool explicitly_invoked = true); |
177 | | ErrorOr<bool> run_builtin(const AST::Command&, Vector<NonnullRefPtr<AST::Rewiring>> const&, int& retval); |
178 | | bool has_builtin(StringView) const; |
179 | | ErrorOr<RefPtr<AST::Node>> run_immediate_function(StringView name, AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const&); |
180 | | static bool has_immediate_function(StringView); |
181 | | void block_on_job(RefPtr<Job>); |
182 | | void block_on_pipeline(RefPtr<AST::Pipeline>); |
183 | | ByteString prompt() const; |
184 | | |
185 | | static ByteString expand_tilde(StringView expression); |
186 | | static ErrorOr<Vector<ByteString>> expand_globs(StringView path, StringView base); |
187 | | static Vector<ByteString> expand_globs(Vector<StringView> path_segments, StringView base); |
188 | | ErrorOr<Vector<AST::Command>> expand_aliases(Vector<AST::Command>); |
189 | | ByteString resolve_path(ByteString) const; |
190 | | Optional<ByteString> resolve_alias(StringView) const; |
191 | | |
192 | | static bool has_history_event(StringView); |
193 | | |
194 | | ErrorOr<RefPtr<AST::Value const>> get_argument(size_t) const; |
195 | | ErrorOr<RefPtr<AST::Value const>> look_up_local_variable(StringView) const; |
196 | | ErrorOr<ByteString> local_variable_or(StringView, ByteString const&) const; |
197 | | void set_local_variable(ByteString const&, RefPtr<AST::Value>, bool only_in_current_frame = false); |
198 | | void unset_local_variable(StringView, bool only_in_current_frame = false); |
199 | | |
200 | | void define_function(ByteString name, Vector<ByteString> argnames, RefPtr<AST::Node> body); |
201 | | bool has_function(StringView); |
202 | | bool invoke_function(const AST::Command&, int& retval); |
203 | | |
204 | | ByteString format(StringView, ssize_t& cursor) const; |
205 | | |
206 | 0 | RefPtr<Line::Editor> editor() const { return m_editor; } |
207 | | |
208 | | enum class LocalFrameKind { |
209 | | FunctionOrGlobal, |
210 | | Block, |
211 | | }; |
212 | | struct LocalFrame { |
213 | | LocalFrame(ByteString name, HashMap<ByteString, RefPtr<AST::Value>> variables, LocalFrameKind kind = LocalFrameKind::Block) |
214 | 0 | : name(move(name)) |
215 | 0 | , local_variables(move(variables)) |
216 | 0 | , is_function_frame(kind == LocalFrameKind::FunctionOrGlobal) |
217 | 0 | { |
218 | 0 | } |
219 | | |
220 | | ByteString name; |
221 | | HashMap<ByteString, RefPtr<AST::Value>> local_variables; |
222 | | bool is_function_frame; |
223 | | }; |
224 | | |
225 | | struct Frame { |
226 | | Frame(Vector<NonnullOwnPtr<LocalFrame>>& frames, LocalFrame const& frame) |
227 | 0 | : frames(frames) |
228 | 0 | , frame(frame) |
229 | 0 | { |
230 | 0 | } |
231 | | ~Frame(); |
232 | | |
233 | 0 | void leak_frame() { should_destroy_frame = false; } |
234 | | |
235 | | private: |
236 | | Vector<NonnullOwnPtr<LocalFrame>>& frames; |
237 | | LocalFrame const& frame; |
238 | | bool should_destroy_frame { true }; |
239 | | }; |
240 | | |
241 | | [[nodiscard]] Frame push_frame(ByteString name, LocalFrameKind = LocalFrameKind::Block); |
242 | | void pop_frame(); |
243 | | |
244 | | struct Promise { |
245 | | struct Data { |
246 | | struct Unveil { |
247 | | ByteString path; |
248 | | ByteString access; |
249 | | }; |
250 | | ByteString exec_promises; |
251 | | Vector<Unveil> unveils; |
252 | | } data; |
253 | | |
254 | | IntrusiveListNode<Promise> node; |
255 | | using List = IntrusiveList<&Promise::node>; |
256 | | }; |
257 | | |
258 | | struct ScopedPromise { |
259 | | ScopedPromise(Promise::List& promises, Promise&& promise) |
260 | 0 | : promises(promises) |
261 | 0 | , promise(move(promise)) |
262 | 0 | { |
263 | 0 | promises.append(this->promise); |
264 | 0 | } |
265 | | |
266 | | ~ScopedPromise() |
267 | 0 | { |
268 | 0 | promises.remove(promise); |
269 | 0 | } |
270 | | |
271 | | Promise::List& promises; |
272 | | Promise promise; |
273 | | }; |
274 | | [[nodiscard]] ScopedPromise promise(Promise::Data data) |
275 | 0 | { |
276 | 0 | return { m_active_promises, { move(data), {} } }; |
277 | 0 | } |
278 | | |
279 | | enum class EscapeMode { |
280 | | Bareword, |
281 | | SingleQuotedString, |
282 | | DoubleQuotedString, |
283 | | }; |
284 | | static ByteString escape_token_for_double_quotes(StringView token); |
285 | | static ByteString escape_token_for_single_quotes(StringView token); |
286 | | static ByteString escape_token(StringView token, EscapeMode = EscapeMode::Bareword); |
287 | | static ByteString escape_token(Utf32View token, EscapeMode = EscapeMode::Bareword); |
288 | | static ByteString unescape_token(StringView token); |
289 | | enum class SpecialCharacterEscapeMode { |
290 | | Untouched, |
291 | | Escaped, |
292 | | QuotedAsEscape, |
293 | | QuotedAsHex, |
294 | | }; |
295 | | static SpecialCharacterEscapeMode special_character_escape_mode(u32 c, EscapeMode); |
296 | | |
297 | | static bool is_glob(StringView); |
298 | | |
299 | | enum class ExecutableOnly { |
300 | | Yes, |
301 | | No |
302 | | }; |
303 | | |
304 | | ErrorOr<void> highlight(Line::Editor&) const; |
305 | | Vector<Line::CompletionSuggestion> complete(); |
306 | | Vector<Line::CompletionSuggestion> complete(StringView); |
307 | | Vector<Line::CompletionSuggestion> complete_program_name(StringView, size_t offset, EscapeMode = EscapeMode::Bareword); |
308 | | Vector<Line::CompletionSuggestion> complete_variable(StringView, size_t offset); |
309 | | Vector<Line::CompletionSuggestion> complete_user(StringView, size_t offset); |
310 | | Vector<Line::CompletionSuggestion> complete_immediate_function_name(StringView, size_t offset); |
311 | | |
312 | | Vector<Line::CompletionSuggestion> complete_path(StringView base, StringView, size_t offset, ExecutableOnly executable_only, AST::Node const* command_node, AST::Node const*, EscapeMode = EscapeMode::Bareword); |
313 | | Vector<Line::CompletionSuggestion> complete_option(StringView, StringView, size_t offset, AST::Node const* command_node, AST::Node const*); |
314 | | ErrorOr<Vector<Line::CompletionSuggestion>> complete_via_program_itself(size_t offset, AST::Node const* command_node, AST::Node const*, EscapeMode escape_mode, StringView known_program_name); |
315 | | |
316 | | void restore_ios(); |
317 | | |
318 | | u64 find_last_job_id() const; |
319 | | Job* find_job(u64 id, bool is_pid = false); |
320 | 0 | Job* current_job() const { return m_current_job; } |
321 | | void kill_job(Job const*, int sig); |
322 | | |
323 | | ByteString get_history_path(); |
324 | | void print_path(StringView path); |
325 | | void cache_path(); |
326 | | |
327 | | bool read_single_line(); |
328 | | |
329 | | void notify_child_event(); |
330 | | |
331 | 0 | bool posix_mode() const { return m_in_posix_mode; } |
332 | | |
333 | | Optional<struct termios> termios; |
334 | | Optional<struct termios> default_termios; |
335 | | bool was_interrupted { false }; |
336 | | bool was_resized { false }; |
337 | | |
338 | | ByteString cwd; |
339 | | ByteString username; |
340 | | ByteString home; |
341 | | |
342 | | constexpr static auto TTYNameSize = 32; |
343 | | constexpr static auto HostNameSize = 64; |
344 | | |
345 | | char ttyname[TTYNameSize]; |
346 | | char hostname[HostNameSize]; |
347 | | |
348 | | uid_t uid; |
349 | | Optional<int> last_return_code; |
350 | | Vector<ByteString> directory_stack; |
351 | | CircularQueue<ByteString, 8> cd_history; // FIXME: have a configurable cd history length |
352 | | HashMap<u64, NonnullRefPtr<Job>> jobs; |
353 | | Vector<RunnablePath, 256> cached_path; |
354 | | |
355 | | ByteString current_script; |
356 | | |
357 | | enum ShellEventType { |
358 | | ReadLine, |
359 | | }; |
360 | | |
361 | | enum class ShellError { |
362 | | None, |
363 | | InternalControlFlowBreak, |
364 | | InternalControlFlowContinue, |
365 | | InternalControlFlowReturn, |
366 | | InternalControlFlowInterrupted, |
367 | | InternalControlFlowKilled, |
368 | | EvaluatedSyntaxError, |
369 | | NonExhaustiveMatchRules, |
370 | | InvalidGlobError, |
371 | | InvalidSliceContentsError, |
372 | | OpenFailure, |
373 | | OutOfMemory, |
374 | | LaunchError, |
375 | | PipeFailure, |
376 | | WriteFailure, |
377 | | }; |
378 | | |
379 | | void raise_error(ShellError kind, ByteString description, Optional<AST::Position> position = {}) |
380 | 0 | { |
381 | 0 | m_error = kind; |
382 | 0 | m_error_description = move(description); |
383 | 0 | if (m_source_position.has_value() && position.has_value()) |
384 | 0 | m_source_position.value().position = position.release_value(); |
385 | 0 | } |
386 | 0 | bool has_error(ShellError err) const { return m_error == err; } |
387 | 0 | bool has_any_error() const { return !has_error(ShellError::None); } |
388 | 0 | ByteString const& error_description() const { return m_error_description; } |
389 | | ShellError take_error() |
390 | 0 | { |
391 | 0 | auto err = m_error; |
392 | 0 | m_error = ShellError::None; |
393 | 0 | m_error_description = {}; |
394 | 0 | return err; |
395 | 0 | } |
396 | | void possibly_print_error() const; |
397 | | static bool is_control_flow(ShellError error) |
398 | 0 | { |
399 | 0 | switch (error) { |
400 | 0 | case ShellError::InternalControlFlowBreak: |
401 | 0 | case ShellError::InternalControlFlowContinue: |
402 | 0 | case ShellError::InternalControlFlowReturn: |
403 | 0 | case ShellError::InternalControlFlowInterrupted: |
404 | 0 | case ShellError::InternalControlFlowKilled: |
405 | 0 | return true; |
406 | 0 | default: |
407 | 0 | return false; |
408 | 0 | } |
409 | 0 | } |
410 | | |
411 | | #define __ENUMERATE_SHELL_OPTION(name, default_, description) \ |
412 | | bool name { default_ }; |
413 | | |
414 | | struct Options { |
415 | | ENUMERATE_SHELL_OPTIONS(); |
416 | | } options; |
417 | | |
418 | | #undef __ENUMERATE_SHELL_OPTION |
419 | | |
420 | | private: |
421 | | Shell(Line::Editor&, bool attempt_interactive, bool posix_mode = false); |
422 | | Shell(); |
423 | | virtual ~Shell() override; |
424 | | |
425 | | void destroy(); |
426 | | void initialize(bool attempt_interactive); |
427 | | |
428 | | RefPtr<AST::Node> parse(StringView, bool interactive = false, bool as_command = true) const; |
429 | | |
430 | | void timer_event(Core::TimerEvent&) override; |
431 | | |
432 | | void set_user_prompt(); |
433 | | |
434 | | bool is_allowed_to_modify_termios(const AST::Command&) const; |
435 | | |
436 | | void bring_cursor_to_beginning_of_a_line() const; |
437 | | |
438 | | Optional<int> resolve_job_spec(StringView); |
439 | | void add_entry_to_cache(RunnablePath const&); |
440 | | void remove_entry_from_cache(StringView); |
441 | | void stop_all_jobs(); |
442 | | Job* m_current_job { nullptr }; |
443 | | LocalFrame* find_frame_containing_local_variable(StringView name); |
444 | | LocalFrame const* find_frame_containing_local_variable(StringView name) const |
445 | 0 | { |
446 | 0 | return const_cast<Shell*>(this)->find_frame_containing_local_variable(name); |
447 | 0 | } |
448 | | |
449 | | void run_tail(RefPtr<Job>); |
450 | | void run_tail(const AST::Command&, const AST::NodeWithAction&, int head_exit_code); |
451 | | |
452 | | [[noreturn]] void execute_process(Vector<char const*>&& argv); |
453 | | ErrorOr<void> execute_process(Span<StringView> argv); |
454 | | |
455 | | virtual void custom_event(Core::CustomEvent&) override; |
456 | | |
457 | | #define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(name) \ |
458 | | ErrorOr<RefPtr<AST::Node>> immediate_##name(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const&); |
459 | | |
460 | | ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS(); |
461 | | |
462 | | #undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION |
463 | | |
464 | | ErrorOr<RefPtr<AST::Node>> immediate_length_impl(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const&, bool across); |
465 | | |
466 | | #define __ENUMERATE_SHELL_BUILTIN(builtin, _mode) \ |
467 | | ErrorOr<int> builtin_##builtin(Main::Arguments); |
468 | | |
469 | | ENUMERATE_SHELL_BUILTINS(); |
470 | | |
471 | | #undef __ENUMERATE_SHELL_BUILTIN |
472 | | |
473 | | static constexpr Array builtin_names = { |
474 | | #define __ENUMERATE_SHELL_BUILTIN(builtin, _mode) #builtin##sv, |
475 | | |
476 | | ENUMERATE_SHELL_BUILTINS() |
477 | | |
478 | | #undef __ENUMERATE_SHELL_BUILTIN |
479 | | |
480 | | "."sv, // Needs to be aliased to "source" in POSIX mode. |
481 | | // clang-format off |
482 | | // Clang-format does not properly indent this, it gives it 4 spaces too few. |
483 | | ":"sv, // POSIX-y name for "noop". |
484 | | // clang-format on |
485 | | }; |
486 | | |
487 | | struct ShellFunction { |
488 | | ByteString name; |
489 | | Vector<ByteString> arguments; |
490 | | RefPtr<AST::Node> body; |
491 | | }; |
492 | | |
493 | | ErrorOr<String> serialize_function_definition(ShellFunction const&) const; |
494 | | |
495 | | bool m_should_ignore_jobs_on_next_exit { false }; |
496 | | pid_t m_pid { 0 }; |
497 | | |
498 | | HashMap<ByteString, ShellFunction> m_functions; |
499 | | Vector<NonnullOwnPtr<LocalFrame>> m_local_frames; |
500 | | Promise::List m_active_promises; |
501 | | Vector<NonnullRefPtr<AST::Redirection>> m_global_redirections; |
502 | | |
503 | | HashMap<ByteString, ByteString> m_aliases; |
504 | | bool m_is_interactive { true }; |
505 | | bool m_is_subshell { false }; |
506 | | bool m_should_reinstall_signal_handlers { true }; |
507 | | bool m_in_posix_mode { false }; |
508 | | |
509 | | ShellError m_error { ShellError::None }; |
510 | | ByteString m_error_description; |
511 | | Optional<SourcePosition> m_source_position; |
512 | | |
513 | | bool m_should_format_live { false }; |
514 | | |
515 | | RefPtr<Line::Editor> m_editor; |
516 | | |
517 | | bool m_default_constructed { false }; |
518 | | |
519 | | mutable bool m_last_continuation_state { false }; // false == not needed. |
520 | | |
521 | | Optional<size_t> m_history_autosave_time; |
522 | | |
523 | | StackInfo m_completion_stack_info; |
524 | | |
525 | | RefPtr<AST::Node> m_prompt_command_node; |
526 | | mutable Optional<ByteString> m_next_scheduled_prompt_text; |
527 | | }; |
528 | | |
529 | | [[maybe_unused]] static constexpr bool is_word_character(char c) |
530 | 23.9M | { |
531 | 23.9M | return c == '_' || (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || (c <= '9' && c >= '0'); |
532 | 23.9M | } Unexecuted instantiation: FuzzShell.cpp:Shell::is_word_character(char) Parser.cpp:Shell::is_word_character(char) Line | Count | Source | 530 | 23.9M | { | 531 | 23.9M | return c == '_' || (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || (c <= '9' && c >= '0'); | 532 | 23.9M | } |
Unexecuted instantiation: AST.cpp:Shell::is_word_character(char) Unexecuted instantiation: ImmediateFunctions.cpp:Shell::is_word_character(char) Unexecuted instantiation: Shell.cpp:Shell::is_word_character(char) Unexecuted instantiation: Builtin.cpp:Shell::is_word_character(char) Unexecuted instantiation: Job.cpp:Shell::is_word_character(char) Unexecuted instantiation: URLHighlight.cpp:Shell::is_word_character(char) Unexecuted instantiation: FuzzShellPosix.cpp:Shell::is_word_character(char) |
533 | | |
534 | | inline size_t find_offset_into_node(StringView unescaped_text, size_t escaped_offset, Shell::EscapeMode escape_mode) |
535 | 0 | { |
536 | 0 | size_t unescaped_offset = 0; |
537 | 0 | size_t offset = 0; |
538 | 0 | auto do_find_offset = [&](auto& unescaped_text) { |
539 | 0 | for (auto c : unescaped_text) { |
540 | 0 | if (offset == escaped_offset) |
541 | 0 | return unescaped_offset; |
542 | | |
543 | 0 | switch (Shell::special_character_escape_mode(c, escape_mode)) { |
544 | 0 | case Shell::SpecialCharacterEscapeMode::Untouched: |
545 | 0 | break; |
546 | 0 | case Shell::SpecialCharacterEscapeMode::Escaped: |
547 | 0 | ++offset; // X -> \X |
548 | 0 | break; |
549 | 0 | case Shell::SpecialCharacterEscapeMode::QuotedAsEscape: |
550 | 0 | switch (escape_mode) { |
551 | 0 | case Shell::EscapeMode::Bareword: |
552 | 0 | offset += 3; // X -> "\Y" |
553 | 0 | break; |
554 | 0 | case Shell::EscapeMode::SingleQuotedString: |
555 | 0 | offset += 5; // X -> '"\Y"' |
556 | 0 | break; |
557 | 0 | case Shell::EscapeMode::DoubleQuotedString: |
558 | 0 | offset += 1; // X -> \Y |
559 | 0 | break; |
560 | 0 | } |
561 | 0 | break; |
562 | 0 | case Shell::SpecialCharacterEscapeMode::QuotedAsHex: |
563 | 0 | switch (escape_mode) { |
564 | 0 | case Shell::EscapeMode::Bareword: |
565 | 0 | offset += 2; // X -> "\..." |
566 | 0 | break; |
567 | 0 | case Shell::EscapeMode::SingleQuotedString: |
568 | 0 | offset += 4; // X -> '"\..."' |
569 | 0 | break; |
570 | 0 | case Shell::EscapeMode::DoubleQuotedString: |
571 | | // X -> \... |
572 | 0 | break; |
573 | 0 | } |
574 | 0 | if (c > NumericLimits<u8>::max()) |
575 | 0 | offset += 8; // X -> "\uhhhhhhhh" |
576 | 0 | else |
577 | 0 | offset += 3; // X -> "\xhh" |
578 | 0 | break; |
579 | 0 | } |
580 | 0 | ++offset; |
581 | 0 | ++unescaped_offset; |
582 | 0 | } |
583 | 0 | return unescaped_offset; |
584 | 0 | }; Unexecuted instantiation: auto Shell::find_offset_into_node(AK::StringView, unsigned long, Shell::Shell::EscapeMode)::{lambda(auto:1&)#1}::operator()<AK::Utf8View>(AK::Utf8View&) constUnexecuted instantiation: auto Shell::find_offset_into_node(AK::StringView, unsigned long, Shell::Shell::EscapeMode)::{lambda(auto:1&)#1}::operator()<AK::StringView>(AK::StringView&) const |
585 | |
|
586 | 0 | Utf8View view { unescaped_text }; |
587 | 0 | if (view.validate()) |
588 | 0 | return do_find_offset(view); |
589 | 0 | return do_find_offset(unescaped_text); |
590 | 0 | } |
591 | | |
592 | | } |
593 | | |
594 | | namespace AK { |
595 | | |
596 | | template<> |
597 | | struct Traits<Shell::Shell::RunnablePath> : public DefaultTraits<Shell::Shell::RunnablePath> { |
598 | 0 | static constexpr bool is_trivial() { return false; } |
599 | | |
600 | | static bool equals(Shell::Shell::RunnablePath const& self, Shell::Shell::RunnablePath const& other) |
601 | 0 | { |
602 | 0 | return self == other; |
603 | 0 | } |
604 | | |
605 | | static bool equals(Shell::Shell::RunnablePath const& self, StringView other) |
606 | 0 | { |
607 | 0 | return self.path == other; |
608 | 0 | } |
609 | | |
610 | | static bool equals(Shell::Shell::RunnablePath const& self, ByteString const& other) |
611 | 0 | { |
612 | 0 | return self.path == other; |
613 | 0 | } |
614 | | }; |
615 | | |
616 | | } |