Serenity Operating System
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}