Serenity Operating System
at master 955 lines 29 kB view raw
1/* 2 * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/CharacterTypes.h> 8#include <Shell/PosixLexer.h> 9 10static bool is_operator(StringView text) 11{ 12 return Shell::Posix::Token::operator_from_name(text).has_value(); 13} 14 15static bool is_part_of_operator(StringView text, char ch) 16{ 17 StringBuilder builder; 18 builder.append(text); 19 builder.append(ch); 20 21 return Shell::Posix::Token::operator_from_name(builder.string_view()).has_value(); 22} 23 24namespace Shell::Posix { 25 26ErrorOr<Vector<Token>> Lexer::batch_next(Optional<Reduction> starting_reduction) 27{ 28 if (starting_reduction.has_value()) 29 m_next_reduction = *starting_reduction; 30 31 for (; m_next_reduction != Reduction::None;) { 32 auto result = TRY(reduce(m_next_reduction)); 33 m_next_reduction = result.next_reduction; 34 if (!result.tokens.is_empty()) 35 return result.tokens; 36 } 37 38 return Vector<Token> {}; 39} 40 41ExpansionRange Lexer::range(ssize_t offset) const 42{ 43 return { 44 m_state.position.end_offset - m_state.position.start_offset + offset - 1, 45 0, 46 }; 47} 48 49char Lexer::consume() 50{ 51 auto ch = m_lexer.consume(); 52 if (ch == '\n') { 53 m_state.position.end_line.line_number++; 54 m_state.position.end_line.line_column = 0; 55 } 56 57 m_state.position.end_offset++; 58 return ch; 59} 60 61void Lexer::reconsume(StringView string) 62{ 63 for (auto byte : string.bytes()) { 64 if (byte == '\n') { 65 m_state.position.end_line.line_number++; 66 m_state.position.end_line.line_column = 0; 67 } 68 69 m_state.position.end_offset++; 70 } 71} 72 73bool Lexer::consume_specific(char ch) 74{ 75 if (m_lexer.peek() == ch) { 76 consume(); 77 return true; 78 } 79 return false; 80} 81 82ErrorOr<Lexer::ReductionResult> Lexer::reduce(Reduction reduction) 83{ 84 switch (reduction) { 85 case Reduction::None: 86 return ReductionResult { {}, Reduction::None }; 87 case Reduction::End: 88 return reduce_end(); 89 case Reduction::Operator: 90 return reduce_operator(); 91 case Reduction::Comment: 92 return reduce_comment(); 93 case Reduction::SingleQuotedString: 94 return reduce_single_quoted_string(); 95 case Reduction::DoubleQuotedString: 96 return reduce_double_quoted_string(); 97 case Reduction::Expansion: 98 return reduce_expansion(); 99 case Reduction::CommandExpansion: 100 return reduce_command_expansion(); 101 case Reduction::Start: 102 return reduce_start(); 103 case Reduction::ArithmeticExpansion: 104 return reduce_arithmetic_expansion(); 105 case Reduction::SpecialParameterExpansion: 106 return reduce_special_parameter_expansion(); 107 case Reduction::ParameterExpansion: 108 return reduce_parameter_expansion(); 109 case Reduction::CommandOrArithmeticSubstitutionExpansion: 110 return reduce_command_or_arithmetic_substitution_expansion(); 111 case Reduction::ExtendedParameterExpansion: 112 return reduce_extended_parameter_expansion(); 113 case Reduction::HeredocContents: 114 return reduce_heredoc_contents(); 115 } 116 117 VERIFY_NOT_REACHED(); 118} 119 120ErrorOr<Lexer::ReductionResult> Lexer::reduce_end() 121{ 122 return ReductionResult { 123 .tokens = { Token::eof() }, 124 .next_reduction = Reduction::None, 125 }; 126} 127 128Lexer::HeredocKeyResult Lexer::process_heredoc_key(Token const& token) 129{ 130 StringBuilder builder; 131 enum ParseState { 132 Free, 133 InDoubleQuotes, 134 InSingleQuotes, 135 }; 136 Vector<ParseState, 4> parse_state; 137 parse_state.append(Free); 138 bool escaped = false; 139 bool had_a_single_quote_segment = false; 140 141 for (auto byte : token.value.bytes()) { 142 switch (parse_state.last()) { 143 case Free: 144 switch (byte) { 145 case '"': 146 if (escaped) { 147 builder.append(byte); 148 escaped = false; 149 } else { 150 parse_state.append(InDoubleQuotes); 151 } 152 break; 153 case '\'': 154 if (escaped) { 155 builder.append(byte); 156 escaped = false; 157 } else { 158 had_a_single_quote_segment = true; 159 parse_state.append(InSingleQuotes); 160 } 161 break; 162 case '\\': 163 if (escaped) { 164 builder.append(byte); 165 escaped = false; 166 } else { 167 escaped = true; 168 } 169 break; 170 default: 171 if (escaped) { 172 builder.append('\\'); 173 escaped = false; 174 } 175 builder.append(byte); 176 break; 177 } 178 break; 179 case InDoubleQuotes: 180 if (!escaped && byte == '"') { 181 parse_state.take_last(); 182 break; 183 } 184 if (escaped) { 185 if (byte != '"') 186 builder.append('\\'); 187 builder.append(byte); 188 break; 189 } 190 if (byte == '\\') 191 escaped = true; 192 else 193 builder.append(byte); 194 break; 195 case InSingleQuotes: 196 if (byte == '\'') { 197 parse_state.take_last(); 198 break; 199 } 200 builder.append(byte); 201 break; 202 } 203 } 204 205 // NOTE: Not checking the final state as any garbage that even partially parses is allowed to be used as a key :/ 206 207 return { 208 .key = builder.to_string().release_value_but_fixme_should_propagate_errors(), 209 .allow_interpolation = !had_a_single_quote_segment, 210 }; 211} 212 213ErrorOr<Lexer::ReductionResult> Lexer::reduce_operator() 214{ 215 if (m_lexer.is_eof()) { 216 if (is_operator(m_state.buffer.string_view())) { 217 auto tokens = TRY(Token::operators_from(m_state)); 218 m_state.buffer.clear(); 219 m_state.position.start_offset = m_state.position.end_offset; 220 m_state.position.start_line = m_state.position.end_line; 221 222 return ReductionResult { 223 .tokens = move(tokens), 224 .next_reduction = Reduction::End, 225 }; 226 } 227 228 return reduce(Reduction::Start); 229 } 230 231 if (is_part_of_operator(m_state.buffer.string_view(), m_lexer.peek())) { 232 m_state.buffer.append(consume()); 233 return ReductionResult { 234 .tokens = {}, 235 .next_reduction = Reduction::Operator, 236 }; 237 } 238 239 auto tokens = Vector<Token> {}; 240 if (is_operator(m_state.buffer.string_view())) { 241 tokens.extend(TRY(Token::operators_from(m_state))); 242 m_state.buffer.clear(); 243 m_state.position.start_offset = m_state.position.end_offset; 244 m_state.position.start_line = m_state.position.end_line; 245 } 246 247 auto expect_heredoc_entry = !tokens.is_empty() && (tokens.last().type == Token::Type::DoubleLessDash || tokens.last().type == Token::Type::DoubleLess); 248 249 auto result = TRY(reduce(Reduction::Start)); 250 tokens.extend(move(result.tokens)); 251 252 while (expect_heredoc_entry && tokens.size() == 1) { 253 result = TRY(reduce(result.next_reduction)); 254 tokens.extend(move(result.tokens)); 255 } 256 257 if (expect_heredoc_entry && tokens.size() > 1) { 258 auto [key, interpolation] = process_heredoc_key(tokens[1]); 259 m_state.heredoc_entries.enqueue(HeredocEntry { 260 .key = key, 261 .allow_interpolation = interpolation, 262 .dedent = tokens[0].type == Token::Type::DoubleLessDash, 263 }); 264 } 265 266 return ReductionResult { 267 .tokens = move(tokens), 268 .next_reduction = result.next_reduction, 269 }; 270} 271 272ErrorOr<Lexer::ReductionResult> Lexer::reduce_comment() 273{ 274 if (m_lexer.is_eof()) { 275 return ReductionResult { 276 .tokens = {}, 277 .next_reduction = Reduction::End, 278 }; 279 } 280 281 if (consume() == '\n') { 282 m_state.on_new_line = true; 283 return ReductionResult { 284 .tokens = { Token::newline() }, 285 .next_reduction = Reduction::Start, 286 }; 287 } 288 289 return ReductionResult { 290 .tokens = {}, 291 .next_reduction = Reduction::Comment, 292 }; 293} 294 295ErrorOr<Lexer::ReductionResult> Lexer::reduce_single_quoted_string() 296{ 297 if (m_lexer.is_eof()) { 298 auto tokens = TRY(Token::maybe_from_state(m_state)); 299 tokens.append(Token::continuation('\'')); 300 return ReductionResult { 301 .tokens = move(tokens), 302 .next_reduction = Reduction::End, 303 }; 304 } 305 306 auto ch = consume(); 307 m_state.buffer.append(ch); 308 309 if (ch == '\'') { 310 return ReductionResult { 311 .tokens = {}, 312 .next_reduction = Reduction::Start, 313 }; 314 } 315 316 return ReductionResult { 317 .tokens = {}, 318 .next_reduction = Reduction::SingleQuotedString, 319 }; 320} 321 322ErrorOr<Lexer::ReductionResult> Lexer::reduce_double_quoted_string() 323{ 324 m_state.previous_reduction = Reduction::DoubleQuotedString; 325 if (m_lexer.is_eof()) { 326 auto tokens = TRY(Token::maybe_from_state(m_state)); 327 tokens.append(Token::continuation('"')); 328 return ReductionResult { 329 .tokens = move(tokens), 330 .next_reduction = Reduction::End, 331 }; 332 } 333 334 auto ch = consume(); 335 m_state.buffer.append(ch); 336 337 if (m_state.escaping) { 338 m_state.escaping = false; 339 340 return ReductionResult { 341 .tokens = {}, 342 .next_reduction = Reduction::DoubleQuotedString, 343 }; 344 } 345 346 switch (ch) { 347 case '\\': 348 m_state.escaping = true; 349 return ReductionResult { 350 .tokens = {}, 351 .next_reduction = Reduction::DoubleQuotedString, 352 }; 353 case '"': 354 m_state.previous_reduction = Reduction::Start; 355 return ReductionResult { 356 .tokens = {}, 357 .next_reduction = Reduction::Start, 358 }; 359 case '$': 360 if (m_lexer.next_is("(")) 361 m_state.expansions.empend(CommandExpansion { .command = StringBuilder {}, .range = range() }); 362 else 363 m_state.expansions.empend(ParameterExpansion { .parameter = StringBuilder {}, .range = range() }); 364 return ReductionResult { 365 .tokens = {}, 366 .next_reduction = Reduction::Expansion, 367 }; 368 case '`': 369 m_state.expansions.empend(CommandExpansion { .command = StringBuilder {}, .range = range() }); 370 return ReductionResult { 371 .tokens = {}, 372 .next_reduction = Reduction::CommandExpansion, 373 }; 374 default: 375 return ReductionResult { 376 .tokens = {}, 377 .next_reduction = Reduction::DoubleQuotedString, 378 }; 379 } 380} 381 382ErrorOr<Lexer::ReductionResult> Lexer::reduce_expansion() 383{ 384 if (m_lexer.is_eof()) 385 return reduce(m_state.previous_reduction); 386 387 auto ch = m_lexer.peek(); 388 389 switch (ch) { 390 case '{': 391 consume(); 392 m_state.buffer.append(ch); 393 return ReductionResult { 394 .tokens = {}, 395 .next_reduction = Reduction::ExtendedParameterExpansion, 396 }; 397 case '(': 398 consume(); 399 m_state.buffer.append(ch); 400 return ReductionResult { 401 .tokens = {}, 402 .next_reduction = Reduction::CommandOrArithmeticSubstitutionExpansion, 403 }; 404 case 'a' ... 'z': 405 case 'A' ... 'Z': 406 case '_': { 407 consume(); 408 m_state.buffer.append(ch); 409 auto& expansion = m_state.expansions.last().get<ParameterExpansion>(); 410 expansion.parameter.append(ch); 411 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 412 413 return ReductionResult { 414 .tokens = {}, 415 .next_reduction = Reduction::ParameterExpansion, 416 }; 417 } 418 case '0' ... '9': 419 case '-': 420 case '!': 421 case '@': 422 case '#': 423 case '?': 424 case '*': 425 case '$': 426 return reduce(Reduction::SpecialParameterExpansion); 427 default: 428 m_state.buffer.append(ch); 429 return reduce(m_state.previous_reduction); 430 } 431} 432 433ErrorOr<Lexer::ReductionResult> Lexer::reduce_command_expansion() 434{ 435 if (m_lexer.is_eof()) { 436 auto& expansion = m_state.expansions.last().get<CommandExpansion>(); 437 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 438 439 return ReductionResult { 440 .tokens = { Token::continuation('`') }, 441 .next_reduction = m_state.previous_reduction, 442 }; 443 } 444 445 auto ch = consume(); 446 447 if (!m_state.escaping && ch == '`') { 448 m_state.buffer.append(ch); 449 auto& expansion = m_state.expansions.last().get<CommandExpansion>(); 450 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 451 452 return ReductionResult { 453 .tokens = {}, 454 .next_reduction = m_state.previous_reduction, 455 }; 456 } 457 458 if (!m_state.escaping && ch == '\\') { 459 m_state.escaping = true; 460 return ReductionResult { 461 .tokens = {}, 462 .next_reduction = Reduction::CommandExpansion, 463 }; 464 } 465 466 m_state.escaping = false; 467 m_state.buffer.append(ch); 468 m_state.expansions.last().get<CommandExpansion>().command.append(ch); 469 return ReductionResult { 470 .tokens = {}, 471 .next_reduction = Reduction::CommandExpansion, 472 }; 473} 474 475ErrorOr<Lexer::ReductionResult> Lexer::reduce_heredoc_contents() 476{ 477 if (m_lexer.is_eof()) { 478 auto tokens = TRY(Token::maybe_from_state(m_state)); 479 m_state.buffer.clear(); 480 m_state.position.start_offset = m_state.position.end_offset; 481 m_state.position.start_line = m_state.position.end_line; 482 483 return ReductionResult { 484 .tokens = move(tokens), 485 .next_reduction = Reduction::End, 486 }; 487 } 488 489 if (!m_state.escaping && consume_specific('\\')) { 490 m_state.escaping = true; 491 m_state.buffer.append('\\'); 492 return ReductionResult { 493 .tokens = {}, 494 .next_reduction = Reduction::HeredocContents, 495 }; 496 } 497 498 if (!m_state.escaping && consume_specific('$')) { 499 m_state.buffer.append('$'); 500 if (m_lexer.next_is("(")) 501 m_state.expansions.empend(CommandExpansion { .command = StringBuilder {}, .range = range() }); 502 else 503 m_state.expansions.empend(ParameterExpansion { .parameter = StringBuilder {}, .range = range() }); 504 505 return ReductionResult { 506 .tokens = {}, 507 .next_reduction = Reduction::Expansion, 508 }; 509 } 510 511 if (!m_state.escaping && consume_specific('`')) { 512 m_state.buffer.append('`'); 513 m_state.expansions.empend(CommandExpansion { .command = StringBuilder {}, .range = range() }); 514 return ReductionResult { 515 .tokens = {}, 516 .next_reduction = Reduction::CommandExpansion, 517 }; 518 } 519 520 m_state.escaping = false; 521 m_state.buffer.append(consume()); 522 return ReductionResult { 523 .tokens = {}, 524 .next_reduction = Reduction::HeredocContents, 525 }; 526} 527 528ErrorOr<Lexer::ReductionResult> Lexer::reduce_start() 529{ 530 auto was_on_new_line = m_state.on_new_line; 531 m_state.on_new_line = false; 532 533 if (m_lexer.is_eof()) { 534 auto tokens = TRY(Token::maybe_from_state(m_state)); 535 m_state.buffer.clear(); 536 m_state.expansions.clear(); 537 m_state.position.start_offset = m_state.position.end_offset; 538 m_state.position.start_line = m_state.position.end_line; 539 540 return ReductionResult { 541 .tokens = move(tokens), 542 .next_reduction = Reduction::End, 543 }; 544 } 545 546 if (was_on_new_line && !m_state.heredoc_entries.is_empty()) { 547 auto const& entry = m_state.heredoc_entries.head(); 548 549 auto start_index = m_lexer.tell(); 550 Optional<size_t> end_index; 551 552 for (; !m_lexer.is_eof();) { 553 auto index = m_lexer.tell(); 554 auto possible_end_index = m_lexer.tell(); 555 if (m_lexer.consume_specific('\n')) { 556 if (entry.dedent) 557 m_lexer.ignore_while(is_any_of("\t"sv)); 558 if (m_lexer.consume_specific(entry.key.bytes_as_string_view())) { 559 if (m_lexer.consume_specific('\n') || m_lexer.is_eof()) { 560 end_index = possible_end_index; 561 break; 562 } 563 } 564 } 565 if (m_lexer.tell() == index) 566 m_lexer.ignore(); 567 } 568 569 auto contents = m_lexer.input().substring_view(start_index, end_index.value_or(m_lexer.tell()) - start_index); 570 reconsume(contents); 571 572 m_state.buffer.clear(); 573 m_state.buffer.append(contents); 574 575 auto token = TRY(Token::maybe_from_state(m_state)).first(); 576 token.relevant_heredoc_key = entry.key; 577 token.type = Token::Type::HeredocContents; 578 579 m_state.heredoc_entries.dequeue(); 580 581 m_state.on_new_line = true; 582 583 m_state.buffer.clear(); 584 585 return ReductionResult { 586 .tokens = { move(token) }, 587 .next_reduction = Reduction::Start, 588 }; 589 } 590 591 if (m_state.escaping && consume_specific('\n')) { 592 m_state.escaping = false; 593 594 auto buffer = m_state.buffer.to_deprecated_string().substring(0, m_state.buffer.length() - 1); 595 m_state.buffer.clear(); 596 m_state.buffer.append(buffer); 597 598 return ReductionResult { 599 .tokens = {}, 600 .next_reduction = Reduction::Start, 601 }; 602 } 603 604 if (!m_state.escaping && m_lexer.peek() == '#' && m_state.buffer.is_empty()) { 605 consume(); 606 return ReductionResult { 607 .tokens = {}, 608 .next_reduction = Reduction::Comment, 609 }; 610 } 611 612 if (!m_state.escaping && consume_specific('\n')) { 613 auto tokens = TRY(Token::maybe_from_state(m_state)); 614 tokens.append(Token::newline()); 615 616 m_state.on_new_line = true; 617 618 m_state.buffer.clear(); 619 m_state.expansions.clear(); 620 m_state.position.start_offset = m_state.position.end_offset; 621 m_state.position.start_line = m_state.position.end_line; 622 623 return ReductionResult { 624 .tokens = move(tokens), 625 .next_reduction = Reduction::Start, 626 }; 627 } 628 629 if (!m_state.escaping && consume_specific('\\')) { 630 m_state.escaping = true; 631 m_state.buffer.append('\\'); 632 return ReductionResult { 633 .tokens = {}, 634 .next_reduction = Reduction::Start, 635 }; 636 } 637 638 if (!m_state.escaping && is_part_of_operator(""sv, m_lexer.peek())) { 639 auto tokens = TRY(Token::maybe_from_state(m_state)); 640 m_state.buffer.clear(); 641 m_state.buffer.append(consume()); 642 m_state.expansions.clear(); 643 m_state.position.start_offset = m_state.position.end_offset; 644 m_state.position.start_line = m_state.position.end_line; 645 646 return ReductionResult { 647 .tokens = move(tokens), 648 .next_reduction = Reduction::Operator, 649 }; 650 } 651 652 if (!m_state.escaping && consume_specific('\'')) { 653 m_state.buffer.append('\''); 654 return ReductionResult { 655 .tokens = {}, 656 .next_reduction = Reduction::SingleQuotedString, 657 }; 658 } 659 660 if (!m_state.escaping && consume_specific('"')) { 661 m_state.buffer.append('"'); 662 return ReductionResult { 663 .tokens = {}, 664 .next_reduction = Reduction::DoubleQuotedString, 665 }; 666 } 667 668 if (!m_state.escaping && is_ascii_space(m_lexer.peek())) { 669 consume(); 670 auto tokens = TRY(Token::maybe_from_state(m_state)); 671 m_state.buffer.clear(); 672 m_state.expansions.clear(); 673 m_state.position.start_offset = m_state.position.end_offset; 674 m_state.position.start_line = m_state.position.end_line; 675 676 return ReductionResult { 677 .tokens = move(tokens), 678 .next_reduction = Reduction::Start, 679 }; 680 } 681 682 if (!m_state.escaping && consume_specific('$')) { 683 m_state.buffer.append('$'); 684 if (m_lexer.next_is("(")) 685 m_state.expansions.empend(CommandExpansion { .command = StringBuilder {}, .range = range() }); 686 else 687 m_state.expansions.empend(ParameterExpansion { .parameter = StringBuilder {}, .range = range() }); 688 689 return ReductionResult { 690 .tokens = {}, 691 .next_reduction = Reduction::Expansion, 692 }; 693 } 694 695 if (!m_state.escaping && consume_specific('`')) { 696 m_state.buffer.append('`'); 697 m_state.expansions.empend(CommandExpansion { .command = StringBuilder {}, .range = range() }); 698 return ReductionResult { 699 .tokens = {}, 700 .next_reduction = Reduction::CommandExpansion, 701 }; 702 } 703 704 m_state.escaping = false; 705 m_state.buffer.append(consume()); 706 return ReductionResult { 707 .tokens = {}, 708 .next_reduction = Reduction::Start, 709 }; 710} 711 712ErrorOr<Lexer::ReductionResult> Lexer::reduce_arithmetic_expansion() 713{ 714 if (m_lexer.is_eof()) { 715 auto& expansion = m_state.expansions.last().get<ArithmeticExpansion>(); 716 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 717 718 return ReductionResult { 719 .tokens = { Token::continuation("$(("_short_string) }, 720 .next_reduction = m_state.previous_reduction, 721 }; 722 } 723 724 if (m_lexer.peek() == ')' && m_state.buffer.string_view().ends_with(')')) { 725 m_state.buffer.append(consume()); 726 auto& expansion = m_state.expansions.last().get<ArithmeticExpansion>(); 727 expansion.expression = TRY(String::from_utf8(expansion.value.string_view().substring_view(0, expansion.value.length() - 1))); 728 expansion.value.clear(); 729 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 730 731 return ReductionResult { 732 .tokens = {}, 733 .next_reduction = m_state.previous_reduction, 734 }; 735 } 736 737 auto ch = consume(); 738 m_state.buffer.append(ch); 739 m_state.expansions.last().get<ArithmeticExpansion>().value.append(ch); 740 return ReductionResult { 741 .tokens = {}, 742 .next_reduction = Reduction::ArithmeticExpansion, 743 }; 744} 745 746ErrorOr<Lexer::ReductionResult> Lexer::reduce_special_parameter_expansion() 747{ 748 auto ch = consume(); 749 m_state.buffer.append(ch); 750 m_state.expansions.last() = ParameterExpansion { 751 .parameter = StringBuilder {}, 752 .range = range(-1), 753 }; 754 auto& expansion = m_state.expansions.last().get<ParameterExpansion>(); 755 expansion.parameter.append(ch); 756 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 757 758 return ReductionResult { 759 .tokens = {}, 760 .next_reduction = m_state.previous_reduction, 761 }; 762} 763 764ErrorOr<Lexer::ReductionResult> Lexer::reduce_parameter_expansion() 765{ 766 auto& expansion = m_state.expansions.last().get<ParameterExpansion>(); 767 768 if (m_lexer.is_eof()) { 769 return ReductionResult { 770 .tokens = {}, 771 .next_reduction = Reduction::Start, 772 }; 773 } 774 775 auto next = m_lexer.peek(); 776 if (is_ascii_alphanumeric(next) || next == '_') { 777 m_state.buffer.append(consume()); 778 expansion.parameter.append(next); 779 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 780 781 return ReductionResult { 782 .tokens = {}, 783 .next_reduction = Reduction::ParameterExpansion, 784 }; 785 } 786 787 return reduce(m_state.previous_reduction); 788} 789 790ErrorOr<Lexer::ReductionResult> Lexer::reduce_command_or_arithmetic_substitution_expansion() 791{ 792 if (m_lexer.is_eof()) { 793 return ReductionResult { 794 .tokens = { Token::continuation("$("_short_string) }, 795 .next_reduction = m_state.previous_reduction, 796 }; 797 } 798 799 auto ch = m_lexer.peek(); 800 if (ch == '(' && m_state.buffer.string_view().ends_with("$("sv)) { 801 m_state.buffer.append(consume()); 802 m_state.expansions.last() = ArithmeticExpansion { 803 .expression = {}, 804 .value = StringBuilder {}, 805 .range = range(-2) 806 }; 807 return ReductionResult { 808 .tokens = {}, 809 .next_reduction = Reduction::ArithmeticExpansion, 810 }; 811 } 812 813 if (ch == ')') { 814 m_state.buffer.append(consume()); 815 m_state.expansions.last().visit([&](auto& expansion) { 816 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 817 }); 818 return ReductionResult { 819 .tokens = {}, 820 .next_reduction = m_state.previous_reduction, 821 }; 822 } 823 824 m_state.buffer.append(consume()); 825 m_state.expansions.last().get<CommandExpansion>().command.append(ch); 826 return ReductionResult { 827 .tokens = {}, 828 .next_reduction = Reduction::CommandOrArithmeticSubstitutionExpansion, 829 }; 830} 831 832ErrorOr<Lexer::ReductionResult> Lexer::reduce_extended_parameter_expansion() 833{ 834 auto& expansion = m_state.expansions.last().get<ParameterExpansion>(); 835 836 if (m_lexer.is_eof()) { 837 return ReductionResult { 838 .tokens = { Token::continuation("${"_short_string) }, 839 .next_reduction = m_state.previous_reduction, 840 }; 841 } 842 843 auto ch = m_lexer.peek(); 844 if (ch == '}') { 845 m_state.buffer.append(consume()); 846 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 847 848 return ReductionResult { 849 .tokens = {}, 850 .next_reduction = m_state.previous_reduction, 851 }; 852 } 853 854 m_state.buffer.append(consume()); 855 expansion.parameter.append(ch); 856 expansion.range.length = m_state.position.end_offset - expansion.range.start - m_state.position.start_offset; 857 858 return ReductionResult { 859 .tokens = {}, 860 .next_reduction = Reduction::ExtendedParameterExpansion, 861 }; 862} 863 864StringView Token::type_name() const 865{ 866 switch (type) { 867 case Type::Eof: 868 return "Eof"sv; 869 case Type::Newline: 870 return "Newline"sv; 871 case Type::Continuation: 872 return "Continuation"sv; 873 case Type::Token: 874 return "Token"sv; 875 case Type::And: 876 return "And"sv; 877 case Type::Pipe: 878 return "Pipe"sv; 879 case Type::OpenParen: 880 return "OpenParen"sv; 881 case Type::CloseParen: 882 return "CloseParen"sv; 883 case Type::Great: 884 return "Great"sv; 885 case Type::Less: 886 return "Less"sv; 887 case Type::AndIf: 888 return "AndIf"sv; 889 case Type::OrIf: 890 return "OrIf"sv; 891 case Type::DoubleSemicolon: 892 return "DoubleSemicolon"sv; 893 case Type::DoubleLess: 894 return "DoubleLess"sv; 895 case Type::DoubleGreat: 896 return "DoubleGreat"sv; 897 case Type::LessAnd: 898 return "LessAnd"sv; 899 case Type::GreatAnd: 900 return "GreatAnd"sv; 901 case Type::LessGreat: 902 return "LessGreat"sv; 903 case Type::DoubleLessDash: 904 return "DoubleLessDash"sv; 905 case Type::Clobber: 906 return "Clobber"sv; 907 case Type::Semicolon: 908 return "Semicolon"sv; 909 case Type::HeredocContents: 910 return "HeredocContents"sv; 911 case Type::AssignmentWord: 912 return "AssignmentWord"sv; 913 case Type::Bang: 914 return "Bang"sv; 915 case Type::Case: 916 return "Case"sv; 917 case Type::CloseBrace: 918 return "CloseBrace"sv; 919 case Type::Do: 920 return "Do"sv; 921 case Type::Done: 922 return "Done"sv; 923 case Type::Elif: 924 return "Elif"sv; 925 case Type::Else: 926 return "Else"sv; 927 case Type::Esac: 928 return "Esac"sv; 929 case Type::Fi: 930 return "Fi"sv; 931 case Type::For: 932 return "For"sv; 933 case Type::If: 934 return "If"sv; 935 case Type::In: 936 return "In"sv; 937 case Type::IoNumber: 938 return "IoNumber"sv; 939 case Type::OpenBrace: 940 return "OpenBrace"sv; 941 case Type::Then: 942 return "Then"sv; 943 case Type::Until: 944 return "Until"sv; 945 case Type::VariableName: 946 return "VariableName"sv; 947 case Type::While: 948 return "While"sv; 949 case Type::Word: 950 return "Word"sv; 951 } 952 return "Idk"sv; 953} 954 955}