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