Serenity Operating System
at master 915 lines 25 kB view raw
1/* 2 * Copyright (c) 2020, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "Formatter.h" 8#include "AST.h" 9#include "Parser.h" 10#include "PosixParser.h" 11#include <AK/Hex.h> 12#include <AK/ScopedValueRollback.h> 13#include <AK/TemporaryChange.h> 14 15namespace Shell { 16 17DeprecatedString Formatter::format() 18{ 19 auto node = m_root_node ?: (m_parse_as_posix ? Posix::Parser(m_source).parse() : Parser(m_source).parse()); 20 if (m_cursor >= 0) 21 m_output_cursor = m_cursor; 22 23 if (!node) 24 return DeprecatedString(); 25 26 if (node->is_syntax_error()) 27 return m_source; 28 29 if (m_cursor >= 0) { 30 auto hit_test = node->hit_test_position(m_cursor); 31 if (hit_test.matching_node) 32 m_hit_node = hit_test.matching_node.ptr(); 33 else 34 m_hit_node = nullptr; 35 } 36 37 m_parent_node = nullptr; 38 39 node->visit(*this); 40 41 VERIFY(m_builders.size() == 1); 42 43 auto string = current_builder().string_view(); 44 45 if (!string.ends_with(' ')) 46 current_builder().append(m_trivia); 47 48 return current_builder().to_deprecated_string(); 49} 50 51void Formatter::with_added_indent(int indent, Function<void()> callback) 52{ 53 TemporaryChange indent_change { m_current_indent, m_current_indent + indent }; 54 callback(); 55} 56 57void Formatter::in_new_block(Function<void()> callback) 58{ 59 current_builder().append('{'); 60 61 with_added_indent(1, [&] { 62 insert_separator(); 63 callback(); 64 }); 65 66 insert_separator(); 67 current_builder().append('}'); 68} 69 70DeprecatedString Formatter::in_new_builder(Function<void()> callback, StringBuilder new_builder) 71{ 72 m_builders.append(move(new_builder)); 73 callback(); 74 return m_builders.take_last().to_deprecated_string(); 75} 76 77void Formatter::test_and_update_output_cursor(const AST::Node* node) 78{ 79 if (!node) 80 return; 81 82 if (node != m_hit_node) 83 return; 84 85 m_output_cursor = current_builder().length() + m_cursor - node->position().start_offset; 86} 87 88void Formatter::visited(const AST::Node* node) 89{ 90 m_last_visited_node = node; 91} 92 93void Formatter::will_visit(const AST::Node* node) 94{ 95 if (!m_last_visited_node) 96 return; 97 98 if (!node) 99 return; 100 101 auto direct_sequence_child = !m_parent_node || m_parent_node->kind() == AST::Node::Kind::Sequence; 102 103 if (direct_sequence_child && node->kind() != AST::Node::Kind::Sequence && node->kind() != AST::Node::Kind::Execute) { 104 // Collapse more than one empty line to a single one. 105 if (node->position().start_line.line_number - m_last_visited_node->position().end_line.line_number > 1) 106 insert_separator(); 107 } 108} 109 110void Formatter::insert_separator(bool escaped) 111{ 112 if (escaped) 113 current_builder().append('\\'); 114 current_builder().append('\n'); 115 if (!escaped && !m_heredocs_to_append_after_sequence.is_empty()) { 116 for (auto& entry : m_heredocs_to_append_after_sequence) { 117 current_builder().append(entry); 118 } 119 m_heredocs_to_append_after_sequence.clear(); 120 } 121 insert_indent(); 122} 123 124void Formatter::insert_indent() 125{ 126 for (size_t i = 0; i < m_current_indent; ++i) 127 current_builder().append(" "sv); 128} 129 130void Formatter::visit(const AST::PathRedirectionNode* node) 131{ 132 will_visit(node); 133 test_and_update_output_cursor(node); 134 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 135 NodeVisitor::visit(node); 136 visited(node); 137} 138 139void Formatter::visit(const AST::And* node) 140{ 141 will_visit(node); 142 test_and_update_output_cursor(node); 143 auto should_indent = m_parent_node && m_parent_node->kind() != AST::Node::Kind::And; 144 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 145 146 with_added_indent(should_indent ? 1 : 0, [&] { 147 node->left()->visit(*this); 148 149 current_builder().append(' '); 150 insert_separator(true); 151 current_builder().append("&& "sv); 152 153 node->right()->visit(*this); 154 }); 155 visited(node); 156} 157 158void Formatter::visit(const AST::ListConcatenate* node) 159{ 160 will_visit(node); 161 test_and_update_output_cursor(node); 162 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 163 164 auto first = true; 165 for (auto& subnode : node->list()) { 166 if (!first) 167 current_builder().append(' '); 168 first = false; 169 subnode->visit(*this); 170 } 171 visited(node); 172} 173 174void Formatter::visit(const AST::Background* node) 175{ 176 will_visit(node); 177 test_and_update_output_cursor(node); 178 179 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 180 NodeVisitor::visit(node); 181 current_builder().append(" &"sv); 182 visited(node); 183} 184 185void Formatter::visit(const AST::BarewordLiteral* node) 186{ 187 will_visit(node); 188 test_and_update_output_cursor(node); 189 current_builder().append(node->text()); 190 visited(node); 191} 192 193void Formatter::visit(const AST::BraceExpansion* node) 194{ 195 will_visit(node); 196 test_and_update_output_cursor(node); 197 if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice) 198 current_builder().append('{'); 199 200 { 201 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 202 bool first = true; 203 for (auto& entry : node->entries()) { 204 if (!first) 205 current_builder().append(','); 206 first = false; 207 entry->visit(*this); 208 } 209 } 210 211 if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice) 212 current_builder().append('}'); 213 visited(node); 214} 215 216void Formatter::visit(const AST::CastToCommand* node) 217{ 218 will_visit(node); 219 test_and_update_output_cursor(node); 220 221 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 222 NodeVisitor::visit(node); 223 224 visited(node); 225} 226 227void Formatter::visit(const AST::CastToList* node) 228{ 229 will_visit(node); 230 test_and_update_output_cursor(node); 231 current_builder().append('('); 232 233 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 234 NodeVisitor::visit(node); 235 236 current_builder().append(')'); 237 visited(node); 238} 239 240void Formatter::visit(const AST::CloseFdRedirection* node) 241{ 242 will_visit(node); 243 test_and_update_output_cursor(node); 244 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 245 246 current_builder().appendff("{}>&-", node->fd()); 247 visited(node); 248} 249 250void Formatter::visit(const AST::CommandLiteral*) 251{ 252 VERIFY_NOT_REACHED(); 253} 254 255void Formatter::visit(const AST::Comment* node) 256{ 257 will_visit(node); 258 test_and_update_output_cursor(node); 259 current_builder().append("#"sv); 260 current_builder().append(node->text()); 261 visited(node); 262} 263 264void Formatter::visit(const AST::ContinuationControl* node) 265{ 266 will_visit(node); 267 test_and_update_output_cursor(node); 268 if (node->continuation_kind() == AST::ContinuationControl::Break) 269 current_builder().append("break"sv); 270 else if (node->continuation_kind() == AST::ContinuationControl::Continue) 271 current_builder().append("continue"sv); 272 else 273 VERIFY_NOT_REACHED(); 274 visited(node); 275} 276 277void Formatter::visit(const AST::DynamicEvaluate* node) 278{ 279 will_visit(node); 280 test_and_update_output_cursor(node); 281 current_builder().append('$'); 282 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 283 NodeVisitor::visit(node); 284 visited(node); 285} 286 287void Formatter::visit(const AST::DoubleQuotedString* node) 288{ 289 will_visit(node); 290 test_and_update_output_cursor(node); 291 auto not_in_heredoc = m_parent_node->kind() != AST::Node::Kind::Heredoc; 292 if (not_in_heredoc) 293 current_builder().append("\""sv); 294 295 TemporaryChange quotes { m_options.in_double_quotes, true }; 296 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 297 298 NodeVisitor::visit(node); 299 300 if (not_in_heredoc) 301 current_builder().append("\""sv); 302 visited(node); 303} 304 305void Formatter::visit(const AST::Fd2FdRedirection* node) 306{ 307 will_visit(node); 308 test_and_update_output_cursor(node); 309 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 310 311 current_builder().appendff("{}>&{}", node->source_fd(), node->dest_fd()); 312 if (m_hit_node == node) 313 ++m_output_cursor; 314 visited(node); 315} 316 317void Formatter::visit(const AST::FunctionDeclaration* node) 318{ 319 will_visit(node); 320 test_and_update_output_cursor(node); 321 current_builder().append(node->name().name); 322 current_builder().append('('); 323 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 324 325 auto first = true; 326 for (auto& arg : node->arguments()) { 327 if (!first) 328 current_builder().append(' '); 329 first = false; 330 current_builder().append(arg.name); 331 } 332 333 current_builder().append(") "sv); 334 335 in_new_block([&] { 336 if (node->block()) 337 node->block()->visit(*this); 338 }); 339 visited(node); 340} 341 342void Formatter::visit(const AST::ForLoop* node) 343{ 344 will_visit(node); 345 test_and_update_output_cursor(node); 346 auto is_loop = node->iterated_expression().is_null(); 347 current_builder().append(is_loop ? "loop"sv : "for "sv); 348 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 349 350 if (!is_loop) { 351 if (node->index_variable().has_value()) { 352 current_builder().append("index "sv); 353 current_builder().append(node->index_variable()->name); 354 current_builder().append(" "sv); 355 } 356 if (node->variable().has_value() && node->variable()->name != "it") { 357 current_builder().append(node->variable()->name); 358 current_builder().append(" in "sv); 359 } 360 361 node->iterated_expression()->visit(*this); 362 } 363 364 current_builder().append(' '); 365 in_new_block([&] { 366 if (node->block()) 367 node->block()->visit(*this); 368 }); 369 visited(node); 370} 371 372void Formatter::visit(const AST::Glob* node) 373{ 374 will_visit(node); 375 test_and_update_output_cursor(node); 376 current_builder().append(node->text()); 377 visited(node); 378} 379 380void Formatter::visit(const AST::Heredoc* node) 381{ 382 will_visit(node); 383 test_and_update_output_cursor(node); 384 385 current_builder().append("<<"sv); 386 if (node->deindent()) 387 current_builder().append('~'); 388 else 389 current_builder().append('-'); 390 391 if (node->allow_interpolation()) 392 current_builder().appendff("{}", node->end()); 393 else 394 current_builder().appendff("'{}'", node->end()); 395 396 auto content = in_new_builder([&] { 397 if (!node->contents()) 398 return; 399 400 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 401 TemporaryChange heredoc { m_options.in_heredoc, true }; 402 403 auto& contents = *node->contents(); 404 contents.visit(*this); 405 current_builder().appendff("\n{}\n", node->end()); 406 }); 407 408 m_heredocs_to_append_after_sequence.append(move(content)); 409 410 visited(node); 411} 412 413void Formatter::visit(const AST::HistoryEvent* node) 414{ 415 will_visit(node); 416 test_and_update_output_cursor(node); 417 418 current_builder().append('!'); 419 switch (node->selector().event.kind) { 420 case AST::HistorySelector::EventKind::ContainingStringLookup: 421 current_builder().append('?'); 422 current_builder().append(node->selector().event.text); 423 break; 424 case AST::HistorySelector::EventKind::StartingStringLookup: 425 current_builder().append(node->selector().event.text); 426 break; 427 case AST::HistorySelector::EventKind::IndexFromStart: 428 current_builder().append(node->selector().event.text); 429 break; 430 case AST::HistorySelector::EventKind::IndexFromEnd: 431 if (node->selector().event.index == 0) 432 current_builder().append('!'); 433 else 434 current_builder().append(node->selector().event.text); 435 break; 436 } 437 438 auto& range = node->selector().word_selector_range; 439 if (!range.end.has_value() 440 || range.end.value().kind != AST::HistorySelector::WordSelectorKind::Last 441 || range.start.kind != AST::HistorySelector::WordSelectorKind::Index || range.start.selector != 0) { 442 443 auto append_word = [this](auto& selector) { 444 switch (selector.kind) { 445 case AST::HistorySelector::WordSelectorKind::Index: 446 if (selector.selector == 0) 447 current_builder().append('^'); 448 else 449 current_builder().appendff("{}", selector.selector); 450 break; 451 case AST::HistorySelector::WordSelectorKind::Last: 452 current_builder().append('$'); 453 break; 454 } 455 }; 456 457 current_builder().append(':'); 458 append_word(range.start); 459 460 if (range.end.has_value()) { 461 current_builder().append('-'); 462 append_word(range.end.value()); 463 } 464 } 465 466 visited(node); 467} 468 469void Formatter::visit(const AST::Execute* node) 470{ 471 will_visit(node); 472 test_and_update_output_cursor(node); 473 auto& builder = current_builder(); 474 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 475 ScopedValueRollback options_rollback { m_options }; 476 477 if (node->does_capture_stdout()) 478 builder.append("$("sv); 479 480 NodeVisitor::visit(node); 481 482 if (node->does_capture_stdout()) 483 builder.append(")"sv); 484 485 visited(node); 486} 487 488void Formatter::visit(const AST::IfCond* node) 489{ 490 will_visit(node); 491 test_and_update_output_cursor(node); 492 493 current_builder().append("if "sv); 494 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 495 496 node->condition()->visit(*this); 497 498 current_builder().append(' '); 499 500 in_new_block([&] { 501 if (node->true_branch()) 502 node->true_branch()->visit(*this); 503 }); 504 505 if (node->false_branch()) { 506 current_builder().append(" else "sv); 507 if (node->false_branch()->kind() != AST::Node::Kind::IfCond) { 508 in_new_block([&] { 509 node->false_branch()->visit(*this); 510 }); 511 } else { 512 node->false_branch()->visit(*this); 513 } 514 } else if (node->else_position().has_value()) { 515 current_builder().append(" else "sv); 516 } 517 visited(node); 518} 519 520void Formatter::visit(const AST::ImmediateExpression* node) 521{ 522 will_visit(node); 523 test_and_update_output_cursor(node); 524 525 current_builder().append("${"sv); 526 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 527 528 current_builder().append(node->function_name()); 529 530 for (auto& node : node->arguments()) { 531 current_builder().append(' '); 532 node->visit(*this); 533 } 534 535 if (node->has_closing_brace()) 536 current_builder().append('}'); 537 538 visited(node); 539} 540 541void Formatter::visit(const AST::Join* node) 542{ 543 will_visit(node); 544 test_and_update_output_cursor(node); 545 auto should_parenthesise = m_options.explicit_parentheses; 546 547 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 548 TemporaryChange parens { m_options.explicit_parentheses, false }; 549 550 if (should_parenthesise) 551 current_builder().append('('); 552 553 node->left()->visit(*this); 554 555 current_builder().append(' '); 556 557 node->right()->visit(*this); 558 559 if (should_parenthesise) 560 current_builder().append(')'); 561 visited(node); 562} 563 564void Formatter::visit(const AST::MatchExpr* node) 565{ 566 will_visit(node); 567 test_and_update_output_cursor(node); 568 current_builder().append("match "sv); 569 570 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 571 572 node->matched_expr()->visit(*this); 573 574 if (!node->expr_name().is_empty()) { 575 current_builder().append(" as "sv); 576 current_builder().append(node->expr_name()); 577 } 578 579 current_builder().append(' '); 580 in_new_block([&] { 581 auto first_entry = true; 582 for (auto& entry : node->entries()) { 583 if (!first_entry) 584 insert_separator(); 585 first_entry = false; 586 auto first = true; 587 entry.options.visit( 588 [&](Vector<NonnullRefPtr<AST::Node>> const& patterns) { 589 for (auto& option : patterns) { 590 if (!first) 591 current_builder().append(" | "sv); 592 first = false; 593 option->visit(*this); 594 } 595 }, 596 [&](Vector<Regex<ECMA262>> const& patterns) { 597 for (auto& option : patterns) { 598 if (!first) 599 current_builder().append(" | "sv); 600 first = false; 601 auto node = make_ref_counted<AST::BarewordLiteral>(AST::Position {}, String::from_utf8(option.pattern_value).release_value_but_fixme_should_propagate_errors()); 602 node->visit(*this); 603 } 604 }); 605 606 current_builder().append(' '); 607 if (entry.match_names.has_value() && !entry.match_names.value().is_empty()) { 608 current_builder().append("as ("sv); 609 auto first = true; 610 for (auto& name : entry.match_names.value()) { 611 if (!first) 612 current_builder().append(' '); 613 first = false; 614 current_builder().append(name); 615 } 616 current_builder().append(") "sv); 617 } 618 in_new_block([&] { 619 if (entry.body) 620 entry.body->visit(*this); 621 }); 622 } 623 }); 624 visited(node); 625} 626 627void Formatter::visit(const AST::Or* node) 628{ 629 will_visit(node); 630 test_and_update_output_cursor(node); 631 auto should_indent = m_parent_node && m_parent_node->kind() != AST::Node::Kind::Or; 632 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 633 634 with_added_indent(should_indent ? 1 : 0, [&] { 635 node->left()->visit(*this); 636 637 current_builder().append(" "sv); 638 insert_separator(true); 639 current_builder().append("|| "sv); 640 641 node->right()->visit(*this); 642 }); 643 visited(node); 644} 645 646void Formatter::visit(const AST::Pipe* node) 647{ 648 will_visit(node); 649 test_and_update_output_cursor(node); 650 auto should_indent = m_parent_node && m_parent_node->kind() != AST::Node::Kind::Pipe; 651 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 652 653 node->left()->visit(*this); 654 current_builder().append(" "sv); 655 656 with_added_indent(should_indent ? 1 : 0, [&] { 657 insert_separator(true); 658 current_builder().append("| "sv); 659 660 node->right()->visit(*this); 661 }); 662 visited(node); 663} 664 665void Formatter::visit(const AST::Range* node) 666{ 667 will_visit(node); 668 test_and_update_output_cursor(node); 669 if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice) 670 current_builder().append('{'); 671 672 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 673 node->start()->visit(*this); 674 current_builder().append(".."sv); 675 node->end()->visit(*this); 676 677 if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice) 678 current_builder().append('}'); 679 visited(node); 680} 681 682void Formatter::visit(const AST::ReadRedirection* node) 683{ 684 will_visit(node); 685 test_and_update_output_cursor(node); 686 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 687 688 if (node->fd() != 0) 689 current_builder().appendff(" {}<", node->fd()); 690 else 691 current_builder().append(" <"sv); 692 NodeVisitor::visit(node); 693 visited(node); 694} 695 696void Formatter::visit(const AST::ReadWriteRedirection* node) 697{ 698 will_visit(node); 699 test_and_update_output_cursor(node); 700 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 701 702 if (node->fd() != 0) 703 current_builder().appendff(" {}<>", node->fd()); 704 else 705 current_builder().append(" <>"sv); 706 NodeVisitor::visit(node); 707 visited(node); 708} 709 710void Formatter::visit(const AST::Sequence* node) 711{ 712 will_visit(node); 713 test_and_update_output_cursor(node); 714 715 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 716 717 bool first = true; 718 for (auto& entry : node->entries()) { 719 if (first) 720 first = false; 721 else 722 insert_separator(); 723 724 entry->visit(*this); 725 } 726 727 visited(node); 728} 729 730void Formatter::visit(const AST::Subshell* node) 731{ 732 will_visit(node); 733 test_and_update_output_cursor(node); 734 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 735 736 in_new_block([&] { 737 NodeVisitor::visit(node); 738 }); 739 visited(node); 740} 741 742void Formatter::visit(const AST::Slice* node) 743{ 744 will_visit(node); 745 test_and_update_output_cursor(node); 746 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 747 748 current_builder().append('['); 749 node->selector()->visit(*this); 750 current_builder().append(']'); 751 752 visited(node); 753} 754 755void Formatter::visit(const AST::SimpleVariable* node) 756{ 757 will_visit(node); 758 test_and_update_output_cursor(node); 759 current_builder().append('$'); 760 current_builder().append(node->name()); 761 if (const AST::Node* slice = node->slice()) 762 slice->visit(*this); 763 visited(node); 764} 765 766void Formatter::visit(const AST::SpecialVariable* node) 767{ 768 will_visit(node); 769 test_and_update_output_cursor(node); 770 current_builder().append('$'); 771 current_builder().append(node->name()); 772 if (const AST::Node* slice = node->slice()) 773 slice->visit(*this); 774 visited(node); 775} 776 777void Formatter::visit(const AST::Juxtaposition* node) 778{ 779 will_visit(node); 780 test_and_update_output_cursor(node); 781 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 782 NodeVisitor::visit(node); 783 visited(node); 784} 785 786void Formatter::visit(const AST::StringLiteral* node) 787{ 788 will_visit(node); 789 test_and_update_output_cursor(node); 790 if (!m_options.in_double_quotes && !m_options.in_heredoc) 791 current_builder().append("'"sv); 792 793 if (m_options.in_double_quotes && !m_options.in_heredoc) { 794 for (auto ch : node->text().bytes_as_string_view()) { 795 switch (ch) { 796 case '"': 797 case '\\': 798 case '$': 799 current_builder().append('\\'); 800 break; 801 case '\n': 802 current_builder().append("\\n"sv); 803 continue; 804 case '\r': 805 current_builder().append("\\r"sv); 806 continue; 807 case '\t': 808 current_builder().append("\\t"sv); 809 continue; 810 case '\v': 811 current_builder().append("\\v"sv); 812 continue; 813 case '\f': 814 current_builder().append("\\f"sv); 815 continue; 816 case '\a': 817 current_builder().append("\\a"sv); 818 continue; 819 case '\e': 820 current_builder().append("\\e"sv); 821 continue; 822 default: 823 break; 824 } 825 current_builder().append(ch); 826 } 827 } else { 828 current_builder().append(node->text()); 829 } 830 831 if (!m_options.in_double_quotes && !m_options.in_heredoc) 832 current_builder().append("'"sv); 833 visited(node); 834} 835 836void Formatter::visit(const AST::StringPartCompose* node) 837{ 838 will_visit(node); 839 test_and_update_output_cursor(node); 840 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 841 NodeVisitor::visit(node); 842 visited(node); 843} 844 845void Formatter::visit(const AST::SyntaxError* node) 846{ 847 will_visit(node); 848 test_and_update_output_cursor(node); 849 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 850 NodeVisitor::visit(node); 851 visited(node); 852} 853 854void Formatter::visit(const AST::Tilde* node) 855{ 856 will_visit(node); 857 test_and_update_output_cursor(node); 858 current_builder().append(node->text()); 859 visited(node); 860} 861 862void Formatter::visit(const AST::VariableDeclarations* node) 863{ 864 will_visit(node); 865 test_and_update_output_cursor(node); 866 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 867 868 auto first = true; 869 for (auto& entry : node->variables()) { 870 if (!first) 871 current_builder().append(' '); 872 first = false; 873 entry.name->visit(*this); 874 current_builder().append('='); 875 876 if (entry.value->is_command()) 877 current_builder().append('('); 878 879 entry.value->visit(*this); 880 881 if (entry.value->is_command()) 882 current_builder().append(')'); 883 } 884 visited(node); 885} 886 887void Formatter::visit(const AST::WriteAppendRedirection* node) 888{ 889 will_visit(node); 890 test_and_update_output_cursor(node); 891 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 892 893 if (node->fd() != 1) 894 current_builder().appendff(" {}>>", node->fd()); 895 else 896 current_builder().append(" >>"sv); 897 NodeVisitor::visit(node); 898 visited(node); 899} 900 901void Formatter::visit(const AST::WriteRedirection* node) 902{ 903 will_visit(node); 904 test_and_update_output_cursor(node); 905 TemporaryChange<const AST::Node*> parent { m_parent_node, node }; 906 907 if (node->fd() != 1) 908 current_builder().appendff(" {}>", node->fd()); 909 else 910 current_builder().append(" >"sv); 911 NodeVisitor::visit(node); 912 visited(node); 913} 914 915}