Serenity Operating System
1/*
2 * Copyright (c) 2020, the SerenityOS developers.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "AST.h"
8#include "Shell.h"
9#include <AK/MemoryStream.h>
10#include <AK/ScopeGuard.h>
11#include <AK/ScopedValueRollback.h>
12#include <AK/String.h>
13#include <AK/StringBuilder.h>
14#include <AK/URL.h>
15#include <LibCore/DeprecatedFile.h>
16#include <LibCore/EventLoop.h>
17#include <errno.h>
18#include <fcntl.h>
19#include <signal.h>
20#include <unistd.h>
21
22ErrorOr<void> AK::Formatter<Shell::AST::Command>::format(FormatBuilder& builder, Shell::AST::Command const& value)
23{
24 if (m_sign_mode != FormatBuilder::SignMode::Default)
25 VERIFY_NOT_REACHED();
26 if (m_alternative_form)
27 VERIFY_NOT_REACHED();
28 if (m_zero_pad)
29 VERIFY_NOT_REACHED();
30 if (m_mode != Mode::Default && m_mode != Mode::String)
31 VERIFY_NOT_REACHED();
32 if (m_width.has_value())
33 VERIFY_NOT_REACHED();
34 if (m_precision.has_value())
35 VERIFY_NOT_REACHED();
36
37 if (value.argv.is_empty()) {
38 TRY(builder.put_literal("(ShellInternal)"sv));
39 } else {
40 bool first = true;
41 for (auto& arg : value.argv) {
42 if (!first)
43 TRY(builder.put_literal(" "sv));
44 first = false;
45 TRY(builder.put_literal(arg));
46 }
47 }
48
49 for (auto& redir : value.redirections) {
50 TRY(builder.put_padding(' ', 1));
51 if (redir->is_path_redirection()) {
52 auto path_redir = static_cast<Shell::AST::PathRedirection const*>(redir.ptr());
53 TRY(builder.put_i64(path_redir->fd));
54 switch (path_redir->direction) {
55 case Shell::AST::PathRedirection::Read:
56 TRY(builder.put_literal("<"sv));
57 break;
58 case Shell::AST::PathRedirection::Write:
59 TRY(builder.put_literal(">"sv));
60 break;
61 case Shell::AST::PathRedirection::WriteAppend:
62 TRY(builder.put_literal(">>"sv));
63 break;
64 case Shell::AST::PathRedirection::ReadWrite:
65 TRY(builder.put_literal("<>"sv));
66 break;
67 }
68 TRY(builder.put_literal(path_redir->path));
69 } else if (redir->is_fd_redirection()) {
70 auto* fdredir = static_cast<Shell::AST::FdRedirection const*>(redir.ptr());
71 TRY(builder.put_i64(fdredir->new_fd));
72 TRY(builder.put_literal(">"sv));
73 TRY(builder.put_i64(fdredir->old_fd));
74 } else if (redir->is_close_redirection()) {
75 auto close_redir = static_cast<Shell::AST::CloseRedirection const*>(redir.ptr());
76 TRY(builder.put_i64(close_redir->fd));
77 TRY(builder.put_literal(">&-"sv));
78 } else {
79 VERIFY_NOT_REACHED();
80 }
81 }
82
83 if (!value.next_chain.is_empty()) {
84 for (auto& command : value.next_chain) {
85 switch (command.action) {
86 case Shell::AST::NodeWithAction::And:
87 TRY(builder.put_literal(" && "sv));
88 break;
89 case Shell::AST::NodeWithAction::Or:
90 TRY(builder.put_literal(" || "sv));
91 break;
92 case Shell::AST::NodeWithAction::Sequence:
93 TRY(builder.put_literal("; "sv));
94 break;
95 }
96
97 TRY(builder.put_literal("("sv));
98 TRY(builder.put_literal(command.node->class_name()));
99 TRY(builder.put_literal("...)"sv));
100 }
101 }
102 if (!value.should_wait)
103 TRY(builder.put_literal("&"sv));
104 return {};
105}
106
107namespace Shell::AST {
108
109template<typename... Args>
110static inline void print_indented(int indent, CheckedFormatString<Args...> format, Args&&... args)
111{
112 auto str = DeprecatedString::formatted(format.view(), forward<Args>(args)...);
113 dbgln("{: >{}}", str, str.length() + indent * 2);
114}
115
116static inline Optional<Position> merge_positions(Optional<Position> const& left, Optional<Position> const& right)
117{
118 if (!left.has_value())
119 return right;
120
121 if (!right.has_value())
122 return left;
123
124 return Position {
125 .start_offset = left->start_offset,
126 .end_offset = right->end_offset,
127 .start_line = left->start_line,
128 .end_line = right->end_line,
129 };
130}
131
132static inline Vector<Command> join_commands(Vector<Command> left, Vector<Command> right)
133{
134 Command command;
135
136 auto last_in_left = left.take_last();
137 auto first_in_right = right.take_first();
138
139 command.argv.extend(last_in_left.argv);
140 command.argv.extend(first_in_right.argv);
141
142 command.redirections.extend(last_in_left.redirections);
143 command.redirections.extend(first_in_right.redirections);
144
145 command.should_wait = first_in_right.should_wait && last_in_left.should_wait;
146 command.is_pipe_source = first_in_right.is_pipe_source;
147 command.should_notify_if_in_background = first_in_right.should_notify_if_in_background || last_in_left.should_notify_if_in_background;
148
149 command.position = merge_positions(last_in_left.position, first_in_right.position);
150
151 Vector<Command> commands;
152 commands.extend(left);
153 commands.append(command);
154 commands.extend(right);
155
156 return commands;
157}
158
159static ErrorOr<String> resolve_slices(RefPtr<Shell> shell, String&& input_value, Vector<NonnullRefPtr<Slice>> slices)
160{
161 if (slices.is_empty())
162 return move(input_value);
163
164 for (auto& slice : slices) {
165 auto value = TRY(slice->run(shell));
166 if (shell && shell->has_any_error())
167 break;
168
169 if (!value) {
170 shell->raise_error(Shell::ShellError::InvalidSliceContentsError, "Invalid slice contents", slice->position());
171 return move(input_value);
172 }
173
174 auto index_values = value->resolve_as_list(shell).release_value_but_fixme_should_propagate_errors();
175 Vector<size_t> indices;
176 indices.ensure_capacity(index_values.size());
177
178 size_t i = 0;
179 for (auto& value : index_values) {
180 auto maybe_index = value.bytes_as_string_view().to_int();
181 if (!maybe_index.has_value()) {
182 shell->raise_error(Shell::ShellError::InvalidSliceContentsError, DeprecatedString::formatted("Invalid value in slice index {}: {} (expected a number)", i, value), slice->position());
183 return move(input_value);
184 }
185 ++i;
186
187 auto index = maybe_index.value();
188 auto original_index = index;
189 if (index < 0)
190 index += input_value.bytes_as_string_view().length();
191
192 if (index < 0 || (size_t)index >= input_value.bytes_as_string_view().length()) {
193 shell->raise_error(Shell::ShellError::InvalidSliceContentsError, DeprecatedString::formatted("Slice index {} (evaluated as {}) out of value bounds [0-{})", index, original_index, input_value.bytes_as_string_view().length()), slice->position());
194 return move(input_value);
195 }
196 indices.unchecked_append(index);
197 }
198
199 StringBuilder builder { indices.size() };
200 for (auto& index : indices)
201 builder.append(input_value.bytes_as_string_view()[index]);
202
203 input_value = builder.to_string().release_value_but_fixme_should_propagate_errors();
204 }
205
206 return move(input_value);
207}
208
209static ErrorOr<Vector<String>> resolve_slices(RefPtr<Shell> shell, Vector<String>&& values, Vector<NonnullRefPtr<Slice>> slices)
210{
211 if (slices.is_empty())
212 return move(values);
213
214 for (auto& slice : slices) {
215 auto value = TRY(slice->run(shell));
216 if (shell && shell->has_any_error())
217 break;
218
219 if (!value) {
220 shell->raise_error(Shell::ShellError::InvalidSliceContentsError, "Invalid slice contents", slice->position());
221 return move(values);
222 }
223
224 auto index_values = value->resolve_as_list(shell).release_value_but_fixme_should_propagate_errors();
225 Vector<size_t> indices;
226 indices.ensure_capacity(index_values.size());
227
228 size_t i = 0;
229 for (auto& value : index_values) {
230 auto maybe_index = value.bytes_as_string_view().to_int();
231 if (!maybe_index.has_value()) {
232 shell->raise_error(Shell::ShellError::InvalidSliceContentsError, DeprecatedString::formatted("Invalid value in slice index {}: {} (expected a number)", i, value), slice->position());
233 return move(values);
234 }
235 ++i;
236
237 auto index = maybe_index.value();
238 auto original_index = index;
239 if (index < 0)
240 index += values.size();
241
242 if (index < 0 || (size_t)index >= values.size()) {
243 shell->raise_error(Shell::ShellError::InvalidSliceContentsError, DeprecatedString::formatted("Slice index {} (evaluated as {}) out of value bounds [0-{})", index, original_index, values.size()), slice->position());
244 return move(values);
245 }
246 indices.unchecked_append(index);
247 }
248
249 Vector<String> result;
250 result.ensure_capacity(indices.size());
251 for (auto& index : indices)
252 result.unchecked_append(values[index]);
253
254 values = move(result);
255 }
256
257 return move(values);
258}
259
260void Node::clear_syntax_error()
261{
262 m_syntax_error_node->clear_syntax_error();
263}
264
265void Node::set_is_syntax_error(SyntaxError& error_node)
266{
267 if (!m_syntax_error_node) {
268 m_syntax_error_node = error_node;
269 } else {
270 m_syntax_error_node->set_is_syntax_error(error_node);
271 }
272}
273
274bool Node::is_syntax_error() const
275{
276 return m_syntax_error_node && m_syntax_error_node->is_syntax_error();
277}
278
279ErrorOr<void> Node::for_each_entry(RefPtr<Shell> shell, Function<ErrorOr<IterationDecision>(NonnullRefPtr<Value>)> callback)
280{
281 auto value = TRY(TRY(run(shell))->resolve_without_cast(shell));
282 if (shell && shell->has_any_error())
283 return {};
284
285 if (value->is_job()) {
286 TRY(callback(value));
287 return {};
288 }
289
290 if (value->is_list_without_resolution()) {
291 auto list = value->resolve_without_cast(shell).release_value_but_fixme_should_propagate_errors();
292 for (auto& element : static_cast<ListValue*>(list.ptr())->values()) {
293 if (TRY(callback(element)) == IterationDecision::Break)
294 break;
295 }
296 return {};
297 }
298
299 auto list = value->resolve_as_list(shell).release_value_but_fixme_should_propagate_errors();
300 for (auto& element : list) {
301 if (TRY(callback(make_ref_counted<StringValue>(move(element)))) == IterationDecision::Break)
302 break;
303 }
304
305 return {};
306}
307
308ErrorOr<Vector<Command>> Node::to_lazy_evaluated_commands(RefPtr<Shell> shell)
309{
310 if (would_execute()) {
311 // Wrap the node in a "should immediately execute next" command.
312 return Vector {
313 Command { {}, {}, true, false, true, true, {}, { NodeWithAction(*this, NodeWithAction::Sequence) }, position() }
314 };
315 }
316
317 return TRY(TRY(run(shell))->resolve_as_commands(shell));
318}
319
320ErrorOr<void> Node::dump(int level) const
321{
322 print_indented(level,
323 "{} at {}:{} (from {}.{} to {}.{})",
324 class_name(),
325 m_position.start_offset,
326 m_position.end_offset,
327 m_position.start_line.line_number,
328 m_position.start_line.line_column,
329 m_position.end_line.line_number,
330 m_position.end_line.line_column);
331
332 return {};
333}
334
335Node::Node(Position position)
336 : m_position(position)
337{
338}
339
340ErrorOr<Vector<Line::CompletionSuggestion>> Node::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
341{
342 auto matching_node = hit_test_result.matching_node;
343 if (matching_node) {
344 auto kind = matching_node->kind();
345 StringLiteral::EnclosureType enclosure_type = StringLiteral::EnclosureType::None;
346 if (kind == Kind::StringLiteral)
347 enclosure_type = static_cast<StringLiteral const*>(matching_node.ptr())->enclosure_type();
348
349 auto set_results_trivia = [enclosure_type](Vector<Line::CompletionSuggestion>&& suggestions) {
350 if (enclosure_type != StringLiteral::EnclosureType::None) {
351 for (auto& entry : suggestions)
352 entry.trailing_trivia = { static_cast<u32>(enclosure_type == StringLiteral::EnclosureType::SingleQuotes ? '\'' : '"') };
353 }
354 return suggestions;
355 };
356 if (kind == Kind::BarewordLiteral || kind == Kind::StringLiteral) {
357 Shell::EscapeMode escape_mode;
358 StringView text;
359 size_t corrected_offset;
360 if (kind == Kind::BarewordLiteral) {
361 auto* node = static_cast<BarewordLiteral const*>(matching_node.ptr());
362 text = node->text();
363 escape_mode = Shell::EscapeMode::Bareword;
364 corrected_offset = find_offset_into_node(text, offset - matching_node->position().start_offset, escape_mode);
365 } else {
366 auto* node = static_cast<StringLiteral const*>(matching_node.ptr());
367 text = node->text();
368 escape_mode = enclosure_type == StringLiteral::EnclosureType::SingleQuotes ? Shell::EscapeMode::SingleQuotedString : Shell::EscapeMode::DoubleQuotedString;
369 corrected_offset = find_offset_into_node(text, offset - matching_node->position().start_offset + 1, escape_mode);
370 }
371
372 if (corrected_offset > text.length())
373 return Vector<Line::CompletionSuggestion> {};
374
375 // If the literal isn't an option, treat it as a path.
376 if (!(text.starts_with('-') || text == "--" || text == "-"))
377 return set_results_trivia(shell.complete_path(""sv, text, corrected_offset, Shell::ExecutableOnly::No, hit_test_result.closest_command_node.ptr(), hit_test_result.matching_node, escape_mode));
378
379 // If the literal is an option, we have to know the program name
380 // should we have no way to get that, bail early.
381
382 if (!hit_test_result.closest_command_node)
383 return Vector<Line::CompletionSuggestion> {};
384
385 auto program_name_node = hit_test_result.closest_command_node->leftmost_trivial_literal();
386 if (!program_name_node)
387 return Vector<Line::CompletionSuggestion> {};
388
389 String program_name;
390 if (program_name_node->is_bareword())
391 program_name = static_cast<BarewordLiteral const*>(program_name_node.ptr())->text();
392 else
393 program_name = static_cast<StringLiteral const*>(program_name_node.ptr())->text();
394
395 return set_results_trivia(shell.complete_option(program_name, text, corrected_offset, hit_test_result.closest_command_node.ptr(), hit_test_result.matching_node));
396 }
397 return Vector<Line::CompletionSuggestion> {};
398 }
399 auto result = hit_test_position(offset);
400 if (!result.matching_node)
401 return shell.complete_path(""sv, ""sv, 0, Shell::ExecutableOnly::No, result.closest_command_node.ptr(), nullptr, Shell::EscapeMode::Bareword);
402
403 auto node = result.matching_node;
404 if (node->is_bareword() || node != result.closest_node_with_semantic_meaning)
405 node = result.closest_node_with_semantic_meaning;
406
407 if (!node)
408 return Vector<Line::CompletionSuggestion> {};
409
410 return node->complete_for_editor(shell, offset, result);
411}
412
413ErrorOr<Vector<Line::CompletionSuggestion>> Node::complete_for_editor(Shell& shell, size_t offset)
414{
415 return Node::complete_for_editor(shell, offset, { nullptr, nullptr, nullptr });
416}
417
418ErrorOr<void> And::dump(int level) const
419{
420 TRY(Node::dump(level));
421 TRY(m_left->dump(level + 1));
422 TRY(m_right->dump(level + 1));
423 return {};
424}
425
426ErrorOr<RefPtr<Value>> And::run(RefPtr<Shell> shell)
427{
428 auto commands = TRY(m_left->to_lazy_evaluated_commands(shell));
429 if (shell && shell->has_any_error())
430 return make_ref_counted<ListValue>({});
431
432 commands.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::And });
433 return make_ref_counted<CommandSequenceValue>(move(commands));
434}
435
436ErrorOr<void> And::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
437{
438 metadata.is_first_in_list = true;
439 TRY(m_left->highlight_in_editor(editor, shell, metadata));
440 TRY(m_right->highlight_in_editor(editor, shell, metadata));
441 return {};
442}
443
444HitTestResult And::hit_test_position(size_t offset) const
445{
446 auto result = m_left->hit_test_position(offset);
447 if (result.matching_node) {
448 if (!result.closest_command_node)
449 result.closest_command_node = m_right;
450 return result;
451 }
452
453 result = m_right->hit_test_position(offset);
454 if (!result.closest_command_node)
455 result.closest_command_node = m_right;
456 return result;
457}
458
459And::And(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right, Position and_position)
460 : Node(move(position))
461 , m_left(move(left))
462 , m_right(move(right))
463 , m_and_position(and_position)
464{
465 if (m_left->is_syntax_error())
466 set_is_syntax_error(m_left->syntax_error_node());
467 else if (m_right->is_syntax_error())
468 set_is_syntax_error(m_right->syntax_error_node());
469}
470
471ErrorOr<void> ListConcatenate::dump(int level) const
472{
473 TRY(Node::dump(level));
474 for (auto& element : m_list)
475 TRY(element->dump(level + 1));
476
477 return {};
478}
479
480ErrorOr<RefPtr<Value>> ListConcatenate::run(RefPtr<Shell> shell)
481{
482 RefPtr<Value> result = nullptr;
483
484 for (auto& element : m_list) {
485 if (shell && shell->has_any_error())
486 break;
487 if (!result) {
488 result = make_ref_counted<ListValue>({ TRY(TRY(element->run(shell))->resolve_without_cast(shell)) });
489 continue;
490 }
491 auto element_value = TRY(TRY(element->run(shell))->resolve_without_cast(shell));
492 if (shell && shell->has_any_error())
493 break;
494
495 if (result->is_command() || element_value->is_command()) {
496 auto joined_commands = join_commands(
497 result->resolve_as_commands(shell).release_value_but_fixme_should_propagate_errors(),
498 element_value->resolve_as_commands(shell).release_value_but_fixme_should_propagate_errors());
499
500 if (joined_commands.size() == 1) {
501 auto& command = joined_commands[0];
502 command.position = position();
503 result = make_ref_counted<CommandValue>(command);
504 } else {
505 result = make_ref_counted<CommandSequenceValue>(move(joined_commands));
506 }
507 } else {
508 Vector<NonnullRefPtr<Value>> values;
509
510 if (result->is_list_without_resolution()) {
511 values.extend(static_cast<ListValue*>(result.ptr())->values());
512 } else {
513 for (auto& result : TRY(result->resolve_as_list(shell)))
514 values.append(make_ref_counted<StringValue>(result));
515 }
516
517 values.append(element_value);
518
519 result = make_ref_counted<ListValue>(move(values));
520 }
521 }
522 if (!result)
523 return make_ref_counted<ListValue>({});
524
525 return result;
526}
527
528ErrorOr<void> ListConcatenate::for_each_entry(RefPtr<Shell> shell, Function<ErrorOr<IterationDecision>(NonnullRefPtr<Value>)> callback)
529{
530 for (auto& entry : m_list) {
531 auto value = TRY(entry->run(shell));
532 if (shell && shell->has_any_error())
533 break;
534 if (!value)
535 continue;
536 if (TRY(callback(value.release_nonnull())) == IterationDecision::Break)
537 break;
538 }
539
540 return {};
541}
542
543ErrorOr<void> ListConcatenate::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
544{
545 auto first = metadata.is_first_in_list;
546 metadata.is_first_in_list = false;
547
548 metadata.is_first_in_list = first;
549 for (auto& element : m_list) {
550 TRY(element->highlight_in_editor(editor, shell, metadata));
551 metadata.is_first_in_list = false;
552 }
553
554 return {};
555}
556
557HitTestResult ListConcatenate::hit_test_position(size_t offset) const
558{
559 bool first = true;
560 for (auto& element : m_list) {
561 auto result = element->hit_test_position(offset);
562 if (!result.closest_node_with_semantic_meaning && !first)
563 result.closest_node_with_semantic_meaning = this;
564 if (result.matching_node)
565 return result;
566 first = false;
567 }
568
569 return {};
570}
571
572RefPtr<Node const> ListConcatenate::leftmost_trivial_literal() const
573{
574 if (m_list.is_empty())
575 return nullptr;
576
577 return m_list.first()->leftmost_trivial_literal();
578}
579
580ListConcatenate::ListConcatenate(Position position, Vector<NonnullRefPtr<Node>> list)
581 : Node(move(position))
582 , m_list(move(list))
583{
584 for (auto& element : m_list) {
585 if (element->is_syntax_error()) {
586 set_is_syntax_error(element->syntax_error_node());
587 break;
588 }
589 }
590}
591
592ErrorOr<void> Background::dump(int level) const
593{
594 TRY(Node::dump(level));
595 TRY(m_command->dump(level + 1));
596 return {};
597}
598
599ErrorOr<RefPtr<Value>> Background::run(RefPtr<Shell> shell)
600{
601 auto commands = TRY(m_command->to_lazy_evaluated_commands(shell));
602 for (auto& command : commands)
603 command.should_wait = false;
604
605 return make_ref_counted<CommandSequenceValue>(move(commands));
606}
607
608ErrorOr<void> Background::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
609{
610 return m_command->highlight_in_editor(editor, shell, metadata);
611}
612
613HitTestResult Background::hit_test_position(size_t offset) const
614{
615 return m_command->hit_test_position(offset);
616}
617
618Background::Background(Position position, NonnullRefPtr<Node> command)
619 : Node(move(position))
620 , m_command(move(command))
621{
622 if (m_command->is_syntax_error())
623 set_is_syntax_error(m_command->syntax_error_node());
624}
625
626ErrorOr<void> BarewordLiteral::dump(int level) const
627{
628 TRY(Node::dump(level));
629 print_indented(level + 1, "{}", m_text);
630 return {};
631}
632
633ErrorOr<RefPtr<Value>> BarewordLiteral::run(RefPtr<Shell>)
634{
635 return make_ref_counted<StringValue>(m_text);
636}
637
638ErrorOr<void> BarewordLiteral::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
639{
640 if (metadata.is_first_in_list) {
641 auto runnable = shell.runnable_path_for(m_text);
642 if (runnable.has_value()) {
643 Line::Style bold = { Line::Style::Bold };
644 Line::Style style = bold;
645
646#ifdef AK_OS_SERENITY
647 if (runnable->kind == Shell::RunnablePath::Kind::Executable || runnable->kind == Shell::RunnablePath::Kind::Alias) {
648 auto name = shell.help_path_for({}, *runnable);
649 if (name.has_value()) {
650 auto url = URL::create_with_help_scheme(name.release_value(), shell.hostname);
651 style = bold.unified_with(Line::Style::Hyperlink(url.to_deprecated_string()));
652 }
653 }
654#endif
655
656 editor.stylize({ m_position.start_offset, m_position.end_offset }, style);
657 } else {
658 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Red) });
659 }
660
661 return {};
662 }
663
664 if (m_text.starts_with('-')) {
665 if (m_text == "--"sv) {
666 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Green) });
667 return {};
668 }
669 if (m_text == "-"sv)
670 return {};
671
672 if (m_text.starts_with_bytes("--"sv)) {
673 auto index = m_text.find_byte_offset('=').value_or(m_text.bytes_as_string_view().length() - 1) + 1;
674 editor.stylize({ m_position.start_offset, m_position.start_offset + index }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan) });
675 } else {
676 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan) });
677 }
678 }
679 if (Core::DeprecatedFile::exists(m_text)) {
680 auto realpath = shell.resolve_path(m_text.bytes_as_string_view());
681 auto url = URL::create_with_file_scheme(realpath);
682 url.set_host(shell.hostname);
683 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Hyperlink(url.to_deprecated_string()) });
684 }
685 return {};
686}
687
688BarewordLiteral::BarewordLiteral(Position position, String text)
689 : Node(move(position))
690 , m_text(move(text))
691{
692}
693
694ErrorOr<void> BraceExpansion::dump(int level) const
695{
696 TRY(Node::dump(level));
697 for (auto& entry : m_entries)
698 TRY(entry->dump(level + 1));
699 return {};
700}
701
702ErrorOr<RefPtr<Value>> BraceExpansion::run(RefPtr<Shell> shell)
703{
704 Vector<NonnullRefPtr<Value>> values;
705 for (auto& entry : m_entries) {
706 if (shell && shell->has_any_error())
707 break;
708 auto value = TRY(entry->run(shell));
709 if (value)
710 values.append(value.release_nonnull());
711 }
712
713 return make_ref_counted<ListValue>(move(values));
714}
715
716HitTestResult BraceExpansion::hit_test_position(size_t offset) const
717{
718 for (auto& entry : m_entries) {
719 auto result = entry->hit_test_position(offset);
720 if (result.matching_node) {
721 if (!result.closest_command_node)
722 result.closest_command_node = entry;
723 return result;
724 }
725 }
726
727 return {};
728}
729
730ErrorOr<void> BraceExpansion::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
731{
732 for (auto& entry : m_entries) {
733 TRY(entry->highlight_in_editor(editor, shell, metadata));
734 metadata.is_first_in_list = false;
735 }
736
737 return {};
738}
739
740BraceExpansion::BraceExpansion(Position position, Vector<NonnullRefPtr<Node>> entries)
741 : Node(move(position))
742 , m_entries(move(entries))
743{
744 for (auto& entry : m_entries) {
745 if (entry->is_syntax_error()) {
746 set_is_syntax_error(entry->syntax_error_node());
747 break;
748 }
749 }
750}
751
752ErrorOr<void> CastToCommand::dump(int level) const
753{
754 TRY(Node::dump(level));
755 TRY(m_inner->dump(level + 1));
756 return {};
757}
758
759ErrorOr<RefPtr<Value>> CastToCommand::run(RefPtr<Shell> shell)
760{
761 if (m_inner->is_command())
762 return m_inner->run(shell);
763
764 auto value = TRY(TRY(m_inner->run(shell))->resolve_without_cast(shell));
765 if (shell && shell->has_any_error())
766 return make_ref_counted<ListValue>({});
767
768 if (value->is_command())
769 return value;
770
771 auto argv = TRY(value->resolve_as_list(shell));
772 return make_ref_counted<CommandValue>(move(argv), position());
773}
774
775ErrorOr<void> CastToCommand::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
776{
777 return m_inner->highlight_in_editor(editor, shell, metadata);
778}
779
780HitTestResult CastToCommand::hit_test_position(size_t offset) const
781{
782 auto result = m_inner->hit_test_position(offset);
783 if (!result.closest_node_with_semantic_meaning)
784 result.closest_node_with_semantic_meaning = this;
785 if (!result.closest_command_node && position().contains(offset))
786 result.closest_command_node = this;
787 return result;
788}
789
790ErrorOr<Vector<Line::CompletionSuggestion>> CastToCommand::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
791{
792 auto matching_node = hit_test_result.matching_node;
793 if (!matching_node || !matching_node->is_bareword())
794 return Vector<Line::CompletionSuggestion> {};
795
796 auto corrected_offset = offset - matching_node->position().start_offset;
797 auto* node = static_cast<BarewordLiteral const*>(matching_node.ptr());
798
799 if (corrected_offset > node->text().bytes_as_string_view().length())
800 return Vector<Line::CompletionSuggestion> {};
801
802 return shell.complete_program_name(node->text(), corrected_offset);
803}
804
805RefPtr<Node const> CastToCommand::leftmost_trivial_literal() const
806{
807 return m_inner->leftmost_trivial_literal();
808}
809
810CastToCommand::CastToCommand(Position position, NonnullRefPtr<Node> inner)
811 : Node(move(position))
812 , m_inner(move(inner))
813{
814 if (m_inner->is_syntax_error())
815 set_is_syntax_error(m_inner->syntax_error_node());
816}
817
818ErrorOr<void> CastToList::dump(int level) const
819{
820 TRY(Node::dump(level));
821 if (m_inner)
822 TRY(m_inner->dump(level + 1));
823 else
824 print_indented(level + 1, "(empty)");
825 return {};
826}
827
828ErrorOr<RefPtr<Value>> CastToList::run(RefPtr<Shell> shell)
829{
830 if (!m_inner)
831 return make_ref_counted<ListValue>({});
832
833 auto inner_value = TRY(TRY(m_inner->run(shell))->resolve_without_cast(shell));
834 if (shell && shell->has_any_error())
835 return make_ref_counted<ListValue>({});
836
837 if (inner_value->is_command() || inner_value->is_list())
838 return inner_value;
839
840 auto values = TRY(inner_value->resolve_as_list(shell));
841 Vector<NonnullRefPtr<Value>> cast_values;
842 for (auto& value : values)
843 cast_values.append(make_ref_counted<StringValue>(value));
844
845 return make_ref_counted<ListValue>(cast_values);
846}
847
848ErrorOr<void> CastToList::for_each_entry(RefPtr<Shell> shell, Function<ErrorOr<IterationDecision>(NonnullRefPtr<Value>)> callback)
849{
850 if (m_inner)
851 TRY(m_inner->for_each_entry(shell, move(callback)));
852 return {};
853}
854
855ErrorOr<void> CastToList::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
856{
857 if (m_inner)
858 TRY(m_inner->highlight_in_editor(editor, shell, metadata));
859 return {};
860}
861
862HitTestResult CastToList::hit_test_position(size_t offset) const
863{
864 if (!m_inner)
865 return {};
866
867 return m_inner->hit_test_position(offset);
868}
869
870RefPtr<Node const> CastToList::leftmost_trivial_literal() const
871{
872 return m_inner->leftmost_trivial_literal();
873}
874
875CastToList::CastToList(Position position, RefPtr<Node> inner)
876 : Node(move(position))
877 , m_inner(move(inner))
878{
879 if (m_inner && m_inner->is_syntax_error())
880 set_is_syntax_error(m_inner->syntax_error_node());
881}
882
883ErrorOr<void> CloseFdRedirection::dump(int level) const
884{
885 TRY(Node::dump(level));
886 print_indented(level, "{} -> Close", m_fd);
887 return {};
888}
889
890ErrorOr<RefPtr<Value>> CloseFdRedirection::run(RefPtr<Shell>)
891{
892 Command command;
893 command.position = position();
894 command.redirections.append(adopt_ref(*new CloseRedirection(m_fd)));
895 return make_ref_counted<CommandValue>(move(command));
896}
897
898ErrorOr<void> CloseFdRedirection::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata)
899{
900 editor.stylize({ m_position.start_offset, m_position.end_offset - 1 }, { Line::Style::Foreground(0x87, 0x9b, 0xcd) }); // 25% Darkened Periwinkle
901 editor.stylize({ m_position.end_offset - 1, m_position.end_offset }, { Line::Style::Foreground(0xff, 0x7e, 0x00) }); // Amber
902 return {};
903}
904
905CloseFdRedirection::CloseFdRedirection(Position position, int fd)
906 : Node(move(position))
907 , m_fd(fd)
908{
909}
910
911CloseFdRedirection::~CloseFdRedirection()
912{
913}
914
915ErrorOr<void> CommandLiteral::dump(int level) const
916{
917 TRY(Node::dump(level));
918 print_indented(level + 1, "(Generated command literal: {})", m_command);
919 return {};
920}
921
922ErrorOr<RefPtr<Value>> CommandLiteral::run(RefPtr<Shell>)
923{
924 return make_ref_counted<CommandValue>(m_command);
925}
926
927CommandLiteral::CommandLiteral(Position position, Command command)
928 : Node(move(position))
929 , m_command(move(command))
930{
931}
932
933CommandLiteral::~CommandLiteral()
934{
935}
936
937ErrorOr<void> Comment::dump(int level) const
938{
939 TRY(Node::dump(level));
940 print_indented(level + 1, "{}", m_text);
941 return {};
942}
943
944ErrorOr<RefPtr<Value>> Comment::run(RefPtr<Shell>)
945{
946 return make_ref_counted<ListValue>({});
947}
948
949ErrorOr<void> Comment::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata)
950{
951 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(150, 150, 150) }); // Light gray
952 return {};
953}
954
955Comment::Comment(Position position, String text)
956 : Node(move(position))
957 , m_text(move(text))
958{
959}
960
961Comment::~Comment()
962{
963}
964
965ErrorOr<void> ContinuationControl::dump(int level) const
966{
967 TRY(Node::dump(level));
968 print_indented(level + 1, "{}", m_kind == Continue ? "(Continue)"sv : "(Break)"sv);
969 return {};
970}
971
972ErrorOr<RefPtr<Value>> ContinuationControl::run(RefPtr<Shell> shell)
973{
974 if (m_kind == Break)
975 shell->raise_error(Shell::ShellError::InternalControlFlowBreak, {}, position());
976 else if (m_kind == Continue)
977 shell->raise_error(Shell::ShellError::InternalControlFlowContinue, {}, position());
978 else
979 VERIFY_NOT_REACHED();
980 return make_ref_counted<ListValue>({});
981}
982
983ErrorOr<void> ContinuationControl::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata)
984{
985 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
986 return {};
987}
988
989ErrorOr<void> DoubleQuotedString::dump(int level) const
990{
991 TRY(Node::dump(level));
992 TRY(m_inner->dump(level + 1));
993 return {};
994}
995
996ErrorOr<RefPtr<Value>> DoubleQuotedString::run(RefPtr<Shell> shell)
997{
998 StringBuilder builder;
999 auto values = TRY(TRY(m_inner->run(shell))->resolve_as_list(shell));
1000
1001 builder.join(""sv, values);
1002
1003 return make_ref_counted<StringValue>(TRY(builder.to_string()));
1004}
1005
1006ErrorOr<void> DoubleQuotedString::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
1007{
1008 Line::Style style { Line::Style::Foreground(Line::Style::XtermColor::Yellow) };
1009 if (metadata.is_first_in_list)
1010 style.unify_with({ Line::Style::Bold });
1011
1012 editor.stylize({ m_position.start_offset, m_position.end_offset }, style);
1013 metadata.is_first_in_list = false;
1014 return m_inner->highlight_in_editor(editor, shell, metadata);
1015}
1016
1017HitTestResult DoubleQuotedString::hit_test_position(size_t offset) const
1018{
1019 return m_inner->hit_test_position(offset);
1020}
1021
1022DoubleQuotedString::DoubleQuotedString(Position position, RefPtr<Node> inner)
1023 : Node(move(position))
1024 , m_inner(move(inner))
1025{
1026 if (m_inner->is_syntax_error())
1027 set_is_syntax_error(m_inner->syntax_error_node());
1028}
1029
1030DoubleQuotedString::~DoubleQuotedString()
1031{
1032}
1033
1034ErrorOr<void> DynamicEvaluate::dump(int level) const
1035{
1036 TRY(Node::dump(level));
1037 TRY(m_inner->dump(level + 1));
1038 return {};
1039}
1040
1041ErrorOr<RefPtr<Value>> DynamicEvaluate::run(RefPtr<Shell> shell)
1042{
1043 auto result = TRY(TRY(m_inner->run(shell))->resolve_without_cast(shell));
1044 if (shell && shell->has_any_error())
1045 return make_ref_counted<ListValue>({});
1046
1047 // Dynamic Evaluation behaves differently between strings and lists.
1048 // Strings are treated as variables, and Lists are treated as commands.
1049 if (result->is_string()) {
1050 auto name_part = result->resolve_as_list(shell).release_value_but_fixme_should_propagate_errors();
1051 VERIFY(name_part.size() == 1);
1052 return make_ref_counted<SimpleVariableValue>(name_part[0]);
1053 }
1054
1055 // If it's anything else, we're just gonna cast it to a list.
1056 auto list = TRY(result->resolve_as_list(shell));
1057 return make_ref_counted<CommandValue>(move(list), position());
1058}
1059
1060ErrorOr<void> DynamicEvaluate::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
1061{
1062 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
1063 return m_inner->highlight_in_editor(editor, shell, metadata);
1064}
1065
1066HitTestResult DynamicEvaluate::hit_test_position(size_t offset) const
1067{
1068 return m_inner->hit_test_position(offset);
1069}
1070
1071DynamicEvaluate::DynamicEvaluate(Position position, NonnullRefPtr<Node> inner)
1072 : Node(move(position))
1073 , m_inner(move(inner))
1074{
1075 if (m_inner->is_syntax_error())
1076 set_is_syntax_error(m_inner->syntax_error_node());
1077}
1078
1079DynamicEvaluate::~DynamicEvaluate()
1080{
1081}
1082
1083ErrorOr<void> Fd2FdRedirection::dump(int level) const
1084{
1085 TRY(Node::dump(level));
1086 print_indented(level, "{} -> {}", m_old_fd, m_new_fd);
1087 return {};
1088}
1089
1090ErrorOr<RefPtr<Value>> Fd2FdRedirection::run(RefPtr<Shell>)
1091{
1092 Command command;
1093 command.position = position();
1094 command.redirections.append(FdRedirection::create(m_new_fd, m_old_fd, Rewiring::Close::None));
1095 return make_ref_counted<CommandValue>(move(command));
1096}
1097
1098ErrorOr<void> Fd2FdRedirection::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata)
1099{
1100 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(0x87, 0x9b, 0xcd) }); // 25% Darkened Periwinkle
1101 return {};
1102}
1103
1104Fd2FdRedirection::Fd2FdRedirection(Position position, int src, int dst)
1105 : Node(move(position))
1106 , m_old_fd(src)
1107 , m_new_fd(dst)
1108{
1109}
1110
1111Fd2FdRedirection::~Fd2FdRedirection()
1112{
1113}
1114
1115ErrorOr<void> FunctionDeclaration::dump(int level) const
1116{
1117 TRY(Node::dump(level));
1118 print_indented(level + 1, "(name: {})\n", m_name.name);
1119 print_indented(level + 1, "(argument names)");
1120 for (auto& arg : m_arguments)
1121 print_indented(level + 2, "(name: {})\n", arg.name);
1122
1123 print_indented(level + 1, "(body)");
1124 if (m_block)
1125 TRY(m_block->dump(level + 2));
1126 else
1127 print_indented(level + 2, "(null)");
1128 return {};
1129}
1130
1131ErrorOr<RefPtr<Value>> FunctionDeclaration::run(RefPtr<Shell> shell)
1132{
1133 Vector<DeprecatedString> args;
1134 for (auto& arg : m_arguments)
1135 args.append(arg.name.to_deprecated_string());
1136
1137 shell->define_function(m_name.name.to_deprecated_string(), move(args), m_block);
1138
1139 return make_ref_counted<ListValue>({});
1140}
1141
1142ErrorOr<void> FunctionDeclaration::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
1143{
1144 editor.stylize({ m_name.position.start_offset, m_name.position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue) });
1145
1146 for (auto& arg : m_arguments)
1147 editor.stylize({ arg.position.start_offset, arg.position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Italic });
1148
1149 metadata.is_first_in_list = true;
1150 if (m_block)
1151 TRY(m_block->highlight_in_editor(editor, shell, metadata));
1152 return {};
1153}
1154
1155HitTestResult FunctionDeclaration::hit_test_position(size_t offset) const
1156{
1157 if (!m_block)
1158 return {};
1159
1160 auto result = m_block->hit_test_position(offset);
1161 if (result.matching_node && result.matching_node->is_simple_variable())
1162 result.closest_node_with_semantic_meaning = this;
1163 return result;
1164}
1165
1166ErrorOr<Vector<Line::CompletionSuggestion>> FunctionDeclaration::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
1167{
1168 auto matching_node = hit_test_result.matching_node;
1169 if (!matching_node)
1170 return Vector<Line::CompletionSuggestion> {};
1171
1172 if (!matching_node->is_simple_variable())
1173 return matching_node->complete_for_editor(shell, offset, hit_test_result);
1174
1175 auto corrected_offset = offset - matching_node->position().start_offset - 1; // Skip the first '$'
1176 auto* node = static_cast<SimpleVariable const*>(matching_node.ptr());
1177
1178 auto name = node->name().bytes_as_string_view().substring_view(0, corrected_offset);
1179
1180 Vector<Line::CompletionSuggestion> results;
1181 for (auto& arg : m_arguments) {
1182 if (arg.name.starts_with_bytes(name))
1183 results.append(arg.name.to_deprecated_string());
1184 }
1185
1186 results.extend(TRY(matching_node->complete_for_editor(shell, offset, hit_test_result)));
1187
1188 return results;
1189}
1190
1191FunctionDeclaration::FunctionDeclaration(Position position, NameWithPosition name, Vector<NameWithPosition> arguments, RefPtr<AST::Node> body)
1192 : Node(move(position))
1193 , m_name(move(name))
1194 , m_arguments(arguments)
1195 , m_block(move(body))
1196{
1197 if (m_block && m_block->is_syntax_error())
1198 set_is_syntax_error(m_block->syntax_error_node());
1199}
1200
1201FunctionDeclaration::~FunctionDeclaration()
1202{
1203}
1204
1205ErrorOr<void> ForLoop::dump(int level) const
1206{
1207 TRY(Node::dump(level));
1208 if (m_variable.has_value())
1209 print_indented(level + 1, "iterating with {} in", m_variable->name);
1210 if (m_index_variable.has_value())
1211 print_indented(level + 1, "with index name {} in", m_index_variable->name);
1212 if (m_iterated_expression)
1213 TRY(m_iterated_expression->dump(level + 2));
1214 else
1215 print_indented(level + 2, "(ever)");
1216 print_indented(level + 1, "Running");
1217 if (m_block)
1218 TRY(m_block->dump(level + 2));
1219 else
1220 print_indented(level + 2, "(null)");
1221 return {};
1222}
1223
1224ErrorOr<RefPtr<Value>> ForLoop::run(RefPtr<Shell> shell)
1225{
1226 if (!m_block)
1227 return make_ref_counted<ListValue>({});
1228
1229 size_t consecutive_interruptions = 0;
1230 auto run = [&](auto& block_value) {
1231 if (shell->has_error(Shell::ShellError::InternalControlFlowBreak)) {
1232 shell->take_error();
1233 return IterationDecision::Break;
1234 }
1235
1236 if (shell->has_error(Shell::ShellError::InternalControlFlowContinue)) {
1237 shell->take_error();
1238 return IterationDecision::Continue;
1239 }
1240
1241 if (shell->has_any_error() && !shell->has_error(Shell::ShellError::InternalControlFlowInterrupted))
1242 return IterationDecision::Break;
1243
1244 if (block_value->is_job()) {
1245 auto job = static_cast<JobValue*>(block_value.ptr())->job();
1246 if (!job || job->is_running_in_background())
1247 return IterationDecision::Continue;
1248 shell->block_on_job(job);
1249
1250 if (shell->has_any_error()) {
1251 if (shell->has_error(Shell::ShellError::InternalControlFlowInterrupted))
1252 ++consecutive_interruptions;
1253
1254 if (shell->has_error(Shell::ShellError::InternalControlFlowKilled))
1255 return IterationDecision::Break;
1256 }
1257 }
1258 return IterationDecision::Continue;
1259 };
1260
1261 if (m_iterated_expression) {
1262 auto variable_name = m_variable.has_value() ? m_variable->name : "it"_short_string;
1263 Optional<StringView> index_name = m_index_variable.has_value() ? Optional<StringView>(m_index_variable->name) : Optional<StringView>();
1264 size_t i = 0;
1265 TRY(m_iterated_expression->for_each_entry(shell, [&](auto value) -> ErrorOr<IterationDecision> {
1266 if (consecutive_interruptions >= 2)
1267 return IterationDecision::Break;
1268
1269 if (shell) {
1270 if (shell->has_error(Shell::ShellError::InternalControlFlowInterrupted))
1271 shell->take_error();
1272
1273 if (shell->has_any_error())
1274 return IterationDecision::Break;
1275 }
1276
1277 RefPtr<Value> block_value;
1278
1279 {
1280 auto frame = shell->push_frame(DeprecatedString::formatted("for ({})", this));
1281 shell->set_local_variable(variable_name.bytes_as_string_view(), value, true);
1282
1283 if (index_name.has_value())
1284 shell->set_local_variable(index_name.value(), make_ref_counted<AST::StringValue>(TRY(String::number(i))), true);
1285
1286 ++i;
1287
1288 block_value = TRY(m_block->run(shell));
1289 }
1290
1291 return run(block_value);
1292 }));
1293 } else {
1294 for (;;) {
1295 if (consecutive_interruptions >= 2)
1296 break;
1297
1298 if (shell) {
1299 if (shell->has_error(Shell::ShellError::InternalControlFlowInterrupted))
1300 shell->take_error();
1301
1302 if (shell->has_any_error())
1303 break;
1304 }
1305
1306 RefPtr<Value> block_value = TRY(m_block->run(shell));
1307 if (run(block_value) == IterationDecision::Break)
1308 break;
1309 }
1310 }
1311
1312 return make_ref_counted<ListValue>({});
1313}
1314
1315ErrorOr<void> ForLoop::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
1316{
1317 auto is_loop = m_iterated_expression.is_null();
1318 editor.stylize({ m_position.start_offset, m_position.start_offset + (is_loop ? 4 : 3) }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
1319 if (!is_loop) {
1320 if (m_in_kw_position.has_value())
1321 editor.stylize({ m_in_kw_position.value().start_offset, m_in_kw_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
1322
1323 if (m_index_kw_position.has_value())
1324 editor.stylize({ m_index_kw_position.value().start_offset, m_index_kw_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
1325
1326 metadata.is_first_in_list = false;
1327 TRY(m_iterated_expression->highlight_in_editor(editor, shell, metadata));
1328 }
1329
1330 if (m_index_variable.has_value())
1331 editor.stylize({ m_index_variable->position.start_offset, m_index_variable->position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Italic });
1332
1333 if (m_variable.has_value())
1334 editor.stylize({ m_variable->position.start_offset, m_variable->position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Italic });
1335
1336 metadata.is_first_in_list = true;
1337 if (m_block)
1338 TRY(m_block->highlight_in_editor(editor, shell, metadata));
1339 return {};
1340}
1341
1342HitTestResult ForLoop::hit_test_position(size_t offset) const
1343{
1344 if (m_iterated_expression) {
1345 if (auto result = m_iterated_expression->hit_test_position(offset); result.matching_node)
1346 return result;
1347 }
1348
1349 if (!m_block)
1350 return {};
1351
1352 return m_block->hit_test_position(offset);
1353}
1354
1355ForLoop::ForLoop(Position position, Optional<NameWithPosition> variable, Optional<NameWithPosition> index_variable, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position, Optional<Position> index_kw_position)
1356 : Node(move(position))
1357 , m_variable(move(variable))
1358 , m_index_variable(move(index_variable))
1359 , m_iterated_expression(move(iterated_expr))
1360 , m_block(move(block))
1361 , m_in_kw_position(move(in_kw_position))
1362 , m_index_kw_position(move(index_kw_position))
1363{
1364 if (m_iterated_expression && m_iterated_expression->is_syntax_error())
1365 set_is_syntax_error(m_iterated_expression->syntax_error_node());
1366 else if (m_block && m_block->is_syntax_error())
1367 set_is_syntax_error(m_block->syntax_error_node());
1368}
1369
1370ForLoop::~ForLoop()
1371{
1372}
1373
1374ErrorOr<void> Glob::dump(int level) const
1375{
1376 TRY(Node::dump(level));
1377 print_indented(level + 1, "{}", m_text);
1378 return {};
1379}
1380
1381ErrorOr<RefPtr<Value>> Glob::run(RefPtr<Shell>)
1382{
1383 return make_ref_counted<GlobValue>(m_text, position());
1384}
1385
1386ErrorOr<void> Glob::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata metadata)
1387{
1388 Line::Style style { Line::Style::Foreground(Line::Style::XtermColor::Cyan) };
1389 if (metadata.is_first_in_list)
1390 style.unify_with({ Line::Style::Bold });
1391 editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style));
1392 return {};
1393}
1394
1395Glob::Glob(Position position, String text)
1396 : Node(move(position))
1397 , m_text(move(text))
1398{
1399}
1400
1401Glob::~Glob()
1402{
1403}
1404
1405ErrorOr<void> Heredoc::dump(int level) const
1406{
1407 TRY(Node::dump(level));
1408 print_indented(level + 1, "(End Key)");
1409 print_indented(level + 2, "{}", m_end);
1410 print_indented(level + 1, "(Allows Interpolation)");
1411 print_indented(level + 2, "{}", m_allows_interpolation);
1412 if (!evaluates_to_string()) {
1413 print_indented(level + 1, "(Target FD)");
1414 print_indented(level + 2, "{}", *m_target_fd);
1415 }
1416 print_indented(level + 1, "(Contents)");
1417 if (m_contents)
1418 TRY(m_contents->dump(level + 2));
1419 else
1420 print_indented(level + 2, "(null)");
1421 return {};
1422}
1423
1424ErrorOr<RefPtr<Value>> Heredoc::run(RefPtr<Shell> shell)
1425{
1426 if (!m_contents) {
1427 if (shell)
1428 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Attempt to evaluate an unresolved heredoc"sv, position());
1429 return nullptr;
1430 }
1431
1432 auto value = TRY([&]() -> ErrorOr<RefPtr<Value>> {
1433 if (!m_deindent)
1434 return TRY(m_contents->run(shell));
1435
1436 // To deindent, first split to lines...
1437 auto value = TRY(m_contents->run(shell));
1438 if (shell && shell->has_any_error())
1439 return make_ref_counted<ListValue>({});
1440
1441 if (!value)
1442 return value;
1443 auto list = TRY(value->resolve_as_list(shell));
1444 // The list better have one entry, otherwise we've put the wrong kind of node inside this heredoc
1445 VERIFY(list.size() == 1);
1446 auto lines = list.first().bytes_as_string_view().split_view('\n');
1447
1448 // Now just trim each line and put them back in a string
1449 StringBuilder builder { list.first().bytes_as_string_view().length() };
1450 for (auto& line : lines) {
1451 builder.append(line.trim_whitespace(TrimMode::Left));
1452 builder.append('\n');
1453 }
1454
1455 return make_ref_counted<StringValue>(TRY(builder.to_string()));
1456 }());
1457
1458 if (evaluates_to_string())
1459 return value;
1460
1461 int fds[2];
1462 auto rc = pipe(fds);
1463 if (rc != 0) {
1464 // pipe() failed for {}
1465 if (shell)
1466 shell->raise_error(Shell::ShellError::PipeFailure, DeprecatedString::formatted("heredoc: {}", strerror(errno)), position());
1467 return nullptr;
1468 }
1469
1470 auto read_end = fds[0];
1471 auto write_end = fds[1];
1472
1473 // Dump all of 'value' into the pipe.
1474 auto* file = fdopen(write_end, "wb");
1475 if (!file) {
1476 if (shell)
1477 shell->raise_error(Shell::ShellError::OpenFailure, "heredoc"sv, position());
1478 return nullptr;
1479 }
1480
1481 auto text = TRY(value->resolve_as_string(shell));
1482 auto bytes = text.bytes();
1483
1484 auto written = fwrite(bytes.data(), 1, bytes.size(), file);
1485 fflush(file);
1486 if (written != bytes.size()) {
1487 if (shell)
1488 shell->raise_error(Shell::ShellError::WriteFailure, "heredoc"sv, position());
1489 }
1490 fclose(file);
1491
1492 Command command;
1493 command.position = position();
1494 command.redirections.append(FdRedirection::create(read_end, *target_fd(), Rewiring::Close::None));
1495 return make_ref_counted<CommandValue>(move(command));
1496}
1497
1498ErrorOr<void> Heredoc::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
1499{
1500 Line::Style content_style { Line::Style::Foreground(Line::Style::XtermColor::Yellow) };
1501 if (metadata.is_first_in_list)
1502 content_style.unify_with({ Line::Style::Bold });
1503
1504 if (!m_contents)
1505 content_style.unify_with({ Line::Style::Foreground(Line::Style::XtermColor::Red) }, true);
1506
1507 editor.stylize({ m_position.start_offset, m_position.end_offset }, content_style);
1508 if (m_contents)
1509 TRY(m_contents->highlight_in_editor(editor, shell, metadata));
1510 return {};
1511}
1512
1513HitTestResult Heredoc::hit_test_position(size_t offset) const
1514{
1515 if (!m_contents)
1516 return {};
1517
1518 return m_contents->hit_test_position(offset);
1519}
1520
1521Heredoc::Heredoc(Position position, String end, bool allow_interpolation, bool deindent, Optional<int> target_fd)
1522 : Node(move(position))
1523 , m_end(move(end))
1524 , m_allows_interpolation(allow_interpolation)
1525 , m_deindent(deindent)
1526 , m_target_fd(target_fd)
1527{
1528}
1529
1530Heredoc::~Heredoc()
1531{
1532}
1533
1534ErrorOr<void> HistoryEvent::dump(int level) const
1535{
1536 TRY(Node::dump(level));
1537 print_indented(level + 1, "Event Selector");
1538 switch (m_selector.event.kind) {
1539 case HistorySelector::EventKind::IndexFromStart:
1540 print_indented(level + 2, "IndexFromStart");
1541 break;
1542 case HistorySelector::EventKind::IndexFromEnd:
1543 print_indented(level + 2, "IndexFromEnd");
1544 break;
1545 case HistorySelector::EventKind::ContainingStringLookup:
1546 print_indented(level + 2, "ContainingStringLookup");
1547 break;
1548 case HistorySelector::EventKind::StartingStringLookup:
1549 print_indented(level + 2, "StartingStringLookup");
1550 break;
1551 }
1552 print_indented(level + 3, "{}({})", m_selector.event.index, m_selector.event.text);
1553
1554 print_indented(level + 1, "Word Selector");
1555 auto print_word_selector = [&](HistorySelector::WordSelector const& selector) {
1556 switch (selector.kind) {
1557 case HistorySelector::WordSelectorKind::Index:
1558 print_indented(level + 3, "Index {}", selector.selector);
1559 break;
1560 case HistorySelector::WordSelectorKind::Last:
1561 print_indented(level + 3, "Last");
1562 break;
1563 }
1564 };
1565
1566 if (m_selector.word_selector_range.end.has_value()) {
1567 print_indented(level + 2, "Range Start");
1568 print_word_selector(m_selector.word_selector_range.start);
1569 print_indented(level + 2, "Range End");
1570 print_word_selector(m_selector.word_selector_range.end.value());
1571 } else {
1572 print_indented(level + 2, "Direct Address");
1573 print_word_selector(m_selector.word_selector_range.start);
1574 }
1575
1576 return {};
1577}
1578
1579ErrorOr<RefPtr<Value>> HistoryEvent::run(RefPtr<Shell> shell)
1580{
1581 if (!shell)
1582 return make_ref_counted<AST::ListValue>({});
1583
1584 auto editor = shell->editor();
1585 if (!editor) {
1586 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "No history available!", position());
1587 return make_ref_counted<AST::ListValue>({});
1588 }
1589 auto& history = editor->history();
1590
1591 // FIXME: Implement reverse iterators and find()?
1592 auto find_reverse = [](auto it_start, auto it_end, auto finder) {
1593 auto it = it_end;
1594 while (it != it_start) {
1595 --it;
1596 if (finder(*it))
1597 return it;
1598 }
1599 return it_end;
1600 };
1601 // First, resolve the event itself.
1602 DeprecatedString resolved_history;
1603 switch (m_selector.event.kind) {
1604 case HistorySelector::EventKind::IndexFromStart:
1605 if (m_selector.event.index >= history.size()) {
1606 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "History event index out of bounds", m_selector.event.text_position);
1607 return make_ref_counted<AST::ListValue>({});
1608 }
1609 resolved_history = history[m_selector.event.index].entry;
1610 break;
1611 case HistorySelector::EventKind::IndexFromEnd:
1612 if (m_selector.event.index >= history.size()) {
1613 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "History event index out of bounds", m_selector.event.text_position);
1614 return make_ref_counted<AST::ListValue>({});
1615 }
1616 resolved_history = history[history.size() - m_selector.event.index - 1].entry;
1617 break;
1618 case HistorySelector::EventKind::ContainingStringLookup: {
1619 auto it = find_reverse(history.begin(), history.end(), [&](auto& entry) { return entry.entry.contains(m_selector.event.text); });
1620 if (it.is_end()) {
1621 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "History event did not match any entry", m_selector.event.text_position);
1622 return make_ref_counted<AST::ListValue>({});
1623 }
1624 resolved_history = it->entry;
1625 break;
1626 }
1627 case HistorySelector::EventKind::StartingStringLookup: {
1628 auto it = find_reverse(history.begin(), history.end(), [&](auto& entry) { return entry.entry.starts_with(m_selector.event.text); });
1629 if (it.is_end()) {
1630 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "History event did not match any entry", m_selector.event.text_position);
1631 return make_ref_counted<AST::ListValue>({});
1632 }
1633 resolved_history = it->entry;
1634 break;
1635 }
1636 }
1637
1638 // Then, split it up to "words".
1639 auto nodes = Parser { resolved_history }.parse_as_multiple_expressions();
1640
1641 // Now take the "words" as described by the word selectors.
1642 bool is_range = m_selector.word_selector_range.end.has_value();
1643 if (is_range) {
1644 auto start_index = m_selector.word_selector_range.start.resolve(nodes.size());
1645 auto end_index = m_selector.word_selector_range.end->resolve(nodes.size());
1646 if (start_index >= nodes.size()) {
1647 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "History word index out of bounds", m_selector.word_selector_range.start.position);
1648 return make_ref_counted<AST::ListValue>({});
1649 }
1650 if (end_index >= nodes.size()) {
1651 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "History word index out of bounds", m_selector.word_selector_range.end->position);
1652 return make_ref_counted<AST::ListValue>({});
1653 }
1654
1655 decltype(nodes) resolved_nodes;
1656 resolved_nodes.append(nodes.data() + start_index, end_index - start_index + 1);
1657 NonnullRefPtr<AST::Node> list = make_ref_counted<AST::ListConcatenate>(position(), move(resolved_nodes));
1658 return list->run(shell);
1659 }
1660
1661 auto index = m_selector.word_selector_range.start.resolve(nodes.size());
1662 if (index >= nodes.size()) {
1663 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "History word index out of bounds", m_selector.word_selector_range.start.position);
1664 return make_ref_counted<AST::ListValue>({});
1665 }
1666 return nodes[index]->run(shell);
1667}
1668
1669ErrorOr<void> HistoryEvent::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata metadata)
1670{
1671 Line::Style style { Line::Style::Foreground(Line::Style::XtermColor::Green) };
1672 if (metadata.is_first_in_list)
1673 style.unify_with({ Line::Style::Bold });
1674 editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style));
1675 return {};
1676}
1677
1678HistoryEvent::HistoryEvent(Position position, HistorySelector selector)
1679 : Node(move(position))
1680 , m_selector(move(selector))
1681{
1682 if (m_selector.word_selector_range.start.syntax_error_node)
1683 set_is_syntax_error(*m_selector.word_selector_range.start.syntax_error_node);
1684 else if (m_selector.word_selector_range.end.has_value() && m_selector.word_selector_range.end->syntax_error_node)
1685 set_is_syntax_error(*m_selector.word_selector_range.end->syntax_error_node);
1686}
1687
1688HistoryEvent::~HistoryEvent()
1689{
1690}
1691
1692ErrorOr<void> Execute::dump(int level) const
1693{
1694 TRY(Node::dump(level));
1695 if (m_capture_stdout)
1696 print_indented(level + 1, "(Capturing stdout)");
1697 TRY(m_command->dump(level + 1));
1698
1699 return {};
1700}
1701
1702ErrorOr<void> Execute::for_each_entry(RefPtr<Shell> shell, Function<ErrorOr<IterationDecision>(NonnullRefPtr<Value>)> callback)
1703{
1704 if (m_command->would_execute())
1705 return m_command->for_each_entry(shell, move(callback));
1706
1707 auto unexpanded_commands = TRY(TRY(m_command->run(shell))->resolve_as_commands(shell));
1708 if (shell && shell->has_any_error())
1709 return {};
1710
1711 if (!shell)
1712 return {};
1713
1714 auto commands = TRY(shell->expand_aliases(move(unexpanded_commands)));
1715
1716 if (m_capture_stdout) {
1717 // Make sure that we're going to be running _something_.
1718 auto has_one_command = false;
1719 for (auto& command : commands) {
1720 if (command.argv.is_empty() && !command.pipeline && command.next_chain.is_empty())
1721 continue;
1722 has_one_command = true;
1723 break;
1724 }
1725
1726 if (!has_one_command) {
1727 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Cannot capture standard output when no command is being executed", m_position);
1728 return {};
1729 }
1730 int pipefd[2];
1731 int rc = pipe(pipefd);
1732 if (rc < 0) {
1733 dbgln("Error: cannot pipe(): {}", strerror(errno));
1734 return {};
1735 }
1736 auto& last_in_commands = commands.last();
1737
1738 last_in_commands.redirections.prepend(FdRedirection::create(pipefd[1], STDOUT_FILENO, Rewiring::Close::Old));
1739 last_in_commands.should_wait = false;
1740 last_in_commands.should_notify_if_in_background = false;
1741 last_in_commands.is_pipe_source = false;
1742
1743 Core::EventLoop loop;
1744
1745 auto notifier = Core::Notifier::construct(pipefd[0], Core::Notifier::Read);
1746 AllocatingMemoryStream stream;
1747
1748 enum CheckResult {
1749 Continue,
1750 Break,
1751 NothingLeft,
1752 };
1753 auto check_and_call = [&]() -> ErrorOr<CheckResult> {
1754 auto ifs = TRY(shell->local_variable_or("IFS"sv, "\n"sv));
1755
1756 if (auto offset = TRY(stream.offset_of(ifs.bytes())); offset.has_value()) {
1757 auto line_end = offset.value();
1758 if (line_end == 0) {
1759 TRY(stream.discard(ifs.length()));
1760
1761 if (shell->options.inline_exec_keep_empty_segments)
1762 if (TRY(callback(make_ref_counted<StringValue>(String {}))) == IterationDecision::Break) {
1763 loop.quit(Break);
1764 notifier->set_enabled(false);
1765 return Break;
1766 }
1767 } else {
1768 auto entry_result = ByteBuffer::create_uninitialized(line_end + ifs.length());
1769 if (entry_result.is_error()) {
1770 loop.quit(Break);
1771 notifier->set_enabled(false);
1772 return Break;
1773 }
1774 auto entry = entry_result.release_value();
1775 TRY(stream.read_until_filled(entry));
1776
1777 auto str = TRY(String::from_utf8(StringView(entry.data(), entry.size() - ifs.length())));
1778 if (TRY(callback(make_ref_counted<StringValue>(move(str)))) == IterationDecision::Break) {
1779 loop.quit(Break);
1780 notifier->set_enabled(false);
1781 return Break;
1782 }
1783 }
1784
1785 return Continue;
1786 }
1787
1788 return NothingLeft;
1789 };
1790
1791 notifier->on_ready_to_read = [&]() -> void {
1792 constexpr static auto buffer_size = 16;
1793 u8 buffer[buffer_size];
1794 size_t remaining_size = buffer_size;
1795
1796 for (;;) {
1797 notifier->set_event_mask(Core::Notifier::None);
1798 bool should_enable_notifier = false;
1799
1800 ScopeGuard notifier_enabler { [&] {
1801 if (should_enable_notifier)
1802 notifier->set_event_mask(Core::Notifier::Read);
1803 } };
1804
1805 if (check_and_call().release_value_but_fixme_should_propagate_errors() == Break) {
1806 loop.quit(Break);
1807 return;
1808 }
1809
1810 auto read_size = read(pipefd[0], buffer, remaining_size);
1811 if (read_size < 0) {
1812 int saved_errno = errno;
1813 if (saved_errno == EINTR) {
1814 should_enable_notifier = true;
1815 continue;
1816 }
1817 if (saved_errno == 0)
1818 continue;
1819 dbgln("read() failed: {}", strerror(saved_errno));
1820 break;
1821 }
1822 if (read_size == 0)
1823 break;
1824
1825 should_enable_notifier = true;
1826 stream.write_until_depleted({ buffer, (size_t)read_size }).release_value_but_fixme_should_propagate_errors();
1827 }
1828
1829 loop.quit(NothingLeft);
1830 };
1831
1832 auto jobs = shell->run_commands(commands);
1833 ScopeGuard kill_jobs_if_around { [&] {
1834 for (auto& job : jobs) {
1835 if (job->is_running_in_background() && !job->exited() && !job->signaled()) {
1836 job->set_should_announce_signal(false); // We're explicitly killing it here.
1837 shell->kill_job(job, SIGTERM);
1838 }
1839 }
1840 } };
1841
1842 auto exit_reason = loop.exec();
1843
1844 notifier->on_ready_to_read = nullptr;
1845
1846 if (close(pipefd[0]) < 0) {
1847 dbgln("close() failed: {}", strerror(errno));
1848 }
1849
1850 if (exit_reason != Break && !stream.is_eof()) {
1851 auto action = Continue;
1852 do {
1853 action = TRY(check_and_call());
1854 if (action == Break)
1855 return {};
1856 } while (action == Continue);
1857
1858 if (!stream.is_eof()) {
1859 auto entry_result = ByteBuffer::create_uninitialized(stream.used_buffer_size());
1860 if (entry_result.is_error()) {
1861 shell->raise_error(Shell::ShellError::OutOfMemory, {}, position());
1862 return {};
1863 }
1864 auto entry = entry_result.release_value();
1865 TRY(stream.read_until_filled(entry));
1866 TRY(callback(make_ref_counted<StringValue>(TRY(String::from_utf8(entry)))));
1867 }
1868 }
1869
1870 return {};
1871 }
1872
1873 auto jobs = shell->run_commands(commands);
1874
1875 if (!jobs.is_empty())
1876 TRY(callback(make_ref_counted<JobValue>(jobs.last())));
1877
1878 return {};
1879}
1880
1881ErrorOr<RefPtr<Value>> Execute::run(RefPtr<Shell> shell)
1882{
1883 if (shell && shell->has_any_error())
1884 return make_ref_counted<ListValue>({});
1885
1886 if (m_command->would_execute())
1887 return m_command->run(shell);
1888
1889 Vector<NonnullRefPtr<Value>> values;
1890 TRY(for_each_entry(shell, [&](auto value) {
1891 values.append(*value);
1892 return IterationDecision::Continue;
1893 }));
1894
1895 if (values.size() == 1 && values.first()->is_job())
1896 return values.first();
1897
1898 return make_ref_counted<ListValue>(move(values));
1899}
1900
1901ErrorOr<void> Execute::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
1902{
1903 if (m_capture_stdout)
1904 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Green) });
1905 metadata.is_first_in_list = true;
1906 return m_command->highlight_in_editor(editor, shell, metadata);
1907}
1908
1909HitTestResult Execute::hit_test_position(size_t offset) const
1910{
1911 auto result = m_command->hit_test_position(offset);
1912 if (!result.closest_node_with_semantic_meaning)
1913 result.closest_node_with_semantic_meaning = this;
1914 if (!result.closest_command_node)
1915 result.closest_command_node = m_command;
1916 return result;
1917}
1918
1919ErrorOr<Vector<Line::CompletionSuggestion>> Execute::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
1920{
1921 auto matching_node = hit_test_result.matching_node;
1922 if (!matching_node || !matching_node->is_bareword())
1923 return Vector<Line::CompletionSuggestion> {};
1924
1925 auto corrected_offset = offset - matching_node->position().start_offset;
1926 auto* node = static_cast<BarewordLiteral const*>(matching_node.ptr());
1927
1928 if (corrected_offset > node->text().bytes_as_string_view().length())
1929 return Vector<Line::CompletionSuggestion> {};
1930
1931 return shell.complete_program_name(node->text(), corrected_offset);
1932}
1933
1934Execute::Execute(Position position, NonnullRefPtr<Node> command, bool capture_stdout)
1935 : Node(move(position))
1936 , m_command(move(command))
1937 , m_capture_stdout(capture_stdout)
1938{
1939 if (m_command->is_syntax_error())
1940 set_is_syntax_error(m_command->syntax_error_node());
1941}
1942
1943Execute::~Execute()
1944{
1945}
1946
1947ErrorOr<void> IfCond::dump(int level) const
1948{
1949 TRY(Node::dump(level));
1950 print_indented(++level, "Condition");
1951 TRY(m_condition->dump(level + 1));
1952 print_indented(level, "True Branch");
1953 if (m_true_branch)
1954 TRY(m_true_branch->dump(level + 1));
1955 else
1956 print_indented(level + 1, "(empty)");
1957 print_indented(level, "False Branch");
1958 if (m_false_branch)
1959 TRY(m_false_branch->dump(level + 1));
1960 else
1961 print_indented(level + 1, "(empty)");
1962
1963 return {};
1964}
1965
1966ErrorOr<RefPtr<Value>> IfCond::run(RefPtr<Shell> shell)
1967{
1968 auto cond = TRY(TRY(m_condition->run(shell))->resolve_without_cast(shell));
1969 if (shell && shell->has_any_error())
1970 return make_ref_counted<ListValue>({});
1971
1972 // The condition could be a builtin, in which case it has already run and exited.
1973 if (cond->is_job()) {
1974 auto cond_job_value = static_cast<JobValue const*>(cond.ptr());
1975 auto cond_job = cond_job_value->job();
1976
1977 shell->block_on_job(cond_job);
1978 }
1979 if (shell->last_return_code == 0) {
1980 if (m_true_branch)
1981 return m_true_branch->run(shell);
1982 } else {
1983 if (m_false_branch)
1984 return m_false_branch->run(shell);
1985 }
1986
1987 return make_ref_counted<ListValue>({});
1988}
1989
1990ErrorOr<void> IfCond::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
1991{
1992 metadata.is_first_in_list = true;
1993
1994 editor.stylize({ m_position.start_offset, m_position.start_offset + 2 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
1995 if (m_else_position.has_value())
1996 editor.stylize({ m_else_position.value().start_offset, m_else_position.value().start_offset + 4 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
1997
1998 TRY(m_condition->highlight_in_editor(editor, shell, metadata));
1999 if (m_true_branch)
2000 TRY(m_true_branch->highlight_in_editor(editor, shell, metadata));
2001 if (m_false_branch)
2002 TRY(m_false_branch->highlight_in_editor(editor, shell, metadata));
2003 return {};
2004}
2005
2006HitTestResult IfCond::hit_test_position(size_t offset) const
2007{
2008 if (auto result = m_condition->hit_test_position(offset); result.matching_node)
2009 return result;
2010
2011 if (m_true_branch) {
2012 if (auto result = m_true_branch->hit_test_position(offset); result.matching_node)
2013 return result;
2014 }
2015
2016 if (m_false_branch) {
2017 if (auto result = m_false_branch->hit_test_position(offset); result.matching_node)
2018 return result;
2019 }
2020
2021 return {};
2022}
2023
2024IfCond::IfCond(Position position, Optional<Position> else_position, NonnullRefPtr<Node> condition, RefPtr<Node> true_branch, RefPtr<Node> false_branch)
2025 : Node(move(position))
2026 , m_condition(move(condition))
2027 , m_true_branch(move(true_branch))
2028 , m_false_branch(move(false_branch))
2029 , m_else_position(move(else_position))
2030{
2031 if (m_condition->is_syntax_error())
2032 set_is_syntax_error(m_condition->syntax_error_node());
2033 else if (m_true_branch && m_true_branch->is_syntax_error())
2034 set_is_syntax_error(m_true_branch->syntax_error_node());
2035 else if (m_false_branch && m_false_branch->is_syntax_error())
2036 set_is_syntax_error(m_false_branch->syntax_error_node());
2037
2038 m_condition = make_ref_counted<AST::Execute>(m_condition->position(), m_condition);
2039
2040 if (m_true_branch) {
2041 auto true_branch = m_true_branch.release_nonnull();
2042 if (true_branch->is_execute())
2043 m_true_branch = static_ptr_cast<AST::Execute>(true_branch)->command();
2044 else
2045 m_true_branch = move(true_branch);
2046 }
2047
2048 if (m_false_branch) {
2049 auto false_branch = m_false_branch.release_nonnull();
2050 if (false_branch->is_execute())
2051 m_false_branch = static_ptr_cast<AST::Execute>(false_branch)->command();
2052 else
2053 m_false_branch = move(false_branch);
2054 }
2055}
2056
2057IfCond::~IfCond()
2058{
2059}
2060
2061ErrorOr<void> ImmediateExpression::dump(int level) const
2062{
2063 TRY(Node::dump(level));
2064 print_indented(level + 1, "(function)"sv);
2065 print_indented(level + 2, "{}", m_function.name);
2066 print_indented(level + 1, "(arguments)");
2067 for (auto& argument : arguments())
2068 TRY(argument->dump(level + 2));
2069
2070 return {};
2071}
2072
2073ErrorOr<RefPtr<Value>> ImmediateExpression::run(RefPtr<Shell> shell)
2074{
2075 auto node = TRY(shell->run_immediate_function(m_function.name, *this, arguments()));
2076 if (node)
2077 return node->run(shell);
2078
2079 return make_ref_counted<ListValue>({});
2080}
2081
2082ErrorOr<void> ImmediateExpression::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2083{
2084 // '${' - FIXME: This could also be '$\\\n{'
2085 editor.stylize({ m_position.start_offset, m_position.start_offset + 2 }, { Line::Style::Foreground(Line::Style::XtermColor::Green) });
2086
2087 // Function name
2088 Line::Style function_style { Line::Style::Foreground(Line::Style::XtermColor::Red) };
2089 if (shell.has_immediate_function(function_name()))
2090 function_style = { Line::Style::Foreground(Line::Style::XtermColor::Green) };
2091 editor.stylize({ m_function.position.start_offset, m_function.position.end_offset }, move(function_style));
2092
2093 // Arguments
2094 for (auto& argument : m_arguments) {
2095 metadata.is_first_in_list = false;
2096 TRY(argument->highlight_in_editor(editor, shell, metadata));
2097 }
2098
2099 // Closing brace
2100 if (m_closing_brace_position.has_value())
2101 editor.stylize({ m_closing_brace_position->start_offset, m_closing_brace_position->end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Green) });
2102
2103 return {};
2104}
2105
2106ErrorOr<Vector<Line::CompletionSuggestion>> ImmediateExpression::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
2107{
2108 auto matching_node = hit_test_result.matching_node;
2109 if (!matching_node || matching_node != this)
2110 return Vector<Line::CompletionSuggestion> {};
2111
2112 auto corrected_offset = offset - m_function.position.start_offset;
2113
2114 if (corrected_offset > m_function.name.bytes_as_string_view().length())
2115 return Vector<Line::CompletionSuggestion> {};
2116
2117 return shell.complete_immediate_function_name(m_function.name, corrected_offset);
2118}
2119
2120HitTestResult ImmediateExpression::hit_test_position(size_t offset) const
2121{
2122 if (m_function.position.contains(offset))
2123 return { this, this, this };
2124
2125 for (auto& argument : m_arguments) {
2126 if (auto result = argument->hit_test_position(offset); result.matching_node)
2127 return result;
2128 }
2129
2130 return {};
2131}
2132
2133ImmediateExpression::ImmediateExpression(Position position, NameWithPosition function, Vector<NonnullRefPtr<AST::Node>> arguments, Optional<Position> closing_brace_position)
2134 : Node(move(position))
2135 , m_arguments(move(arguments))
2136 , m_function(move(function))
2137 , m_closing_brace_position(move(closing_brace_position))
2138{
2139 if (is_syntax_error())
2140 return;
2141
2142 for (auto& argument : m_arguments) {
2143 if (argument->is_syntax_error()) {
2144 set_is_syntax_error(argument->syntax_error_node());
2145 return;
2146 }
2147 }
2148}
2149
2150ImmediateExpression::~ImmediateExpression()
2151{
2152}
2153
2154ErrorOr<void> Join::dump(int level) const
2155{
2156 TRY(Node::dump(level));
2157 TRY(m_left->dump(level + 1));
2158 TRY(m_right->dump(level + 1));
2159 return {};
2160}
2161
2162ErrorOr<RefPtr<Value>> Join::run(RefPtr<Shell> shell)
2163{
2164 auto left = TRY(m_left->to_lazy_evaluated_commands(shell));
2165 if (shell && shell->has_any_error())
2166 return make_ref_counted<ListValue>({});
2167
2168 if (left.last().should_wait && !left.last().next_chain.is_empty()) {
2169 // Join (C0s*; C1) X -> (C0s*; Join C1 X)
2170 auto& lhs_node = left.last().next_chain.last().node;
2171 lhs_node = make_ref_counted<Join>(m_position, lhs_node, m_right);
2172 return make_ref_counted<CommandSequenceValue>(move(left));
2173 }
2174
2175 auto right = TRY(m_right->to_lazy_evaluated_commands(shell));
2176 if (shell && shell->has_any_error())
2177 return make_ref_counted<ListValue>({});
2178
2179 return make_ref_counted<CommandSequenceValue>(join_commands(move(left), move(right)));
2180}
2181
2182ErrorOr<void> Join::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2183{
2184 TRY(m_left->highlight_in_editor(editor, shell, metadata));
2185 if (m_left->is_list() || m_left->is_command())
2186 metadata.is_first_in_list = false;
2187 return m_right->highlight_in_editor(editor, shell, metadata);
2188}
2189
2190HitTestResult Join::hit_test_position(size_t offset) const
2191{
2192 auto result = m_left->hit_test_position(offset);
2193 if (result.matching_node)
2194 return result;
2195
2196 return m_right->hit_test_position(offset);
2197}
2198
2199RefPtr<Node const> Join::leftmost_trivial_literal() const
2200{
2201 if (auto value = m_left->leftmost_trivial_literal())
2202 return value;
2203 return m_right->leftmost_trivial_literal();
2204}
2205
2206Join::Join(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right)
2207 : Node(move(position))
2208 , m_left(move(left))
2209 , m_right(move(right))
2210{
2211 if (m_left->is_syntax_error())
2212 set_is_syntax_error(m_left->syntax_error_node());
2213 else if (m_right->is_syntax_error())
2214 set_is_syntax_error(m_right->syntax_error_node());
2215}
2216
2217Join::~Join()
2218{
2219}
2220
2221ErrorOr<void> MatchExpr::dump(int level) const
2222{
2223 TRY(Node::dump(level));
2224 print_indented(level + 1, "(expression: {})", m_expr_name);
2225 TRY(m_matched_expr->dump(level + 2));
2226 print_indented(level + 1, "(named: {})", m_expr_name);
2227 print_indented(level + 1, "(entries)");
2228 for (auto& entry : m_entries) {
2229 StringBuilder builder;
2230 builder.append("(match"sv);
2231 if (entry.match_names.has_value()) {
2232 builder.append(" to names ("sv);
2233 bool first = true;
2234 for (auto& name : entry.match_names.value()) {
2235 if (!first)
2236 builder.append(' ');
2237 first = false;
2238 builder.append(name);
2239 }
2240 builder.append("))"sv);
2241
2242 } else {
2243 builder.append(')');
2244 }
2245 print_indented(level + 2, "{}", builder.string_view());
2246 TRY(entry.options.visit(
2247 [&](Vector<NonnullRefPtr<Node>> const& options) -> ErrorOr<void> {
2248 for (auto& option : options)
2249 TRY(option->dump(level + 3));
2250 return {};
2251 },
2252 [&](Vector<Regex<ECMA262>> const& options) -> ErrorOr<void> {
2253 for (auto& option : options)
2254 print_indented(level + 3, "(regex: {})", option.pattern_value);
2255 return {};
2256 }));
2257 print_indented(level + 2, "(execute)");
2258 if (entry.body)
2259 TRY(entry.body->dump(level + 3));
2260 else
2261 print_indented(level + 3, "(nothing)"sv);
2262 }
2263 return {};
2264}
2265
2266ErrorOr<RefPtr<Value>> MatchExpr::run(RefPtr<Shell> shell)
2267{
2268 auto value = TRY(TRY(m_matched_expr->run(shell))->resolve_without_cast(shell));
2269 if (shell && shell->has_any_error())
2270 return make_ref_counted<ListValue>({});
2271
2272 auto list = value->resolve_as_list(shell).release_value_but_fixme_should_propagate_errors();
2273
2274 auto list_matches = [&](auto&& pattern, auto& spans) {
2275 if constexpr (IsSame<RemoveCVReference<decltype(pattern)>, Regex<ECMA262>>) {
2276 if (list.size() != 1)
2277 return false;
2278 auto& subject = list.first();
2279 auto match = pattern.match(subject);
2280 if (!match.success)
2281 return false;
2282
2283 spans.ensure_capacity(match.n_capture_groups);
2284 for (size_t i = 0; i < match.n_capture_groups; ++i) {
2285 auto& capture = match.capture_group_matches[0][i];
2286 spans.append(capture.view.to_string().release_value_but_fixme_should_propagate_errors());
2287 }
2288 return true;
2289 } else {
2290 if (pattern.size() != list.size())
2291 return false;
2292
2293 for (size_t i = 0; i < pattern.size(); ++i) {
2294 Vector<AK::MaskSpan> mask_spans;
2295 if (!list[i].bytes_as_string_view().matches(pattern[i], mask_spans))
2296 return false;
2297 for (auto& span : mask_spans)
2298 spans.append(list[i].substring_from_byte_offset(span.start, span.length).release_value_but_fixme_should_propagate_errors());
2299 }
2300
2301 return true;
2302 }
2303 };
2304
2305 auto resolve_pattern = [&](auto& option) -> decltype(auto) {
2306 if constexpr (IsSame<RemoveCVReference<decltype(option)>, Regex<ECMA262>>) {
2307 return ErrorOr<Regex<ECMA262>>(move(option));
2308 } else {
2309 Vector<String> pattern;
2310 if (option->is_glob()) {
2311 pattern.append(static_cast<Glob const*>(option.ptr())->text());
2312 } else if (option->is_bareword()) {
2313 pattern.append(static_cast<BarewordLiteral const*>(option.ptr())->text());
2314 } else {
2315 auto list_or_error = option->run(shell);
2316 if (list_or_error.is_error() || (shell && shell->has_any_error()))
2317 return ErrorOr<Vector<String>>(move(pattern));
2318
2319 auto list = list_or_error.release_value();
2320 auto result = option->for_each_entry(shell, [&](auto&& value) -> ErrorOr<IterationDecision> {
2321 pattern.extend(TRY(value->resolve_as_list(nullptr))); // Note: 'nullptr' incurs special behavior,
2322 // asking the node for a 'raw' value.
2323 return IterationDecision::Continue;
2324 });
2325
2326 if (result.is_error())
2327 return ErrorOr<Vector<String>>(result.release_error());
2328 }
2329
2330 return ErrorOr<Vector<String>>(move(pattern));
2331 }
2332 };
2333
2334 auto frame = shell->push_frame(DeprecatedString::formatted("match ({})", this));
2335 if (!m_expr_name.is_empty())
2336 shell->set_local_variable(m_expr_name.to_deprecated_string(), value, true);
2337
2338 for (auto& entry : m_entries) {
2339 auto result = TRY(entry.options.visit([&](auto& options) -> ErrorOr<Variant<IterationDecision, RefPtr<Value>>> {
2340 for (auto& option : options) {
2341 Vector<String> spans;
2342 if (list_matches(TRY(resolve_pattern(option)), spans)) {
2343 if (entry.body) {
2344 if (entry.match_names.has_value()) {
2345 size_t i = 0;
2346 for (auto& name : entry.match_names.value()) {
2347 if (spans.size() > i)
2348 shell->set_local_variable(name.to_deprecated_string(), make_ref_counted<AST::StringValue>(spans[i]), true);
2349 ++i;
2350 }
2351 }
2352 return TRY(entry.body->run(shell));
2353 }
2354 return RefPtr<Value>(make_ref_counted<AST::ListValue>({}));
2355 }
2356 }
2357 return IterationDecision::Continue;
2358 }));
2359 if (result.has<IterationDecision>() && result.get<IterationDecision>() == IterationDecision::Break)
2360 break;
2361
2362 if (result.has<RefPtr<Value>>())
2363 return move(result).get<RefPtr<Value>>();
2364 }
2365
2366 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Non-exhaustive match rules!", position());
2367 return make_ref_counted<AST::ListValue>({});
2368}
2369
2370ErrorOr<void> MatchExpr::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2371{
2372 editor.stylize({ m_position.start_offset, m_position.start_offset + 5 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
2373 if (m_as_position.has_value())
2374 editor.stylize({ m_as_position.value().start_offset, m_as_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
2375
2376 metadata.is_first_in_list = false;
2377 TRY(m_matched_expr->highlight_in_editor(editor, shell, metadata));
2378
2379 for (auto& entry : m_entries) {
2380 metadata.is_first_in_list = false;
2381 TRY(entry.options.visit(
2382 [&](Vector<NonnullRefPtr<Node>>& node_options) -> ErrorOr<void> {
2383 for (auto& option : node_options)
2384 TRY(option->highlight_in_editor(editor, shell, metadata));
2385 return {};
2386 },
2387 [](auto&) -> ErrorOr<void> { return {}; }));
2388
2389 metadata.is_first_in_list = true;
2390 if (entry.body)
2391 TRY(entry.body->highlight_in_editor(editor, shell, metadata));
2392
2393 for (auto& position : entry.pipe_positions)
2394 editor.stylize({ position.start_offset, position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
2395
2396 if (entry.match_as_position.has_value())
2397 editor.stylize({ entry.match_as_position.value().start_offset, entry.match_as_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
2398 }
2399
2400 return {};
2401}
2402
2403HitTestResult MatchExpr::hit_test_position(size_t offset) const
2404{
2405 auto result = m_matched_expr->hit_test_position(offset);
2406 if (result.matching_node)
2407 return result;
2408
2409 for (auto& entry : m_entries) {
2410 if (!entry.body)
2411 continue;
2412 auto result = entry.body->hit_test_position(offset);
2413 if (result.matching_node)
2414 return result;
2415 }
2416
2417 return {};
2418}
2419
2420MatchExpr::MatchExpr(Position position, NonnullRefPtr<Node> expr, String name, Optional<Position> as_position, Vector<MatchEntry> entries)
2421 : Node(move(position))
2422 , m_matched_expr(move(expr))
2423 , m_expr_name(move(name))
2424 , m_as_position(move(as_position))
2425 , m_entries(move(entries))
2426{
2427 if (m_matched_expr->is_syntax_error()) {
2428 set_is_syntax_error(m_matched_expr->syntax_error_node());
2429 } else {
2430 for (auto& entry : m_entries) {
2431 if (!entry.body)
2432 continue;
2433 if (entry.body->is_syntax_error()) {
2434 set_is_syntax_error(entry.body->syntax_error_node());
2435 break;
2436 }
2437 }
2438 }
2439}
2440
2441MatchExpr::~MatchExpr()
2442{
2443}
2444
2445ErrorOr<void> Or::dump(int level) const
2446{
2447 TRY(Node::dump(level));
2448 TRY(m_left->dump(level + 1));
2449 TRY(m_right->dump(level + 1));
2450 return {};
2451}
2452
2453ErrorOr<RefPtr<Value>> Or::run(RefPtr<Shell> shell)
2454{
2455 auto commands = TRY(m_left->to_lazy_evaluated_commands(shell));
2456 if (shell && shell->has_any_error())
2457 return make_ref_counted<ListValue>({});
2458
2459 commands.last().next_chain.empend(*m_right, NodeWithAction::Or);
2460 return make_ref_counted<CommandSequenceValue>(move(commands));
2461}
2462
2463ErrorOr<void> Or::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2464{
2465 TRY(m_left->highlight_in_editor(editor, shell, metadata));
2466 return m_right->highlight_in_editor(editor, shell, metadata);
2467}
2468
2469HitTestResult Or::hit_test_position(size_t offset) const
2470{
2471 auto result = m_left->hit_test_position(offset);
2472 if (result.matching_node) {
2473 if (!result.closest_command_node)
2474 result.closest_command_node = m_right;
2475 return result;
2476 }
2477
2478 result = m_right->hit_test_position(offset);
2479 if (!result.closest_command_node)
2480 result.closest_command_node = m_right;
2481 return result;
2482}
2483
2484Or::Or(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right, Position or_position)
2485 : Node(move(position))
2486 , m_left(move(left))
2487 , m_right(move(right))
2488 , m_or_position(or_position)
2489{
2490 if (m_left->is_syntax_error())
2491 set_is_syntax_error(m_left->syntax_error_node());
2492 else if (m_right->is_syntax_error())
2493 set_is_syntax_error(m_right->syntax_error_node());
2494}
2495
2496Or::~Or()
2497{
2498}
2499
2500ErrorOr<void> Pipe::dump(int level) const
2501{
2502 TRY(Node::dump(level));
2503 TRY(m_left->dump(level + 1));
2504 TRY(m_right->dump(level + 1));
2505 return {};
2506}
2507
2508ErrorOr<RefPtr<Value>> Pipe::run(RefPtr<Shell> shell)
2509{
2510 auto left = TRY(m_left->to_lazy_evaluated_commands(shell));
2511 if (shell && shell->has_any_error())
2512 return make_ref_counted<ListValue>({});
2513
2514 auto right = TRY(m_right->to_lazy_evaluated_commands(shell));
2515 if (shell && shell->has_any_error())
2516 return make_ref_counted<ListValue>({});
2517
2518 auto last_in_left = left.take_last();
2519 auto first_in_right = right.take_first();
2520
2521 auto pipe_read_end = FdRedirection::create(-1, STDIN_FILENO, Rewiring::Close::Old);
2522 auto pipe_write_end = FdRedirection::create(-1, STDOUT_FILENO, pipe_read_end, Rewiring::Close::RefreshOld);
2523
2524 auto insert_at_start_or_after_last_pipe = [&](auto& pipe, auto& command) {
2525 size_t insert_index = 0;
2526 auto& redirections = command.redirections;
2527 for (ssize_t i = redirections.size() - 1; i >= 0; --i) {
2528 auto& redirection = redirections[i];
2529 if (!redirection->is_fd_redirection())
2530 continue;
2531 auto& fd_redirection = static_cast<FdRedirection&>(*redirection);
2532 if (fd_redirection.old_fd == -1) {
2533 insert_index = i;
2534 break;
2535 }
2536 }
2537
2538 redirections.insert(insert_index, pipe);
2539 };
2540
2541 insert_at_start_or_after_last_pipe(pipe_read_end, first_in_right);
2542 insert_at_start_or_after_last_pipe(pipe_write_end, last_in_left);
2543
2544 last_in_left.should_wait = false;
2545 last_in_left.is_pipe_source = true;
2546
2547 if (first_in_right.pipeline) {
2548 last_in_left.pipeline = first_in_right.pipeline;
2549 } else {
2550 auto pipeline = make_ref_counted<Pipeline>();
2551 last_in_left.pipeline = pipeline;
2552 first_in_right.pipeline = pipeline;
2553 }
2554
2555 Vector<Command> commands;
2556 commands.extend(left);
2557 commands.append(last_in_left);
2558 commands.append(first_in_right);
2559 commands.extend(right);
2560
2561 return make_ref_counted<CommandSequenceValue>(move(commands));
2562}
2563
2564ErrorOr<void> Pipe::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2565{
2566 TRY(m_left->highlight_in_editor(editor, shell, metadata));
2567 return m_right->highlight_in_editor(editor, shell, metadata);
2568}
2569
2570HitTestResult Pipe::hit_test_position(size_t offset) const
2571{
2572 auto result = m_left->hit_test_position(offset);
2573 if (result.matching_node) {
2574 if (!result.closest_command_node)
2575 result.closest_command_node = m_right;
2576 return result;
2577 }
2578
2579 result = m_right->hit_test_position(offset);
2580 if (!result.closest_command_node)
2581 result.closest_command_node = m_right;
2582 return result;
2583}
2584
2585Pipe::Pipe(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right)
2586 : Node(move(position))
2587 , m_left(move(left))
2588 , m_right(move(right))
2589{
2590 if (m_left->is_syntax_error())
2591 set_is_syntax_error(m_left->syntax_error_node());
2592 else if (m_right->is_syntax_error())
2593 set_is_syntax_error(m_right->syntax_error_node());
2594}
2595
2596Pipe::~Pipe()
2597{
2598}
2599
2600PathRedirectionNode::PathRedirectionNode(Position position, int fd, NonnullRefPtr<Node> path)
2601 : Node(move(position))
2602 , m_fd(fd)
2603 , m_path(move(path))
2604{
2605}
2606
2607ErrorOr<void> PathRedirectionNode::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2608{
2609 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(0x87, 0x9b, 0xcd) }); // 25% Darkened Periwinkle
2610 metadata.is_first_in_list = false;
2611 TRY(m_path->highlight_in_editor(editor, shell, metadata));
2612 if (m_path->is_bareword()) {
2613 auto path_text = TRY(TRY(m_path->run(nullptr))->resolve_as_list(nullptr));
2614 VERIFY(path_text.size() == 1);
2615 // Apply a URL to the path.
2616 auto& position = m_path->position();
2617 auto& path = path_text[0];
2618 if (!path.starts_with('/'))
2619 path = String::formatted("{}/{}", shell.cwd, path).release_value_but_fixme_should_propagate_errors();
2620 auto url = URL::create_with_file_scheme(path.to_deprecated_string());
2621 url.set_host(shell.hostname);
2622 editor.stylize({ position.start_offset, position.end_offset }, { Line::Style::Hyperlink(url.to_deprecated_string()) });
2623 }
2624 return {};
2625}
2626
2627HitTestResult PathRedirectionNode::hit_test_position(size_t offset) const
2628{
2629 auto result = m_path->hit_test_position(offset);
2630 if (!result.closest_node_with_semantic_meaning)
2631 result.closest_node_with_semantic_meaning = this;
2632 return result;
2633}
2634
2635ErrorOr<Vector<Line::CompletionSuggestion>> PathRedirectionNode::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
2636{
2637 auto matching_node = hit_test_result.matching_node;
2638 if (!matching_node || !matching_node->is_bareword())
2639 return Vector<Line::CompletionSuggestion> {};
2640
2641 auto corrected_offset = offset - matching_node->position().start_offset;
2642 auto* node = static_cast<BarewordLiteral const*>(matching_node.ptr());
2643
2644 if (corrected_offset > node->text().bytes_as_string_view().length())
2645 return Vector<Line::CompletionSuggestion> {};
2646
2647 return shell.complete_path(""sv, node->text(), corrected_offset, Shell::ExecutableOnly::No, nullptr, nullptr);
2648}
2649
2650PathRedirectionNode::~PathRedirectionNode()
2651{
2652}
2653
2654ErrorOr<void> Range::dump(int level) const
2655{
2656 TRY(Node::dump(level));
2657 print_indented(level + 1, "(From)");
2658 TRY(m_start->dump(level + 2));
2659 print_indented(level + 1, "(To)");
2660 TRY(m_end->dump(level + 2));
2661 return {};
2662}
2663
2664ErrorOr<RefPtr<Value>> Range::run(RefPtr<Shell> shell)
2665{
2666 auto interpolate = [position = position()](RefPtr<Value> start, RefPtr<Value> end, RefPtr<Shell> shell) -> Vector<NonnullRefPtr<Value>> {
2667 Vector<NonnullRefPtr<Value>> values;
2668
2669 if (start->is_string() && end->is_string()) {
2670 auto start_str = start->resolve_as_list(shell).release_value_but_fixme_should_propagate_errors()[0];
2671 auto end_str = end->resolve_as_list(shell).release_value_but_fixme_should_propagate_errors()[0];
2672
2673 Utf8View start_view { start_str }, end_view { end_str };
2674 if (start_view.validate() && end_view.validate()) {
2675 if (start_view.length() == 1 && end_view.length() == 1) {
2676 // Interpolate between two code points.
2677 auto start_code_point = *start_view.begin();
2678 auto end_code_point = *end_view.begin();
2679 auto step = start_code_point > end_code_point ? -1 : 1;
2680 StringBuilder builder;
2681 for (u32 code_point = start_code_point; code_point != end_code_point; code_point += step) {
2682 builder.clear();
2683 builder.append_code_point(code_point);
2684 values.append(make_ref_counted<StringValue>(builder.to_string().release_value_but_fixme_should_propagate_errors()));
2685 }
2686 // Append the ending code point too, most shells treat this as inclusive.
2687 builder.clear();
2688 builder.append_code_point(end_code_point);
2689 values.append(make_ref_counted<StringValue>(builder.to_string().release_value_but_fixme_should_propagate_errors()));
2690 } else {
2691 // Could be two numbers?
2692 auto start_int = start_str.bytes_as_string_view().to_int();
2693 auto end_int = end_str.bytes_as_string_view().to_int();
2694 if (start_int.has_value() && end_int.has_value()) {
2695 auto start = start_int.value();
2696 auto end = end_int.value();
2697 auto step = start > end ? -1 : 1;
2698 for (int value = start; value != end; value += step)
2699 values.append(make_ref_counted<StringValue>(String::number(value).release_value_but_fixme_should_propagate_errors()));
2700 // Append the range end too, most shells treat this as inclusive.
2701 values.append(make_ref_counted<StringValue>(String::number(end).release_value_but_fixme_should_propagate_errors()));
2702 } else {
2703 goto yield_start_end;
2704 }
2705 }
2706 } else {
2707 yield_start_end:;
2708 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, DeprecatedString::formatted("Cannot interpolate between '{}' and '{}'!", start_str, end_str), position);
2709 // We can't really interpolate between the two, so just yield both.
2710 values.append(make_ref_counted<StringValue>(move(start_str)));
2711 values.append(make_ref_counted<StringValue>(move(end_str)));
2712 }
2713
2714 return values;
2715 }
2716
2717 warnln("Shell: Cannot apply the requested interpolation");
2718 return values;
2719 };
2720
2721 auto start_value = TRY(m_start->run(shell));
2722 if (shell && shell->has_any_error())
2723 return make_ref_counted<ListValue>({});
2724
2725 auto end_value = TRY(m_end->run(shell));
2726 if (shell && shell->has_any_error())
2727 return make_ref_counted<ListValue>({});
2728
2729 if (!start_value || !end_value)
2730 return make_ref_counted<ListValue>({});
2731
2732 return make_ref_counted<ListValue>(interpolate(*start_value, *end_value, shell));
2733}
2734
2735ErrorOr<void> Range::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2736{
2737 TRY(m_start->highlight_in_editor(editor, shell, metadata));
2738
2739 // Highlight the '..'
2740 editor.stylize({ m_start->position().end_offset, m_end->position().start_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
2741
2742 metadata.is_first_in_list = false;
2743 return m_end->highlight_in_editor(editor, shell, metadata);
2744}
2745
2746HitTestResult Range::hit_test_position(size_t offset) const
2747{
2748 auto result = m_start->hit_test_position(offset);
2749 if (result.matching_node) {
2750 if (!result.closest_command_node)
2751 result.closest_command_node = m_start;
2752 return result;
2753 }
2754
2755 result = m_end->hit_test_position(offset);
2756 if (!result.closest_command_node)
2757 result.closest_command_node = m_end;
2758 return result;
2759}
2760
2761Range::Range(Position position, NonnullRefPtr<Node> start, NonnullRefPtr<Node> end)
2762 : Node(move(position))
2763 , m_start(move(start))
2764 , m_end(move(end))
2765{
2766 if (m_start->is_syntax_error())
2767 set_is_syntax_error(m_start->syntax_error_node());
2768 else if (m_end->is_syntax_error())
2769 set_is_syntax_error(m_end->syntax_error_node());
2770}
2771
2772Range::~Range()
2773{
2774}
2775
2776ErrorOr<void> ReadRedirection::dump(int level) const
2777{
2778 TRY(Node::dump(level));
2779 TRY(m_path->dump(level + 1));
2780 print_indented(level + 1, "To {}", m_fd);
2781 return {};
2782}
2783
2784ErrorOr<RefPtr<Value>> ReadRedirection::run(RefPtr<Shell> shell)
2785{
2786 Command command;
2787 auto path_segments = TRY(TRY(m_path->run(shell))->resolve_as_list(shell));
2788 if (shell && shell->has_any_error())
2789 return make_ref_counted<ListValue>({});
2790
2791 StringBuilder builder;
2792 builder.join(' ', path_segments);
2793
2794 command.redirections.append(PathRedirection::create(builder.to_string().release_value_but_fixme_should_propagate_errors(), m_fd, PathRedirection::Read));
2795 return make_ref_counted<CommandValue>(move(command));
2796}
2797
2798ReadRedirection::ReadRedirection(Position position, int fd, NonnullRefPtr<Node> path)
2799 : PathRedirectionNode(move(position), fd, move(path))
2800{
2801}
2802
2803ReadRedirection::~ReadRedirection()
2804{
2805}
2806
2807ErrorOr<void> ReadWriteRedirection::dump(int level) const
2808{
2809 TRY(Node::dump(level));
2810 TRY(m_path->dump(level + 1));
2811 print_indented(level + 1, "To/From {}", m_fd);
2812 return {};
2813}
2814
2815ErrorOr<RefPtr<Value>> ReadWriteRedirection::run(RefPtr<Shell> shell)
2816{
2817 Command command;
2818 auto path_segments = TRY(TRY(m_path->run(shell))->resolve_as_list(shell));
2819 if (shell && shell->has_any_error())
2820 return make_ref_counted<ListValue>({});
2821
2822 StringBuilder builder;
2823 builder.join(' ', path_segments);
2824
2825 command.redirections.append(PathRedirection::create(builder.to_string().release_value_but_fixme_should_propagate_errors(), m_fd, PathRedirection::ReadWrite));
2826 return make_ref_counted<CommandValue>(move(command));
2827}
2828
2829ReadWriteRedirection::ReadWriteRedirection(Position position, int fd, NonnullRefPtr<Node> path)
2830 : PathRedirectionNode(move(position), fd, move(path))
2831{
2832}
2833
2834ReadWriteRedirection::~ReadWriteRedirection()
2835{
2836}
2837
2838ErrorOr<void> Sequence::dump(int level) const
2839{
2840 TRY(Node::dump(level));
2841 for (auto& entry : m_entries)
2842 TRY(entry->dump(level + 1));
2843 return {};
2844}
2845
2846ErrorOr<RefPtr<Value>> Sequence::run(RefPtr<Shell> shell)
2847{
2848 Vector<Command> all_commands;
2849 Command* last_command_in_sequence = nullptr;
2850 for (auto& entry : m_entries) {
2851 if (shell && shell->has_any_error())
2852 break;
2853 if (!last_command_in_sequence) {
2854 auto commands = TRY(entry->to_lazy_evaluated_commands(shell));
2855 all_commands.extend(move(commands));
2856 last_command_in_sequence = &all_commands.last();
2857 continue;
2858 }
2859
2860 if (last_command_in_sequence->should_wait) {
2861 last_command_in_sequence->next_chain.append(NodeWithAction { entry, NodeWithAction::Sequence });
2862 } else {
2863 all_commands.extend(TRY(entry->to_lazy_evaluated_commands(shell)));
2864 last_command_in_sequence = &all_commands.last();
2865 }
2866 }
2867
2868 return make_ref_counted<CommandSequenceValue>(move(all_commands));
2869}
2870
2871ErrorOr<void> Sequence::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2872{
2873 for (auto& entry : m_entries)
2874 TRY(entry->highlight_in_editor(editor, shell, metadata));
2875 return {};
2876}
2877
2878HitTestResult Sequence::hit_test_position(size_t offset) const
2879{
2880 for (auto& entry : m_entries) {
2881 auto result = entry->hit_test_position(offset);
2882 if (result.matching_node) {
2883 if (!result.closest_command_node)
2884 result.closest_command_node = entry;
2885 return result;
2886 }
2887 }
2888
2889 return {};
2890}
2891
2892RefPtr<Node const> Sequence::leftmost_trivial_literal() const
2893{
2894 for (auto& entry : m_entries) {
2895 if (auto node = entry->leftmost_trivial_literal())
2896 return node;
2897 }
2898 return nullptr;
2899}
2900
2901Sequence::Sequence(Position position, Vector<NonnullRefPtr<Node>> entries, Vector<Position> separator_positions)
2902 : Node(move(position))
2903 , m_entries(move(entries))
2904 , m_separator_positions(separator_positions)
2905{
2906 for (auto& entry : m_entries) {
2907 if (entry->is_syntax_error()) {
2908 set_is_syntax_error(entry->syntax_error_node());
2909 break;
2910 }
2911 }
2912}
2913
2914Sequence::~Sequence()
2915{
2916}
2917
2918ErrorOr<void> Subshell::dump(int level) const
2919{
2920 TRY(Node::dump(level));
2921 if (m_block)
2922 TRY(m_block->dump(level + 1));
2923 return {};
2924}
2925
2926ErrorOr<RefPtr<Value>> Subshell::run(RefPtr<Shell> shell)
2927{
2928 if (!m_block)
2929 return make_ref_counted<ListValue>({});
2930
2931 return make_ref_counted<AST::CommandSequenceValue>(TRY(m_block->to_lazy_evaluated_commands(shell)));
2932}
2933
2934ErrorOr<void> Subshell::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2935{
2936 metadata.is_first_in_list = true;
2937 if (m_block)
2938 TRY(m_block->highlight_in_editor(editor, shell, metadata));
2939 return {};
2940}
2941
2942HitTestResult Subshell::hit_test_position(size_t offset) const
2943{
2944 if (m_block)
2945 return m_block->hit_test_position(offset);
2946
2947 return {};
2948}
2949
2950Subshell::Subshell(Position position, RefPtr<Node> block)
2951 : Node(move(position))
2952 , m_block(block)
2953{
2954 if (m_block && m_block->is_syntax_error())
2955 set_is_syntax_error(m_block->syntax_error_node());
2956}
2957
2958Subshell::~Subshell()
2959{
2960}
2961
2962ErrorOr<void> Slice::dump(int level) const
2963{
2964 TRY(Node::dump(level));
2965 TRY(m_selector->dump(level + 1));
2966 return {};
2967}
2968
2969ErrorOr<RefPtr<Value>> Slice::run(RefPtr<Shell> shell)
2970{
2971 return m_selector->run(shell);
2972}
2973
2974ErrorOr<void> Slice::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
2975{
2976 return m_selector->highlight_in_editor(editor, shell, metadata);
2977}
2978
2979HitTestResult Slice::hit_test_position(size_t offset) const
2980{
2981 return m_selector->hit_test_position(offset);
2982}
2983
2984ErrorOr<Vector<Line::CompletionSuggestion>> Slice::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
2985{
2986 // TODO: Maybe intercept this, and suggest values in range?
2987 return m_selector->complete_for_editor(shell, offset, hit_test_result);
2988}
2989
2990Slice::Slice(Position position, NonnullRefPtr<AST::Node> selector)
2991 : Node(move(position))
2992 , m_selector(move(selector))
2993{
2994 if (m_selector->is_syntax_error())
2995 set_is_syntax_error(m_selector->syntax_error_node());
2996}
2997
2998Slice::~Slice()
2999{
3000}
3001
3002ErrorOr<void> SimpleVariable::dump(int level) const
3003{
3004 TRY(Node::dump(level));
3005 print_indented(level + 1, "(Name)");
3006 print_indented(level + 2, "{}", m_name);
3007 print_indented(level + 1, "(Slice)");
3008 if (m_slice)
3009 TRY(m_slice->dump(level + 2));
3010 else
3011 print_indented(level + 2, "(None)");
3012 return {};
3013}
3014
3015ErrorOr<RefPtr<Value>> SimpleVariable::run(RefPtr<Shell>)
3016{
3017 NonnullRefPtr<Value> value = make_ref_counted<SimpleVariableValue>(m_name);
3018 if (m_slice)
3019 value = value->with_slices(*m_slice).release_value_but_fixme_should_propagate_errors();
3020 return value;
3021}
3022
3023ErrorOr<void> SimpleVariable::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
3024{
3025 Line::Style style { Line::Style::Foreground(214, 112, 214) };
3026 if (metadata.is_first_in_list)
3027 style.unify_with({ Line::Style::Bold });
3028 editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style));
3029 if (m_slice)
3030 TRY(m_slice->highlight_in_editor(editor, shell, metadata));
3031 return {};
3032}
3033
3034HitTestResult SimpleVariable::hit_test_position(size_t offset) const
3035{
3036 if (!position().contains(offset))
3037 return {};
3038
3039 if (m_slice && m_slice->position().contains(offset))
3040 return m_slice->hit_test_position(offset);
3041
3042 return { this, this, nullptr };
3043}
3044
3045ErrorOr<Vector<Line::CompletionSuggestion>> SimpleVariable::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
3046{
3047 auto matching_node = hit_test_result.matching_node;
3048 if (!matching_node)
3049 return Vector<Line::CompletionSuggestion> {};
3050
3051 if (matching_node != this)
3052 return Vector<Line::CompletionSuggestion> {};
3053
3054 auto corrected_offset = offset - matching_node->position().start_offset - 1;
3055
3056 if (corrected_offset > m_name.bytes_as_string_view().length() + 1)
3057 return Vector<Line::CompletionSuggestion> {};
3058
3059 return shell.complete_variable(m_name, corrected_offset);
3060}
3061
3062SimpleVariable::SimpleVariable(Position position, String name)
3063 : VariableNode(move(position))
3064 , m_name(move(name))
3065{
3066}
3067
3068SimpleVariable::~SimpleVariable()
3069{
3070}
3071
3072ErrorOr<void> SpecialVariable::dump(int level) const
3073{
3074 TRY(Node::dump(level));
3075 print_indented(level + 1, "(Name)");
3076 print_indented(level + 1, "{:c}", m_name);
3077 print_indented(level + 1, "(Slice)");
3078 if (m_slice)
3079 TRY(m_slice->dump(level + 2));
3080 else
3081 print_indented(level + 2, "(None)");
3082 return {};
3083}
3084
3085ErrorOr<RefPtr<Value>> SpecialVariable::run(RefPtr<Shell>)
3086{
3087 NonnullRefPtr<Value> value = make_ref_counted<SpecialVariableValue>(m_name);
3088 if (m_slice)
3089 value = value->with_slices(*m_slice).release_value_but_fixme_should_propagate_errors();
3090 return value;
3091}
3092
3093ErrorOr<void> SpecialVariable::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
3094{
3095 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(214, 112, 214) });
3096 if (m_slice)
3097 TRY(m_slice->highlight_in_editor(editor, shell, metadata));
3098 return {};
3099}
3100
3101ErrorOr<Vector<Line::CompletionSuggestion>> SpecialVariable::complete_for_editor(Shell&, size_t, HitTestResult const&) const
3102{
3103 return Vector<Line::CompletionSuggestion> {};
3104}
3105
3106HitTestResult SpecialVariable::hit_test_position(size_t offset) const
3107{
3108 if (m_slice && m_slice->position().contains(offset))
3109 return m_slice->hit_test_position(offset);
3110
3111 return { this, this, nullptr };
3112}
3113
3114SpecialVariable::SpecialVariable(Position position, char name)
3115 : VariableNode(move(position))
3116 , m_name(name)
3117{
3118}
3119
3120SpecialVariable::~SpecialVariable()
3121{
3122}
3123
3124ErrorOr<void> Juxtaposition::dump(int level) const
3125{
3126 TRY(Node::dump(level));
3127 TRY(m_left->dump(level + 1));
3128 TRY(m_right->dump(level + 1));
3129 return {};
3130}
3131
3132ErrorOr<RefPtr<Value>> Juxtaposition::run(RefPtr<Shell> shell)
3133{
3134 auto left_value = TRY(TRY(m_left->run(shell))->resolve_without_cast(shell));
3135 if (shell && shell->has_any_error())
3136 return make_ref_counted<ListValue>({});
3137
3138 auto right_value = TRY(TRY(m_right->run(shell))->resolve_without_cast(shell));
3139 if (shell && shell->has_any_error())
3140 return make_ref_counted<ListValue>({});
3141
3142 auto left = left_value->resolve_as_list(shell).release_value_but_fixme_should_propagate_errors();
3143 auto right = right_value->resolve_as_list(shell).release_value_but_fixme_should_propagate_errors();
3144
3145 if (m_mode == Mode::StringExpand) {
3146 Vector<String> result;
3147 result.ensure_capacity(left.size() + right.size());
3148
3149 for (auto& left_item : left)
3150 result.append(left_item);
3151
3152 if (!result.is_empty() && !right.is_empty()) {
3153 auto& last = result.last();
3154 last = String::formatted("{}{}", last, right.first()).release_value_but_fixme_should_propagate_errors();
3155 right.take_first();
3156 }
3157 for (auto& right_item : right)
3158 result.append(right_item);
3159
3160 return make_ref_counted<ListValue>(move(result));
3161 }
3162
3163 if (left_value->is_string() && right_value->is_string()) {
3164
3165 VERIFY(left.size() == 1);
3166 VERIFY(right.size() == 1);
3167
3168 StringBuilder builder;
3169 builder.append(left[0]);
3170 builder.append(right[0]);
3171
3172 return make_ref_counted<StringValue>(builder.to_string().release_value_but_fixme_should_propagate_errors());
3173 }
3174
3175 // Otherwise, treat them as lists and create a list product (or just append).
3176 if (left.is_empty() || right.is_empty())
3177 return make_ref_counted<ListValue>({});
3178
3179 Vector<String> result;
3180 result.ensure_capacity(left.size() * right.size());
3181
3182 StringBuilder builder;
3183 for (auto& left_element : left) {
3184 for (auto& right_element : right) {
3185 builder.append(left_element);
3186 builder.append(right_element);
3187 result.append(builder.to_string().release_value_but_fixme_should_propagate_errors());
3188 builder.clear();
3189 }
3190 }
3191
3192 return make_ref_counted<ListValue>(move(result));
3193}
3194
3195ErrorOr<void> Juxtaposition::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
3196{
3197 TRY(m_left->highlight_in_editor(editor, shell, metadata));
3198
3199 // '~/foo/bar' is special, we have to actually resolve the tilde
3200 // since that resolution is a pure operation, we can just go ahead
3201 // and do it to get the value :)
3202 if (m_right->is_bareword() && m_left->is_tilde()) {
3203 auto tilde_value = TRY(TRY(m_left->run(shell))->resolve_as_list(shell))[0];
3204 auto bareword_value = TRY(TRY(m_right->run(shell))->resolve_as_list(shell))[0];
3205
3206 StringBuilder path_builder;
3207 path_builder.append(tilde_value);
3208 path_builder.append('/');
3209 path_builder.append(bareword_value);
3210 auto path = path_builder.to_deprecated_string();
3211
3212 if (Core::DeprecatedFile::exists(path)) {
3213 auto realpath = shell.resolve_path(path);
3214 auto url = URL::create_with_file_scheme(realpath);
3215 url.set_host(shell.hostname);
3216 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Hyperlink(url.to_deprecated_string()) });
3217 }
3218
3219 } else {
3220 TRY(m_right->highlight_in_editor(editor, shell, metadata));
3221 }
3222
3223 return {};
3224}
3225
3226ErrorOr<Vector<Line::CompletionSuggestion>> Juxtaposition::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
3227{
3228 auto matching_node = hit_test_result.matching_node;
3229 if (m_left->would_execute() || m_right->would_execute()) {
3230 return Vector<Line::CompletionSuggestion> {};
3231 }
3232
3233 // '~/foo/bar' is special, we have to actually resolve the tilde
3234 // then complete the bareword with that path prefix.
3235 auto left_values = TRY(TRY(m_left->run(shell))->resolve_as_list(shell));
3236
3237 if (left_values.is_empty())
3238 return m_right->complete_for_editor(shell, offset, hit_test_result);
3239
3240 auto& left_value = left_values.first();
3241
3242 auto right_values = TRY(TRY(m_right->run(shell))->resolve_as_list(shell));
3243 StringView right_value {};
3244
3245 auto corrected_offset = offset - matching_node->position().start_offset;
3246
3247 if (!right_values.is_empty())
3248 right_value = right_values.first();
3249
3250 if (m_left->is_tilde() && !right_value.is_empty()) {
3251 right_value = right_value.substring_view(1);
3252 corrected_offset--;
3253 }
3254
3255 if (corrected_offset > right_value.length())
3256 return Vector<Line::CompletionSuggestion> {};
3257
3258 return shell.complete_path(left_value, right_value, corrected_offset, Shell::ExecutableOnly::No, hit_test_result.closest_command_node.ptr(), hit_test_result.matching_node);
3259}
3260
3261HitTestResult Juxtaposition::hit_test_position(size_t offset) const
3262{
3263 auto result = m_left->hit_test_position(offset);
3264 if (!result.closest_node_with_semantic_meaning)
3265 result.closest_node_with_semantic_meaning = this;
3266 if (result.matching_node)
3267 return result;
3268
3269 result = m_right->hit_test_position(offset);
3270 if (!result.closest_node_with_semantic_meaning)
3271 result.closest_node_with_semantic_meaning = this;
3272 return result;
3273}
3274
3275Juxtaposition::Juxtaposition(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right, Juxtaposition::Mode mode)
3276 : Node(move(position))
3277 , m_left(move(left))
3278 , m_right(move(right))
3279 , m_mode(mode)
3280{
3281 if (m_left->is_syntax_error())
3282 set_is_syntax_error(m_left->syntax_error_node());
3283 else if (m_right->is_syntax_error())
3284 set_is_syntax_error(m_right->syntax_error_node());
3285}
3286
3287Juxtaposition::~Juxtaposition()
3288{
3289}
3290
3291ErrorOr<void> StringLiteral::dump(int level) const
3292{
3293 TRY(Node::dump(level));
3294 print_indented(level + 1, "{}", m_text);
3295 return {};
3296}
3297
3298ErrorOr<RefPtr<Value>> StringLiteral::run(RefPtr<Shell>)
3299{
3300 return make_ref_counted<StringValue>(m_text);
3301}
3302
3303ErrorOr<void> StringLiteral::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata metadata)
3304{
3305 if (m_text.is_empty())
3306 return {};
3307
3308 Line::Style style { Line::Style::Foreground(Line::Style::XtermColor::Yellow) };
3309 if (metadata.is_first_in_list)
3310 style.unify_with({ Line::Style::Bold });
3311 editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style));
3312
3313 return {};
3314}
3315
3316StringLiteral::StringLiteral(Position position, String text, EnclosureType enclosure_type)
3317 : Node(move(position))
3318 , m_text(move(text))
3319 , m_enclosure_type(enclosure_type)
3320{
3321}
3322
3323StringLiteral::~StringLiteral()
3324{
3325}
3326
3327ErrorOr<void> StringPartCompose::dump(int level) const
3328{
3329 TRY(Node::dump(level));
3330 TRY(m_left->dump(level + 1));
3331 TRY(m_right->dump(level + 1));
3332 return {};
3333}
3334
3335ErrorOr<RefPtr<Value>> StringPartCompose::run(RefPtr<Shell> shell)
3336{
3337 auto left = TRY(TRY(m_left->run(shell))->resolve_as_list(shell));
3338 if (shell && shell->has_any_error())
3339 return make_ref_counted<ListValue>({});
3340
3341 auto right = TRY(TRY(m_right->run(shell))->resolve_as_list(shell));
3342 if (shell && shell->has_any_error())
3343 return make_ref_counted<ListValue>({});
3344
3345 StringBuilder builder;
3346 builder.join(' ', left);
3347 builder.join(' ', right);
3348
3349 return make_ref_counted<StringValue>(builder.to_string().release_value_but_fixme_should_propagate_errors());
3350}
3351
3352ErrorOr<void> StringPartCompose::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
3353{
3354 TRY(m_left->highlight_in_editor(editor, shell, metadata));
3355 return m_right->highlight_in_editor(editor, shell, metadata);
3356}
3357
3358HitTestResult StringPartCompose::hit_test_position(size_t offset) const
3359{
3360 auto result = m_left->hit_test_position(offset);
3361 if (result.matching_node)
3362 return result;
3363 return m_right->hit_test_position(offset);
3364}
3365
3366StringPartCompose::StringPartCompose(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right)
3367 : Node(move(position))
3368 , m_left(move(left))
3369 , m_right(move(right))
3370{
3371 if (m_left->is_syntax_error())
3372 set_is_syntax_error(m_left->syntax_error_node());
3373 else if (m_right->is_syntax_error())
3374 set_is_syntax_error(m_right->syntax_error_node());
3375}
3376
3377StringPartCompose::~StringPartCompose()
3378{
3379}
3380
3381ErrorOr<void> SyntaxError::dump(int level) const
3382{
3383 TRY(Node::dump(level));
3384 print_indented(level + 1, "(Error text)");
3385 print_indented(level + 2, "{}", m_syntax_error_text);
3386 print_indented(level + 1, "(Can be recovered from)");
3387 print_indented(level + 2, "{}", m_is_continuable);
3388 return {};
3389}
3390
3391ErrorOr<RefPtr<Value>> SyntaxError::run(RefPtr<Shell> shell)
3392{
3393 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, m_syntax_error_text.to_deprecated_string(), position());
3394 return make_ref_counted<StringValue>(String {});
3395}
3396
3397ErrorOr<void> SyntaxError::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata)
3398{
3399 editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Red), Line::Style::Bold });
3400 return {};
3401}
3402
3403SyntaxError::SyntaxError(Position position, String error, bool is_continuable)
3404 : Node(move(position))
3405 , m_syntax_error_text(move(error))
3406 , m_is_continuable(is_continuable)
3407{
3408}
3409
3410SyntaxError& SyntaxError::syntax_error_node()
3411{
3412 return *this;
3413}
3414
3415SyntaxError::~SyntaxError()
3416{
3417}
3418
3419ErrorOr<void> SyntheticNode::dump(int level) const
3420{
3421 TRY(Node::dump(level));
3422 return {};
3423}
3424
3425ErrorOr<RefPtr<Value>> SyntheticNode::run(RefPtr<Shell>)
3426{
3427 return m_value;
3428}
3429
3430ErrorOr<void> SyntheticNode::highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata)
3431{
3432 return {};
3433}
3434
3435SyntheticNode::SyntheticNode(Position position, NonnullRefPtr<Value> value)
3436 : Node(move(position))
3437 , m_value(move(value))
3438{
3439}
3440
3441ErrorOr<void> Tilde::dump(int level) const
3442{
3443 TRY(Node::dump(level));
3444 print_indented(level + 1, "{}", m_username);
3445 return {};
3446}
3447
3448ErrorOr<RefPtr<Value>> Tilde::run(RefPtr<Shell>)
3449{
3450 return make_ref_counted<TildeValue>(m_username);
3451}
3452
3453ErrorOr<void> Tilde::highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata)
3454{
3455 return {};
3456}
3457
3458HitTestResult Tilde::hit_test_position(size_t offset) const
3459{
3460 if (!position().contains(offset))
3461 return {};
3462
3463 return { this, this, nullptr };
3464}
3465
3466ErrorOr<Vector<Line::CompletionSuggestion>> Tilde::complete_for_editor(Shell& shell, size_t offset, HitTestResult const& hit_test_result) const
3467{
3468 auto matching_node = hit_test_result.matching_node;
3469 if (!matching_node)
3470 return Vector<Line::CompletionSuggestion> {};
3471
3472 if (matching_node != this)
3473 return Vector<Line::CompletionSuggestion> {};
3474
3475 auto corrected_offset = offset - matching_node->position().start_offset - 1;
3476
3477 if (corrected_offset > m_username.bytes_as_string_view().length() + 1)
3478 return Vector<Line::CompletionSuggestion> {};
3479
3480 return shell.complete_user(m_username, corrected_offset);
3481}
3482
3483String Tilde::text() const
3484{
3485 StringBuilder builder;
3486 builder.append('~');
3487 builder.append(m_username);
3488 return builder.to_string().release_value_but_fixme_should_propagate_errors();
3489}
3490
3491Tilde::Tilde(Position position, String username)
3492 : Node(move(position))
3493 , m_username(move(username))
3494{
3495}
3496
3497Tilde::~Tilde()
3498{
3499}
3500
3501ErrorOr<void> WriteAppendRedirection::dump(int level) const
3502{
3503 TRY(Node::dump(level));
3504 TRY(m_path->dump(level + 1));
3505 print_indented(level + 1, "From {}", m_fd);
3506 return {};
3507}
3508
3509ErrorOr<RefPtr<Value>> WriteAppendRedirection::run(RefPtr<Shell> shell)
3510{
3511 Command command;
3512 auto path_segments = TRY(TRY(m_path->run(shell))->resolve_as_list(shell));
3513 if (shell && shell->has_any_error())
3514 return make_ref_counted<ListValue>({});
3515
3516 StringBuilder builder;
3517 builder.join(' ', path_segments);
3518
3519 command.redirections.append(PathRedirection::create(builder.to_string().release_value_but_fixme_should_propagate_errors(), m_fd, PathRedirection::WriteAppend));
3520 return make_ref_counted<CommandValue>(move(command));
3521}
3522
3523WriteAppendRedirection::WriteAppendRedirection(Position position, int fd, NonnullRefPtr<Node> path)
3524 : PathRedirectionNode(move(position), fd, move(path))
3525{
3526}
3527
3528WriteAppendRedirection::~WriteAppendRedirection()
3529{
3530}
3531
3532ErrorOr<void> WriteRedirection::dump(int level) const
3533{
3534 TRY(Node::dump(level));
3535 TRY(m_path->dump(level + 1));
3536 print_indented(level + 1, "From {}", m_fd);
3537 return {};
3538}
3539
3540ErrorOr<RefPtr<Value>> WriteRedirection::run(RefPtr<Shell> shell)
3541{
3542 Command command;
3543 auto path_segments = TRY(TRY(m_path->run(shell))->resolve_as_list(shell));
3544 if (shell && shell->has_any_error())
3545 return make_ref_counted<ListValue>({});
3546
3547 StringBuilder builder;
3548 builder.join(' ', path_segments);
3549
3550 command.redirections.append(PathRedirection::create(builder.to_string().release_value_but_fixme_should_propagate_errors(), m_fd, PathRedirection::Write));
3551 return make_ref_counted<CommandValue>(move(command));
3552}
3553
3554WriteRedirection::WriteRedirection(Position position, int fd, NonnullRefPtr<Node> path)
3555 : PathRedirectionNode(move(position), fd, move(path))
3556{
3557}
3558
3559WriteRedirection::~WriteRedirection()
3560{
3561}
3562
3563ErrorOr<void> VariableDeclarations::dump(int level) const
3564{
3565 TRY(Node::dump(level));
3566 for (auto& var : m_variables) {
3567 print_indented(level + 1, "Set");
3568 TRY(var.name->dump(level + 2));
3569 TRY(var.value->dump(level + 2));
3570 }
3571 return {};
3572}
3573
3574ErrorOr<RefPtr<Value>> VariableDeclarations::run(RefPtr<Shell> shell)
3575{
3576 for (auto& var : m_variables) {
3577 auto name_value = TRY(TRY(var.name->run(shell))->resolve_as_list(shell));
3578 if (shell && shell->has_any_error())
3579 break;
3580
3581 VERIFY(name_value.size() == 1);
3582 auto name = name_value[0];
3583 auto value = TRY(var.value->run(shell));
3584 if (shell && shell->has_any_error())
3585 break;
3586 value = TRY(value->resolve_without_cast(shell));
3587
3588 shell->set_local_variable(name.to_deprecated_string(), value.release_nonnull());
3589 }
3590
3591 return make_ref_counted<ListValue>({});
3592}
3593
3594ErrorOr<void> VariableDeclarations::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
3595{
3596 metadata.is_first_in_list = false;
3597 for (auto& var : m_variables) {
3598 TRY(var.name->highlight_in_editor(editor, shell, metadata));
3599 // Highlight the '='.
3600 editor.stylize({ var.name->position().end_offset - 1, var.name->position().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue) });
3601 TRY(var.value->highlight_in_editor(editor, shell, metadata));
3602 }
3603
3604 return {};
3605}
3606
3607HitTestResult VariableDeclarations::hit_test_position(size_t offset) const
3608{
3609 for (auto decl : m_variables) {
3610 auto result = decl.value->hit_test_position(offset);
3611 if (result.matching_node)
3612 return result;
3613 }
3614
3615 return { nullptr, nullptr, nullptr };
3616}
3617
3618VariableDeclarations::VariableDeclarations(Position position, Vector<Variable> variables)
3619 : Node(move(position))
3620 , m_variables(move(variables))
3621{
3622 for (auto& decl : m_variables) {
3623 if (decl.name->is_syntax_error()) {
3624 set_is_syntax_error(decl.name->syntax_error_node());
3625 break;
3626 }
3627 if (decl.value->is_syntax_error()) {
3628 set_is_syntax_error(decl.value->syntax_error_node());
3629 break;
3630 }
3631 }
3632}
3633
3634VariableDeclarations::~VariableDeclarations()
3635{
3636}
3637
3638Value::~Value()
3639{
3640}
3641
3642ErrorOr<String> Value::resolve_as_string(RefPtr<Shell> shell)
3643{
3644 if (shell)
3645 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Conversion to string not allowed");
3646 return String {};
3647}
3648
3649ErrorOr<Vector<AST::Command>> Value::resolve_as_commands(RefPtr<Shell> shell)
3650{
3651 Command command;
3652 command.argv = TRY(resolve_as_list(shell));
3653 return Vector { move(command) };
3654}
3655
3656ListValue::ListValue(Vector<String> values)
3657{
3658 if (values.is_empty())
3659 return;
3660 m_contained_values.ensure_capacity(values.size());
3661 for (auto& str : values)
3662 m_contained_values.append(adopt_ref(*new StringValue(move(str))));
3663}
3664
3665ErrorOr<NonnullRefPtr<Value>> Value::with_slices(NonnullRefPtr<Slice> slice) const&
3666{
3667 auto value = TRY(clone());
3668 value->m_slices.append(move(slice));
3669 return value;
3670}
3671
3672ErrorOr<NonnullRefPtr<Value>> Value::with_slices(Vector<NonnullRefPtr<Slice>> slices) const&
3673{
3674 auto value = TRY(clone());
3675 value->m_slices.extend(move(slices));
3676 return value;
3677}
3678
3679ListValue::~ListValue()
3680{
3681}
3682
3683ErrorOr<Vector<String>> ListValue::resolve_as_list(RefPtr<Shell> shell)
3684{
3685 Vector<String> values;
3686 for (auto& value : m_contained_values)
3687 values.extend(TRY(value->resolve_as_list(shell)));
3688
3689 return resolve_slices(shell, move(values), m_slices);
3690}
3691
3692ErrorOr<NonnullRefPtr<Value>> ListValue::resolve_without_cast(RefPtr<Shell> shell)
3693{
3694 Vector<NonnullRefPtr<Value>> values;
3695 for (auto& value : m_contained_values)
3696 values.append(TRY(value->resolve_without_cast(shell)));
3697
3698 NonnullRefPtr<Value> value = make_ref_counted<ListValue>(move(values));
3699 if (!m_slices.is_empty())
3700 value = TRY(value->with_slices(m_slices));
3701 return value;
3702}
3703
3704CommandValue::~CommandValue()
3705{
3706}
3707
3708CommandSequenceValue::~CommandSequenceValue()
3709{
3710}
3711
3712ErrorOr<Vector<String>> CommandSequenceValue::resolve_as_list(RefPtr<Shell> shell)
3713{
3714 shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Unexpected cast of a command sequence to a list");
3715 return Vector<String> {};
3716}
3717
3718ErrorOr<Vector<Command>> CommandSequenceValue::resolve_as_commands(RefPtr<Shell>)
3719{
3720 return m_contained_values;
3721}
3722
3723ErrorOr<Vector<String>> CommandValue::resolve_as_list(RefPtr<Shell>)
3724{
3725 return m_command.argv;
3726}
3727
3728ErrorOr<Vector<Command>> CommandValue::resolve_as_commands(RefPtr<Shell>)
3729{
3730 return Vector { m_command };
3731}
3732
3733JobValue::~JobValue()
3734{
3735}
3736
3737StringValue::~StringValue()
3738{
3739}
3740
3741ErrorOr<String> StringValue::resolve_as_string(RefPtr<Shell> shell)
3742{
3743 if (m_split.is_empty())
3744 return TRY(resolve_slices(shell, String { m_string }, m_slices));
3745 return Value::resolve_as_string(shell);
3746}
3747
3748ErrorOr<Vector<String>> StringValue::resolve_as_list(RefPtr<Shell> shell)
3749{
3750 if (is_list()) {
3751 auto parts = StringView(m_string).split_view(m_split, m_keep_empty ? SplitBehavior::KeepEmpty : SplitBehavior::Nothing);
3752 Vector<String> result;
3753 result.ensure_capacity(parts.size());
3754 for (auto& part : parts)
3755 result.append(TRY(String::from_utf8(part)));
3756 return resolve_slices(shell, move(result), m_slices);
3757 }
3758
3759 return Vector<String> { TRY(resolve_slices(shell, String { m_string }, m_slices)) };
3760}
3761
3762ErrorOr<NonnullRefPtr<Value>> StringValue::resolve_without_cast(RefPtr<Shell> shell)
3763{
3764 if (is_list())
3765 return try_make_ref_counted<AST::ListValue>(TRY(resolve_as_list(shell))); // No need to reapply the slices.
3766
3767 return *this;
3768}
3769
3770GlobValue::~GlobValue()
3771{
3772}
3773
3774ErrorOr<Vector<String>> GlobValue::resolve_as_list(RefPtr<Shell> shell)
3775{
3776 if (!shell)
3777 return resolve_slices(shell, Vector { m_glob }, m_slices);
3778
3779 auto results = shell->expand_globs(m_glob, shell->cwd);
3780 if (results.is_empty())
3781 shell->raise_error(Shell::ShellError::InvalidGlobError, "Glob did not match anything!", m_generation_position);
3782
3783 Vector<String> strings;
3784 TRY(strings.try_ensure_capacity(results.size()));
3785 for (auto& entry : results) {
3786 TRY(strings.try_append(TRY(String::from_utf8(entry))));
3787 }
3788
3789 return resolve_slices(shell, move(strings), m_slices);
3790}
3791
3792SimpleVariableValue::~SimpleVariableValue()
3793{
3794}
3795
3796ErrorOr<String> SimpleVariableValue::resolve_as_string(RefPtr<Shell> shell)
3797{
3798 if (!shell)
3799 return resolve_slices(shell, String {}, m_slices);
3800
3801 if (auto value = TRY(resolve_without_cast(shell)); value != this)
3802 return resolve_slices(shell, TRY(value->resolve_as_string(shell)), m_slices);
3803
3804 auto name = m_name.to_deprecated_string();
3805 char* env_value = getenv(name.characters());
3806 return resolve_slices(shell, TRY(String::from_utf8(StringView { env_value, strlen(env_value) })), m_slices);
3807}
3808
3809ErrorOr<Vector<String>> SimpleVariableValue::resolve_as_list(RefPtr<Shell> shell)
3810{
3811 if (!shell)
3812 return resolve_slices(shell, Vector<String> {}, m_slices);
3813
3814 if (auto value = TRY(resolve_without_cast(shell)); value != this)
3815 return value->resolve_as_list(shell);
3816
3817 auto name = m_name.to_deprecated_string();
3818 char* env_value = getenv(name.characters());
3819 if (env_value == nullptr)
3820 return { resolve_slices(shell, Vector { String {} }, m_slices) };
3821
3822 return Vector<String> { TRY(resolve_slices(shell, TRY(String::from_utf8(StringView { env_value, strlen(env_value) })), m_slices)) };
3823}
3824
3825ErrorOr<NonnullRefPtr<Value>> SimpleVariableValue::resolve_without_cast(RefPtr<Shell> shell)
3826{
3827 VERIFY(shell);
3828
3829 if (auto value = TRY(shell->lookup_local_variable(m_name))) {
3830 auto result = value.release_nonnull();
3831 // If a slice is applied, add it.
3832 if (!m_slices.is_empty())
3833 result = TRY(result->with_slices(m_slices));
3834
3835 return const_cast<Value&>(*result);
3836 }
3837
3838 return *this;
3839}
3840
3841SpecialVariableValue::~SpecialVariableValue()
3842{
3843}
3844
3845ErrorOr<String> SpecialVariableValue::resolve_as_string(RefPtr<Shell> shell)
3846{
3847 if (!shell)
3848 return String {};
3849
3850 auto result = TRY(resolve_as_list(shell));
3851 if (result.size() == 1)
3852 return result[0];
3853
3854 if (result.is_empty())
3855 return String {};
3856
3857 return Value::resolve_as_string(shell);
3858}
3859
3860ErrorOr<Vector<String>> SpecialVariableValue::resolve_as_list(RefPtr<Shell> shell)
3861{
3862 if (!shell)
3863 return Vector<String> {};
3864
3865 switch (m_name) {
3866 case '?':
3867 return { resolve_slices(shell, Vector { TRY(String::number(shell->last_return_code.value_or(0))) }, m_slices) };
3868 case '$':
3869 return { resolve_slices(shell, Vector { TRY(String::number(getpid())) }, m_slices) };
3870 case '*':
3871 if (auto argv = TRY(shell->lookup_local_variable("ARGV"sv)))
3872 return resolve_slices(shell, TRY(const_cast<Value&>(*argv).resolve_as_list(shell)), m_slices);
3873 return resolve_slices(shell, Vector<String> {}, m_slices);
3874 case '#':
3875 if (auto argv = TRY(shell->lookup_local_variable("ARGV"sv))) {
3876 if (argv->is_list()) {
3877 auto list_argv = static_cast<AST::ListValue const*>(argv.ptr());
3878 return { resolve_slices(shell, Vector { TRY(String::number(list_argv->values().size())) }, m_slices) };
3879 }
3880 return { resolve_slices(shell, Vector { "1"_short_string }, m_slices) };
3881 }
3882 return { resolve_slices(shell, Vector { "0"_short_string }, m_slices) };
3883 default:
3884 return { resolve_slices(shell, Vector { String {} }, m_slices) };
3885 }
3886}
3887
3888ErrorOr<NonnullRefPtr<Value>> SpecialVariableValue::resolve_without_cast(RefPtr<Shell> shell)
3889{
3890 if (!shell)
3891 return *this;
3892
3893 return try_make_ref_counted<ListValue>(TRY(resolve_as_list(shell)));
3894}
3895
3896TildeValue::~TildeValue()
3897{
3898}
3899
3900ErrorOr<String> TildeValue::resolve_as_string(RefPtr<Shell> shell)
3901{
3902 return TRY(resolve_as_list(shell)).first();
3903}
3904
3905ErrorOr<Vector<String>> TildeValue::resolve_as_list(RefPtr<Shell> shell)
3906{
3907 StringBuilder builder;
3908 builder.append('~');
3909 builder.append(m_username);
3910
3911 if (!shell)
3912 return { resolve_slices(shell, Vector { TRY(builder.to_string()) }, m_slices) };
3913
3914 return { resolve_slices(shell, Vector { TRY(String::from_utf8(shell->expand_tilde(builder.to_deprecated_string()))) }, m_slices) };
3915}
3916
3917ErrorOr<NonnullRefPtr<Rewiring>> CloseRedirection::apply() const
3918{
3919 return adopt_nonnull_ref_or_enomem(new (nothrow) Rewiring(fd, fd, Rewiring::Close::ImmediatelyCloseNew));
3920}
3921
3922CloseRedirection::~CloseRedirection()
3923{
3924}
3925
3926ErrorOr<NonnullRefPtr<Rewiring>> PathRedirection::apply() const
3927{
3928 auto check_fd_and_return = [my_fd = this->fd](int fd, String const& path) -> ErrorOr<NonnullRefPtr<Rewiring>> {
3929 if (fd < 0) {
3930 auto error = Error::from_errno(errno);
3931 dbgln("open() failed for '{}' with {}", path, error);
3932 return error;
3933 }
3934 return adopt_nonnull_ref_or_enomem(new (nothrow) Rewiring(fd, my_fd, Rewiring::Close::Old));
3935 };
3936
3937 auto path_string = path.to_deprecated_string();
3938 switch (direction) {
3939 case AST::PathRedirection::WriteAppend:
3940 return check_fd_and_return(open(path_string.characters(), O_WRONLY | O_CREAT | O_APPEND, 0666), path);
3941
3942 case AST::PathRedirection::Write:
3943 return check_fd_and_return(open(path_string.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666), path);
3944
3945 case AST::PathRedirection::Read:
3946 return check_fd_and_return(open(path_string.characters(), O_RDONLY), path);
3947
3948 case AST::PathRedirection::ReadWrite:
3949 return check_fd_and_return(open(path_string.characters(), O_RDWR | O_CREAT, 0666), path);
3950 }
3951
3952 VERIFY_NOT_REACHED();
3953}
3954
3955PathRedirection::~PathRedirection()
3956{
3957}
3958
3959FdRedirection::~FdRedirection()
3960{
3961}
3962
3963Redirection::~Redirection()
3964{
3965}
3966
3967}