Serenity Operating System
1/*
2 * Copyright (c) 2020-2021, the SerenityOS developers.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "Parser.h"
8#include "Shell.h"
9#include <AK/AllOf.h>
10#include <AK/GenericLexer.h>
11#include <AK/ScopeGuard.h>
12#include <AK/ScopedValueRollback.h>
13#include <AK/TemporaryChange.h>
14#include <ctype.h>
15#include <stdio.h>
16#include <unistd.h>
17
18namespace Shell {
19
20Parser::SavedOffset Parser::save_offset() const
21{
22 return { m_offset, m_line };
23}
24
25char Parser::peek()
26{
27 if (at_end())
28 return 0;
29
30 VERIFY(m_offset < m_input.length());
31
32 auto ch = m_input[m_offset];
33 if (ch == '\\' && m_input.length() > m_offset + 1 && m_input[m_offset + 1] == '\n') {
34 m_offset += 2;
35 ++m_line.line_number;
36 m_line.line_column = 0;
37 return peek();
38 }
39
40 return ch;
41}
42
43char Parser::consume()
44{
45 if (at_end())
46 return 0;
47
48 auto ch = peek();
49 ++m_offset;
50
51 if (ch == '\n') {
52 ++m_line.line_number;
53 m_line.line_column = 0;
54 } else {
55 ++m_line.line_column;
56 }
57
58 return ch;
59}
60
61bool Parser::expect(char ch)
62{
63 return expect(StringView { &ch, 1 });
64}
65
66bool Parser::expect(StringView expected)
67{
68 auto offset_at_start = m_offset;
69 auto line_at_start = line();
70
71 if (expected.length() + m_offset > m_input.length())
72 return false;
73
74 for (auto& c : expected) {
75 if (peek() != c) {
76 restore_to(offset_at_start, line_at_start);
77 return false;
78 }
79
80 consume();
81 }
82
83 return true;
84}
85
86template<typename A, typename... Args>
87NonnullRefPtr<A> Parser::create(Args&&... args)
88{
89 return adopt_ref(*new A(AST::Position { m_rule_start_offsets.last(), m_offset, m_rule_start_lines.last(), line() }, forward<Args>(args)...));
90}
91
92[[nodiscard]] OwnPtr<Parser::ScopedOffset> Parser::push_start()
93{
94 return make<ScopedOffset>(m_rule_start_offsets, m_rule_start_lines, m_offset, m_line.line_number, m_line.line_column);
95}
96
97Parser::Offset Parser::current_position()
98{
99 return Offset { m_offset, { m_line.line_number, m_line.line_column } };
100}
101
102static constexpr bool is_whitespace(char c)
103{
104 return c == ' ' || c == '\t';
105}
106
107static constexpr bool is_digit(char c)
108{
109 return c <= '9' && c >= '0';
110}
111
112static constexpr auto is_not(char c)
113{
114 return [c](char ch) { return ch != c; };
115}
116
117static inline char to_byte(char a, char b)
118{
119 char buf[3] { a, b, 0 };
120 return strtol(buf, nullptr, 16);
121}
122
123RefPtr<AST::Node> Parser::parse()
124{
125 m_offset = 0;
126 m_line = { 0, 0 };
127
128 auto toplevel = parse_toplevel();
129
130 if (m_offset < m_input.length()) {
131 // Parsing stopped midway, this is a syntax error.
132 auto error_start = push_start();
133 while (!at_end())
134 consume();
135 auto syntax_error_node = create<AST::SyntaxError>("Unexpected tokens past the end"_string.release_value_but_fixme_should_propagate_errors());
136 if (!toplevel)
137 toplevel = move(syntax_error_node);
138 else if (!toplevel->is_syntax_error())
139 toplevel->set_is_syntax_error(*syntax_error_node);
140 }
141
142 return toplevel;
143}
144
145RefPtr<AST::Node> Parser::parse_as_single_expression()
146{
147 auto input = Shell::escape_token_for_double_quotes(m_input);
148 Parser parser { input };
149 return parser.parse_expression();
150}
151
152Vector<NonnullRefPtr<AST::Node>> Parser::parse_as_multiple_expressions()
153{
154 Vector<NonnullRefPtr<AST::Node>> nodes;
155 for (;;) {
156 consume_while(is_whitespace);
157 auto node = parse_expression();
158 if (!node)
159 node = parse_redirection();
160 if (!node)
161 return nodes;
162 nodes.append(node.release_nonnull());
163 }
164}
165
166RefPtr<AST::Node> Parser::parse_toplevel()
167{
168 auto rule_start = push_start();
169
170 SequenceParseResult result;
171 Vector<NonnullRefPtr<AST::Node>> sequence;
172 Vector<AST::Position> positions;
173 do {
174 result = parse_sequence();
175 if (result.entries.is_empty())
176 break;
177
178 sequence.extend(move(result.entries));
179 positions.extend(move(result.separator_positions));
180 } while (result.decision == ShouldReadMoreSequences::Yes);
181
182 if (sequence.is_empty())
183 return nullptr;
184
185 return create<AST::Execute>(
186 create<AST::Sequence>(move(sequence), move(positions)));
187}
188
189Parser::SequenceParseResult Parser::parse_sequence()
190{
191 Vector<NonnullRefPtr<AST::Node>> left;
192 auto read_terminators = [&](bool consider_tabs_and_spaces) {
193 if (m_heredoc_initiations.is_empty()) {
194 discard_terminators:;
195 consume_while(is_any_of(consider_tabs_and_spaces ? " \t\n;"sv : "\n;"sv));
196 } else {
197 for (;;) {
198 if (consider_tabs_and_spaces && (peek() == '\t' || peek() == ' ')) {
199 consume();
200 continue;
201 }
202 if (peek() == ';') {
203 consume();
204 continue;
205 }
206 if (peek() == '\n') {
207 auto rule_start = push_start();
208 consume();
209 if (!parse_heredoc_entries()) {
210 StringBuilder error_builder;
211 error_builder.append("Expected to find heredoc entries for "sv);
212 bool first = true;
213 for (auto& entry : m_heredoc_initiations) {
214 if (first)
215 error_builder.appendff("{} (at {}:{})", entry.end, entry.node->position().start_line.line_column, entry.node->position().start_line.line_number);
216 else
217 error_builder.appendff(", {} (at {}:{})", entry.end, entry.node->position().start_line.line_column, entry.node->position().start_line.line_number);
218 first = false;
219 }
220 left.append(create<AST::SyntaxError>(error_builder.to_string().release_value_but_fixme_should_propagate_errors(), true));
221 // Just read the rest of the newlines
222 goto discard_terminators;
223 }
224 continue;
225 }
226 break;
227 }
228 }
229 };
230
231 read_terminators(true);
232
233 auto rule_start = push_start();
234 {
235 auto var_decls = parse_variable_decls();
236 if (var_decls)
237 left.append(var_decls.release_nonnull());
238 }
239
240 auto pos_before_seps = save_offset();
241
242 switch (peek()) {
243 case '}':
244 return { move(left), {}, ShouldReadMoreSequences::No };
245 case '\n':
246 read_terminators(false);
247 [[fallthrough]];
248 case ';': {
249 if (left.is_empty())
250 break;
251
252 consume_while(is_any_of("\n;"sv));
253 auto pos_after_seps = save_offset();
254 AST::Position separator_position { pos_before_seps.offset, pos_after_seps.offset, pos_before_seps.line, pos_after_seps.line };
255
256 return { move(left), { move(separator_position) }, ShouldReadMoreSequences::Yes };
257 }
258 default:
259 break;
260 }
261
262 auto first_entry = parse_function_decl();
263
264 Vector<AST::Position> separator_positions;
265
266 if (!first_entry)
267 first_entry = parse_or_logical_sequence();
268
269 if (!first_entry)
270 return { move(left), {}, ShouldReadMoreSequences::No };
271
272 left.append(first_entry.release_nonnull());
273 separator_positions.empend(pos_before_seps.offset, pos_before_seps.offset, pos_before_seps.line, pos_before_seps.line);
274
275 consume_while(is_whitespace);
276
277 pos_before_seps = save_offset();
278 switch (peek()) {
279 case '\n':
280 read_terminators(false);
281 [[fallthrough]];
282 case ';': {
283 consume_while(is_any_of("\n;"sv));
284 auto pos_after_seps = save_offset();
285 separator_positions.empend(pos_before_seps.offset, pos_after_seps.offset, pos_before_seps.line, pos_after_seps.line);
286 return { move(left), move(separator_positions), ShouldReadMoreSequences::Yes };
287 }
288 case '&': {
289 consume();
290 auto pos_after_seps = save_offset();
291 auto bg = create<AST::Background>(left.take_last()); // Execute Background
292 left.append(move(bg));
293 separator_positions.empend(pos_before_seps.offset, pos_after_seps.offset, pos_before_seps.line, pos_after_seps.line);
294 return { move(left), move(separator_positions), ShouldReadMoreSequences::Yes };
295 }
296 default:
297 return { move(left), move(separator_positions), ShouldReadMoreSequences::No };
298 }
299}
300
301RefPtr<AST::Node> Parser::parse_variable_decls()
302{
303 auto rule_start = push_start();
304
305 consume_while(is_whitespace);
306
307 auto pos_before_name = save_offset();
308 auto var_name = consume_while(is_word_character);
309 if (var_name.is_empty())
310 return nullptr;
311
312 if (!expect('=')) {
313 restore_to(pos_before_name.offset, pos_before_name.line);
314 return nullptr;
315 }
316
317 auto name_expr = create<AST::BarewordLiteral>(String::from_utf8(var_name).release_value_but_fixme_should_propagate_errors());
318
319 auto start = push_start();
320 auto expression = parse_expression();
321 if (!expression || expression->is_syntax_error()) {
322 restore_to(*start);
323 if (peek() == '(') {
324 consume();
325 auto command = parse_pipe_sequence();
326 if (!command)
327 restore_to(*start);
328 else if (!expect(')'))
329 command->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating close paren"_string.release_value_but_fixme_should_propagate_errors(), true));
330 expression = command;
331 }
332 }
333 if (!expression) {
334 if (is_whitespace(peek())) {
335 auto string_start = push_start();
336 expression = create<AST::StringLiteral>(String {}, AST::StringLiteral::EnclosureType::None);
337 } else {
338 restore_to(pos_before_name.offset, pos_before_name.line);
339 return nullptr;
340 }
341 }
342
343 Vector<AST::VariableDeclarations::Variable> variables;
344 variables.append({ move(name_expr), expression.release_nonnull() });
345
346 if (consume_while(is_whitespace).is_empty())
347 return create<AST::VariableDeclarations>(move(variables));
348
349 auto rest = parse_variable_decls();
350 if (!rest)
351 return create<AST::VariableDeclarations>(move(variables));
352
353 VERIFY(rest->is_variable_decls());
354 auto* rest_decl = static_cast<AST::VariableDeclarations*>(rest.ptr());
355
356 variables.extend(rest_decl->variables());
357
358 return create<AST::VariableDeclarations>(move(variables));
359}
360
361RefPtr<AST::Node> Parser::parse_function_decl()
362{
363 auto rule_start = push_start();
364
365 auto restore = [&] {
366 restore_to(*rule_start);
367 return nullptr;
368 };
369
370 consume_while(is_whitespace);
371 auto pos_before_name = save_offset();
372 auto function_name = consume_while(is_word_character);
373 auto pos_after_name = save_offset();
374 if (function_name.is_empty())
375 return restore();
376
377 if (!expect('('))
378 return restore();
379
380 Vector<AST::NameWithPosition> arguments;
381 for (;;) {
382 consume_while(is_whitespace);
383
384 if (expect(')'))
385 break;
386
387 auto name_offset = m_offset;
388 auto start_line = line();
389 auto arg_name = consume_while(is_word_character);
390 if (arg_name.is_empty()) {
391 // FIXME: Should this be a syntax error, or just return?
392 return restore();
393 }
394 arguments.append({ String::from_utf8(arg_name).release_value_but_fixme_should_propagate_errors(), { name_offset, m_offset, start_line, line() } });
395 }
396
397 consume_while(is_any_of("\n\t "sv));
398
399 {
400 RefPtr<AST::Node> syntax_error;
401 {
402 auto obrace_error_start = push_start();
403 syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a function body"_string.release_value_but_fixme_should_propagate_errors(), true);
404 }
405 if (!expect('{')) {
406 return create<AST::FunctionDeclaration>(
407 AST::NameWithPosition {
408 String::from_utf8(function_name).release_value_but_fixme_should_propagate_errors(),
409 { pos_before_name.offset, pos_after_name.offset, pos_before_name.line, pos_after_name.line } },
410 move(arguments),
411 move(syntax_error));
412 }
413 }
414
415 TemporaryChange controls { m_continuation_controls_allowed, false };
416 auto body = parse_toplevel();
417
418 {
419 RefPtr<AST::SyntaxError> syntax_error;
420 {
421 auto cbrace_error_start = push_start();
422 syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a function body"_string.release_value_but_fixme_should_propagate_errors(), true);
423 }
424 if (!expect('}')) {
425 if (body)
426 body->set_is_syntax_error(*syntax_error);
427 else
428 body = move(syntax_error);
429
430 return create<AST::FunctionDeclaration>(
431 AST::NameWithPosition {
432 String::from_utf8(function_name).release_value_but_fixme_should_propagate_errors(),
433 { pos_before_name.offset, pos_after_name.offset, pos_before_name.line, pos_after_name.line } },
434 move(arguments),
435 move(body));
436 }
437 }
438
439 return create<AST::FunctionDeclaration>(
440 AST::NameWithPosition {
441 String::from_utf8(function_name).release_value_but_fixme_should_propagate_errors(),
442 { pos_before_name.offset, pos_after_name.offset, pos_before_name.line, pos_after_name.line } },
443 move(arguments),
444 move(body));
445}
446
447RefPtr<AST::Node> Parser::parse_or_logical_sequence()
448{
449 consume_while(is_whitespace);
450 auto rule_start = push_start();
451 auto and_sequence = parse_and_logical_sequence();
452 if (!and_sequence)
453 return nullptr;
454
455 consume_while(is_whitespace);
456 auto pos_before_or = save_offset();
457 if (!expect("||"sv))
458 return and_sequence;
459 auto pos_after_or = save_offset();
460
461 auto right_and_sequence = parse_and_logical_sequence();
462 if (!right_and_sequence)
463 right_and_sequence = create<AST::SyntaxError>("Expected an expression after '||'"_string.release_value_but_fixme_should_propagate_errors(), true);
464
465 return create<AST::Or>(
466 and_sequence.release_nonnull(),
467 right_and_sequence.release_nonnull(),
468 AST::Position { pos_before_or.offset, pos_after_or.offset, pos_before_or.line, pos_after_or.line });
469}
470
471RefPtr<AST::Node> Parser::parse_and_logical_sequence()
472{
473 consume_while(is_whitespace);
474 auto rule_start = push_start();
475 auto pipe_sequence = parse_pipe_sequence();
476 if (!pipe_sequence)
477 return nullptr;
478
479 consume_while(is_whitespace);
480 auto pos_before_and = save_offset();
481 if (!expect("&&"sv))
482 return pipe_sequence;
483 auto pos_after_end = save_offset();
484
485 auto right_and_sequence = parse_and_logical_sequence();
486 if (!right_and_sequence)
487 right_and_sequence = create<AST::SyntaxError>("Expected an expression after '&&'"_string.release_value_but_fixme_should_propagate_errors(), true);
488
489 return create<AST::And>(
490 pipe_sequence.release_nonnull(),
491 right_and_sequence.release_nonnull(),
492 AST::Position { pos_before_and.offset, pos_after_end.offset, pos_before_and.line, pos_after_end.line });
493}
494
495RefPtr<AST::Node> Parser::parse_pipe_sequence()
496{
497 auto rule_start = push_start();
498 auto left = parse_control_structure();
499 if (!left) {
500 if (auto cmd = parse_command())
501 left = cmd;
502 else
503 return nullptr;
504 }
505
506 consume_while(is_whitespace);
507
508 if (peek() != '|')
509 return left;
510
511 auto before_pipe = save_offset();
512 consume();
513 auto also_pipe_stderr = peek() == '&';
514 if (also_pipe_stderr) {
515 consume();
516
517 RefPtr<AST::Node> redirection;
518 {
519 auto redirection_start = push_start();
520 redirection = create<AST::Fd2FdRedirection>(STDERR_FILENO, STDOUT_FILENO);
521 }
522
523 left = create<AST::Join>(left.release_nonnull(), redirection.release_nonnull());
524 }
525
526 if (auto pipe_seq = parse_pipe_sequence()) {
527 return create<AST::Pipe>(left.release_nonnull(), pipe_seq.release_nonnull()); // Pipe
528 }
529
530 restore_to(before_pipe.offset, before_pipe.line);
531 return left;
532}
533
534RefPtr<AST::Node> Parser::parse_command()
535{
536 auto rule_start = push_start();
537 consume_while(is_whitespace);
538
539 auto redir = parse_redirection();
540 if (!redir) {
541 auto list_expr = parse_list_expression();
542 if (!list_expr)
543 return nullptr;
544
545 auto cast = create<AST::CastToCommand>(list_expr.release_nonnull()); // Cast List Command
546
547 auto next_command = parse_command();
548 if (!next_command)
549 return cast;
550
551 return create<AST::Join>(move(cast), next_command.release_nonnull()); // Join List Command
552 }
553
554 auto command = parse_command();
555 if (!command)
556 return redir;
557
558 return create<AST::Join>(redir.release_nonnull(), command.release_nonnull()); // Join Command Command
559}
560
561RefPtr<AST::Node> Parser::parse_control_structure()
562{
563 auto rule_start = push_start();
564 consume_while(is_whitespace);
565 if (auto control = parse_continuation_control())
566 return control;
567
568 if (auto for_loop = parse_for_loop())
569 return for_loop;
570
571 if (auto loop = parse_loop_loop())
572 return loop;
573
574 if (auto if_expr = parse_if_expr())
575 return if_expr;
576
577 if (auto subshell = parse_subshell())
578 return subshell;
579
580 if (auto match = parse_match_expr())
581 return match;
582
583 return nullptr;
584}
585
586RefPtr<AST::Node> Parser::parse_continuation_control()
587{
588 if (!m_continuation_controls_allowed)
589 return nullptr;
590
591 auto rule_start = push_start();
592
593 if (expect("break"sv)) {
594 {
595 auto break_end = push_start();
596 if (consume_while(is_any_of(" \t\n;"sv)).is_empty()) {
597 restore_to(*rule_start);
598 return nullptr;
599 }
600 restore_to(*break_end);
601 }
602 return create<AST::ContinuationControl>(AST::ContinuationControl::Break);
603 }
604
605 if (expect("continue"sv)) {
606 {
607 auto continue_end = push_start();
608 if (consume_while(is_any_of(" \t\n;"sv)).is_empty()) {
609 restore_to(*rule_start);
610 return nullptr;
611 }
612 restore_to(*continue_end);
613 }
614 return create<AST::ContinuationControl>(AST::ContinuationControl::Continue);
615 }
616
617 return nullptr;
618}
619
620RefPtr<AST::Node> Parser::parse_for_loop()
621{
622 auto rule_start = push_start();
623 if (!expect("for"sv))
624 return nullptr;
625
626 if (consume_while(is_any_of(" \t\n"sv)).is_empty()) {
627 restore_to(*rule_start);
628 return nullptr;
629 }
630
631 Optional<AST::NameWithPosition> index_variable_name, variable_name;
632 Optional<AST::Position> in_start_position, index_start_position;
633
634 auto offset_before_index = current_position();
635 if (expect("index"sv)) {
636 auto offset = current_position();
637 if (!consume_while(is_whitespace).is_empty()) {
638 auto offset_before_variable = current_position();
639 auto variable = consume_while(is_word_character);
640 if (!variable.is_empty()) {
641 index_start_position = AST::Position { offset_before_index.offset, offset.offset, offset_before_index.line, offset.line };
642
643 auto offset_after_variable = current_position();
644 index_variable_name = AST::NameWithPosition {
645 String::from_utf8(variable).release_value_but_fixme_should_propagate_errors(),
646 { offset_before_variable.offset, offset_after_variable.offset, offset_before_variable.line, offset_after_variable.line },
647 };
648
649 consume_while(is_whitespace);
650 } else {
651 restore_to(offset_before_index.offset, offset_before_index.line);
652 }
653 } else {
654 restore_to(offset_before_index.offset, offset_before_index.line);
655 }
656 }
657
658 auto variable_name_start_offset = current_position();
659 auto name = consume_while(is_word_character);
660 auto variable_name_end_offset = current_position();
661 if (!name.is_empty()) {
662 variable_name = AST::NameWithPosition {
663 String::from_utf8(name).release_value_but_fixme_should_propagate_errors(),
664 { variable_name_start_offset.offset, variable_name_end_offset.offset, variable_name_start_offset.line, variable_name_end_offset.line }
665 };
666 consume_while(is_whitespace);
667 auto in_error_start = push_start();
668 if (!expect("in"sv)) {
669 auto syntax_error = create<AST::SyntaxError>("Expected 'in' after a variable name in a 'for' loop"_string.release_value_but_fixme_should_propagate_errors(), true);
670 return create<AST::ForLoop>(move(variable_name), move(index_variable_name), move(syntax_error), nullptr); // ForLoop Var Iterated Block
671 }
672 in_start_position = AST::Position { in_error_start->offset, m_offset, in_error_start->line, line() };
673 }
674
675 consume_while(is_whitespace);
676 RefPtr<AST::Node> iterated_expression;
677 {
678 auto iter_error_start = push_start();
679 iterated_expression = parse_expression();
680 if (!iterated_expression)
681 iterated_expression = create<AST::SyntaxError>("Expected an expression in 'for' loop"_string.release_value_but_fixme_should_propagate_errors(), true);
682 }
683
684 consume_while(is_any_of(" \t\n"sv));
685 {
686 auto obrace_error_start = push_start();
687 if (!expect('{')) {
688 auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a 'for' loop body"_string.release_value_but_fixme_should_propagate_errors(), true);
689 return create<AST::ForLoop>(move(variable_name), move(index_variable_name), move(iterated_expression), move(syntax_error), move(in_start_position), move(index_start_position)); // ForLoop Var Iterated Block
690 }
691 }
692
693 TemporaryChange controls { m_continuation_controls_allowed, true };
694 auto body = parse_toplevel();
695
696 {
697 auto cbrace_error_start = push_start();
698 if (!expect('}')) {
699 auto error_start = push_start();
700 auto syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a 'for' loop body"_string.release_value_but_fixme_should_propagate_errors(), true);
701 if (body)
702 body->set_is_syntax_error(*syntax_error);
703 else
704 body = syntax_error;
705 }
706 }
707
708 return create<AST::ForLoop>(move(variable_name), move(index_variable_name), move(iterated_expression), move(body), move(in_start_position), move(index_start_position)); // ForLoop Var Iterated Block
709}
710
711RefPtr<AST::Node> Parser::parse_loop_loop()
712{
713 auto rule_start = push_start();
714 if (!expect("loop"sv))
715 return nullptr;
716
717 if (consume_while(is_any_of(" \t\n"sv)).is_empty()) {
718 restore_to(*rule_start);
719 return nullptr;
720 }
721
722 {
723 auto obrace_error_start = push_start();
724 if (!expect('{')) {
725 auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a 'loop' loop body"_string.release_value_but_fixme_should_propagate_errors(), true);
726 return create<AST::ForLoop>(AST::NameWithPosition {}, AST::NameWithPosition {}, nullptr, move(syntax_error)); // ForLoop null null Block
727 }
728 }
729
730 TemporaryChange controls { m_continuation_controls_allowed, true };
731 auto body = parse_toplevel();
732
733 {
734 auto cbrace_error_start = push_start();
735 if (!expect('}')) {
736 auto error_start = push_start();
737 auto syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a 'loop' loop body"_string.release_value_but_fixme_should_propagate_errors(), true);
738 if (body)
739 body->set_is_syntax_error(*syntax_error);
740 else
741 body = syntax_error;
742 }
743 }
744
745 return create<AST::ForLoop>(AST::NameWithPosition {}, AST::NameWithPosition {}, nullptr, move(body)); // ForLoop null null Block
746}
747
748RefPtr<AST::Node> Parser::parse_if_expr()
749{
750 auto rule_start = push_start();
751 if (!expect("if"sv))
752 return nullptr;
753
754 if (consume_while(is_any_of(" \t\n"sv)).is_empty()) {
755 restore_to(*rule_start);
756 return nullptr;
757 }
758
759 RefPtr<AST::Node> condition;
760 {
761 auto cond_error_start = push_start();
762 condition = parse_or_logical_sequence();
763 if (!condition)
764 condition = create<AST::SyntaxError>("Expected a logical sequence after 'if'"_string.release_value_but_fixme_should_propagate_errors(), true);
765 }
766
767 auto parse_braced_toplevel = [&]() -> RefPtr<AST::Node> {
768 RefPtr<AST::Node> body;
769 {
770 auto obrace_error_start = push_start();
771 if (!expect('{')) {
772 body = create<AST::SyntaxError>("Expected an open brace '{' to start an 'if' true branch"_string.release_value_but_fixme_should_propagate_errors(), true);
773 }
774 }
775
776 if (!body)
777 body = parse_toplevel();
778
779 {
780 auto cbrace_error_start = push_start();
781 if (!expect('}')) {
782 auto error_start = push_start();
783 RefPtr<AST::SyntaxError> syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end an 'if' true branch"_string.release_value_but_fixme_should_propagate_errors(), true);
784 if (body)
785 body->set_is_syntax_error(*syntax_error);
786 else
787 body = syntax_error;
788 }
789 }
790
791 return body;
792 };
793
794 consume_while(is_any_of(" \t\n"sv));
795 auto true_branch = parse_braced_toplevel();
796
797 auto end_before_else = m_offset;
798 auto line_before_else = line();
799 consume_while(is_any_of(" \t\n"sv));
800 Optional<AST::Position> else_position;
801 {
802 auto else_start = push_start();
803 if (expect("else"sv))
804 else_position = AST::Position { else_start->offset, m_offset, else_start->line, line() };
805 else
806 restore_to(end_before_else, line_before_else);
807 }
808
809 if (else_position.has_value()) {
810 consume_while(is_any_of(" \t\n"sv));
811 if (peek() == '{') {
812 auto false_branch = parse_braced_toplevel();
813 return create<AST::IfCond>(else_position, condition.release_nonnull(), move(true_branch), move(false_branch)); // If expr true_branch Else false_branch
814 }
815
816 auto else_if_branch = parse_if_expr();
817 return create<AST::IfCond>(else_position, condition.release_nonnull(), move(true_branch), move(else_if_branch)); // If expr true_branch Else If ...
818 }
819
820 return create<AST::IfCond>(else_position, condition.release_nonnull(), move(true_branch), nullptr); // If expr true_branch
821}
822
823RefPtr<AST::Node> Parser::parse_subshell()
824{
825 auto rule_start = push_start();
826 if (!expect('{'))
827 return nullptr;
828
829 auto body = parse_toplevel();
830
831 {
832 auto cbrace_error_start = push_start();
833 if (!expect('}')) {
834 auto error_start = push_start();
835 RefPtr<AST::SyntaxError> syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a subshell"_string.release_value_but_fixme_should_propagate_errors(), true);
836 if (body)
837 body->set_is_syntax_error(*syntax_error);
838 else
839 body = syntax_error;
840 }
841 }
842
843 return create<AST::Subshell>(move(body));
844}
845
846RefPtr<AST::Node> Parser::parse_match_expr()
847{
848 auto rule_start = push_start();
849 if (!expect("match"sv))
850 return nullptr;
851
852 if (consume_while(is_whitespace).is_empty()) {
853 restore_to(*rule_start);
854 return nullptr;
855 }
856
857 auto match_expression = parse_expression();
858 if (!match_expression) {
859 return create<AST::MatchExpr>(
860 create<AST::SyntaxError>("Expected an expression after 'match'"_string.release_value_but_fixme_should_propagate_errors(), true),
861 String {}, Optional<AST::Position> {}, Vector<AST::MatchEntry> {});
862 }
863
864 consume_while(is_any_of(" \t\n"sv));
865
866 String match_name;
867 Optional<AST::Position> as_position;
868 auto as_start = m_offset;
869 auto as_line = line();
870 if (expect("as"sv)) {
871 as_position = AST::Position { as_start, m_offset, as_line, line() };
872
873 if (consume_while(is_any_of(" \t\n"sv)).is_empty()) {
874 auto node = create<AST::MatchExpr>(
875 match_expression.release_nonnull(),
876 String {}, move(as_position), Vector<AST::MatchEntry> {});
877 node->set_is_syntax_error(create<AST::SyntaxError>("Expected whitespace after 'as' in 'match'"_string.release_value_but_fixme_should_propagate_errors(), true));
878 return node;
879 }
880
881 match_name = String::from_utf8(consume_while(is_word_character)).release_value_but_fixme_should_propagate_errors();
882 if (match_name.is_empty()) {
883 auto node = create<AST::MatchExpr>(
884 match_expression.release_nonnull(),
885 String {}, move(as_position), Vector<AST::MatchEntry> {});
886 node->set_is_syntax_error(create<AST::SyntaxError>("Expected an identifier after 'as' in 'match'"_string.release_value_but_fixme_should_propagate_errors(), true));
887 return node;
888 }
889 }
890
891 consume_while(is_any_of(" \t\n"sv));
892
893 if (!expect('{')) {
894 auto node = create<AST::MatchExpr>(
895 match_expression.release_nonnull(),
896 move(match_name), move(as_position), Vector<AST::MatchEntry> {});
897 node->set_is_syntax_error(create<AST::SyntaxError>("Expected an open brace '{' to start a 'match' entry list"_string.release_value_but_fixme_should_propagate_errors(), true));
898 return node;
899 }
900
901 consume_while(is_any_of(" \t\n"sv));
902
903 Vector<AST::MatchEntry> entries;
904 for (;;) {
905 auto entry = parse_match_entry();
906 consume_while(is_any_of(" \t\n"sv));
907 if (entry.options.visit([](auto& x) { return x.is_empty(); }))
908 break;
909
910 entries.append(move(entry));
911 }
912
913 consume_while(is_any_of(" \t\n"sv));
914
915 if (!expect('}')) {
916 auto node = create<AST::MatchExpr>(
917 match_expression.release_nonnull(),
918 move(match_name), move(as_position), move(entries));
919 node->set_is_syntax_error(create<AST::SyntaxError>("Expected a close brace '}' to end a 'match' entry list"_string.release_value_but_fixme_should_propagate_errors(), true));
920 return node;
921 }
922
923 return create<AST::MatchExpr>(match_expression.release_nonnull(), move(match_name), move(as_position), move(entries));
924}
925
926AST::MatchEntry Parser::parse_match_entry()
927{
928 auto rule_start = push_start();
929
930 Vector<NonnullRefPtr<AST::Node>> patterns;
931 Vector<Regex<ECMA262>> regexps;
932 Vector<AST::Position> pipe_positions;
933 Optional<Vector<String>> match_names;
934 Optional<AST::Position> match_as_position;
935 enum {
936 Regex,
937 Glob,
938 } pattern_kind;
939
940 consume_while(is_any_of(" \t\n"sv));
941
942 auto regex_pattern = parse_regex_pattern();
943 if (regex_pattern.has_value()) {
944 if (auto error = regex_pattern.value().parser_result.error; error != regex::Error::NoError)
945 return { Vector<NonnullRefPtr<AST::Node>> {}, {}, {}, {}, create<AST::SyntaxError>(String::from_utf8(regex::get_error_string(error)).release_value_but_fixme_should_propagate_errors(), false) };
946
947 pattern_kind = Regex;
948 regexps.append(regex_pattern.release_value());
949 } else {
950 auto glob_pattern = parse_match_pattern();
951 if (!glob_pattern)
952 return { Vector<NonnullRefPtr<AST::Node>> {}, {}, {}, {}, create<AST::SyntaxError>("Expected a pattern in 'match' body"_string.release_value_but_fixme_should_propagate_errors(), true) };
953
954 pattern_kind = Glob;
955 patterns.append(glob_pattern.release_nonnull());
956 }
957
958 consume_while(is_any_of(" \t\n"sv));
959
960 auto previous_pipe_start_position = m_offset;
961 auto previous_pipe_start_line = line();
962 RefPtr<AST::SyntaxError> error;
963 while (expect('|')) {
964 pipe_positions.append({ previous_pipe_start_position, m_offset, previous_pipe_start_line, line() });
965 consume_while(is_any_of(" \t\n"sv));
966 switch (pattern_kind) {
967 case Regex: {
968 auto pattern = parse_regex_pattern();
969 if (!pattern.has_value()) {
970 error = create<AST::SyntaxError>("Expected a regex pattern to follow '|' in 'match' body"_string.release_value_but_fixme_should_propagate_errors(), true);
971 break;
972 }
973 regexps.append(pattern.release_value());
974 break;
975 }
976 case Glob: {
977 auto pattern = parse_match_pattern();
978 if (!pattern) {
979 error = create<AST::SyntaxError>("Expected a pattern to follow '|' in 'match' body"_string.release_value_but_fixme_should_propagate_errors(), true);
980 break;
981 }
982 patterns.append(pattern.release_nonnull());
983 break;
984 }
985 }
986
987 consume_while(is_any_of(" \t\n"sv));
988
989 previous_pipe_start_line = line();
990 previous_pipe_start_position = m_offset;
991 }
992
993 consume_while(is_any_of(" \t\n"sv));
994
995 auto as_start_position = m_offset;
996 auto as_start_line = line();
997 if (pattern_kind == Glob && expect("as"sv)) {
998 match_as_position = AST::Position { as_start_position, m_offset, as_start_line, line() };
999 consume_while(is_any_of(" \t\n"sv));
1000 if (!expect('(')) {
1001 if (!error)
1002 error = create<AST::SyntaxError>("Expected an explicit list of identifiers after a pattern 'as'"_string.release_value_but_fixme_should_propagate_errors());
1003 } else {
1004 match_names = Vector<String>();
1005 for (;;) {
1006 consume_while(is_whitespace);
1007 auto name = consume_while(is_word_character);
1008 if (name.is_empty())
1009 break;
1010 match_names->append(String::from_utf8(name).release_value_but_fixme_should_propagate_errors());
1011 }
1012
1013 if (!expect(')')) {
1014 if (!error)
1015 error = create<AST::SyntaxError>("Expected a close paren ')' to end the identifier list of pattern 'as'"_string.release_value_but_fixme_should_propagate_errors(), true);
1016 }
1017 }
1018 consume_while(is_any_of(" \t\n"sv));
1019 }
1020
1021 if (pattern_kind == Regex) {
1022 Vector<String> names;
1023 for (auto& regex : regexps) {
1024 if (names.is_empty()) {
1025 for (auto& name : regex.parser_result.capture_groups)
1026 names.append(String::from_utf8(name).release_value_but_fixme_should_propagate_errors());
1027 } else {
1028 size_t index = 0;
1029 for (auto& name : regex.parser_result.capture_groups) {
1030 if (names.size() <= index) {
1031 names.append(String::from_utf8(name).release_value_but_fixme_should_propagate_errors());
1032 continue;
1033 }
1034
1035 if (names[index] != name.view()) {
1036 if (!error)
1037 error = create<AST::SyntaxError>("Alternative regex patterns must have the same capture groups"_string.release_value_but_fixme_should_propagate_errors(), false);
1038 break;
1039 }
1040 }
1041 }
1042 }
1043 match_names = move(names);
1044 }
1045
1046 if (!expect('{')) {
1047 if (!error)
1048 error = create<AST::SyntaxError>("Expected an open brace '{' to start a match entry body"_string.release_value_but_fixme_should_propagate_errors(), true);
1049 }
1050
1051 auto body = parse_toplevel();
1052
1053 if (!expect('}')) {
1054 if (!error)
1055 error = create<AST::SyntaxError>("Expected a close brace '}' to end a match entry body"_string.release_value_but_fixme_should_propagate_errors(), true);
1056 }
1057
1058 if (body && error)
1059 body->set_is_syntax_error(*error);
1060 else if (error)
1061 body = error;
1062
1063 if (pattern_kind == Glob)
1064 return { move(patterns), move(match_names), move(match_as_position), move(pipe_positions), move(body) };
1065
1066 return { move(regexps), move(match_names), move(match_as_position), move(pipe_positions), move(body) };
1067}
1068
1069RefPtr<AST::Node> Parser::parse_match_pattern()
1070{
1071 return parse_expression();
1072}
1073
1074Optional<Regex<ECMA262>> Parser::parse_regex_pattern()
1075{
1076 auto rule_start = push_start();
1077
1078 auto start = m_offset;
1079 if (!expect("(?:"sv) && !expect("(?<"sv))
1080 return {};
1081
1082 size_t open_parens = 1;
1083 while (open_parens > 0) {
1084 if (at_end())
1085 break;
1086
1087 if (next_is("("sv))
1088 ++open_parens;
1089 else if (next_is(")"sv))
1090 --open_parens;
1091 consume();
1092 }
1093
1094 if (open_parens != 0) {
1095 restore_to(*rule_start);
1096 return {};
1097 }
1098
1099 auto end = m_offset;
1100 auto pattern = m_input.substring_view(start, end - start);
1101 return Regex<ECMA262>(pattern);
1102}
1103
1104RefPtr<AST::Node> Parser::parse_redirection()
1105{
1106 auto rule_start = push_start();
1107
1108 // heredoc entry
1109 if (next_is("<<-"sv) || next_is("<<~"sv))
1110 return nullptr;
1111
1112 auto pipe_fd = 0;
1113 auto number = consume_while(is_digit);
1114 if (number.is_empty()) {
1115 pipe_fd = -1;
1116 } else {
1117 auto fd = number.to_int();
1118 pipe_fd = fd.value_or(-1);
1119 }
1120
1121 switch (peek()) {
1122 case '>': {
1123 consume();
1124 if (peek() == '>') {
1125 consume();
1126 consume_while(is_whitespace);
1127 pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO;
1128 auto path = parse_expression();
1129 if (!path) {
1130 if (!at_end()) {
1131 // Eat a character and hope the problem goes away
1132 consume();
1133 }
1134 path = create<AST::SyntaxError>("Expected a path after redirection"_string.release_value_but_fixme_should_propagate_errors(), true);
1135 }
1136 return create<AST::WriteAppendRedirection>(pipe_fd, path.release_nonnull()); // Redirection WriteAppend
1137 }
1138 if (peek() == '&') {
1139 consume();
1140 // FIXME: 'fd>&-' Syntax not the best. needs discussion.
1141 if (peek() == '-') {
1142 consume();
1143 pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO;
1144 return create<AST::CloseFdRedirection>(pipe_fd); // Redirection CloseFd
1145 }
1146 int dest_pipe_fd = 0;
1147 auto number = consume_while(is_digit);
1148 pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO;
1149 if (number.is_empty()) {
1150 dest_pipe_fd = -1;
1151 } else {
1152 auto fd = number.to_int();
1153 dest_pipe_fd = fd.value_or(-1);
1154 }
1155 auto redir = create<AST::Fd2FdRedirection>(pipe_fd, dest_pipe_fd); // Redirection Fd2Fd
1156 if (dest_pipe_fd == -1)
1157 redir->set_is_syntax_error(*create<AST::SyntaxError>("Expected a file descriptor"_string.release_value_but_fixme_should_propagate_errors()));
1158 return redir;
1159 }
1160 consume_while(is_whitespace);
1161 pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO;
1162 auto path = parse_expression();
1163 if (!path) {
1164 if (!at_end()) {
1165 // Eat a character and hope the problem goes away
1166 consume();
1167 }
1168 path = create<AST::SyntaxError>("Expected a path after redirection"_string.release_value_but_fixme_should_propagate_errors(), true);
1169 }
1170 return create<AST::WriteRedirection>(pipe_fd, path.release_nonnull()); // Redirection Write
1171 }
1172 case '<': {
1173 consume();
1174 enum {
1175 Read,
1176 ReadWrite,
1177 } mode { Read };
1178
1179 if (peek() == '>') {
1180 mode = ReadWrite;
1181 consume();
1182 }
1183
1184 consume_while(is_whitespace);
1185 pipe_fd = pipe_fd >= 0 ? pipe_fd : STDIN_FILENO;
1186 auto path = parse_expression();
1187 if (!path) {
1188 if (!at_end()) {
1189 // Eat a character and hope the problem goes away
1190 consume();
1191 }
1192 path = create<AST::SyntaxError>("Expected a path after redirection"_string.release_value_but_fixme_should_propagate_errors(), true);
1193 }
1194 if (mode == Read)
1195 return create<AST::ReadRedirection>(pipe_fd, path.release_nonnull()); // Redirection Read
1196
1197 return create<AST::ReadWriteRedirection>(pipe_fd, path.release_nonnull()); // Redirection ReadWrite
1198 }
1199 default:
1200 restore_to(*rule_start);
1201 return nullptr;
1202 }
1203}
1204
1205RefPtr<AST::Node> Parser::parse_list_expression()
1206{
1207 consume_while(is_whitespace);
1208
1209 auto rule_start = push_start();
1210 Vector<NonnullRefPtr<AST::Node>> nodes;
1211
1212 do {
1213 auto expr = parse_expression();
1214 if (!expr)
1215 break;
1216 nodes.append(expr.release_nonnull());
1217 } while (!consume_while(is_whitespace).is_empty());
1218
1219 if (nodes.is_empty())
1220 return nullptr;
1221
1222 return create<AST::ListConcatenate>(move(nodes)); // Concatenate List
1223}
1224
1225RefPtr<AST::Node> Parser::parse_expression()
1226{
1227 auto rule_start = push_start();
1228 if (m_rule_start_offsets.size() > max_allowed_nested_rule_depth)
1229 return create<AST::SyntaxError>(String::formatted("Expression nested too deep (max allowed is {})", max_allowed_nested_rule_depth).release_value_but_fixme_should_propagate_errors());
1230
1231 auto starting_char = peek();
1232
1233 auto read_concat = [&](auto&& expr) -> NonnullRefPtr<AST::Node> {
1234 if (is_whitespace(peek()))
1235 return move(expr);
1236
1237 if (auto next_expr = parse_expression())
1238 return create<AST::Juxtaposition>(move(expr), next_expr.release_nonnull());
1239
1240 return move(expr);
1241 };
1242
1243 // Heredocs are expressions, so allow them
1244 if (!(next_is("<<-"sv) || next_is("<<~"sv))) {
1245 if (strchr("&|)} ;<>\n", starting_char) != nullptr)
1246 return nullptr;
1247 }
1248
1249 if (m_extra_chars_not_allowed_in_barewords.contains_slow(starting_char))
1250 return nullptr;
1251
1252 if (m_is_in_brace_expansion_spec && next_is(".."sv))
1253 return nullptr;
1254
1255 if (isdigit(starting_char)) {
1256 ScopedValueRollback offset_rollback { m_offset };
1257
1258 auto redir = parse_redirection();
1259 if (redir)
1260 return nullptr;
1261 }
1262
1263 if (starting_char == '$') {
1264 if (auto variable = parse_variable())
1265 return read_concat(variable.release_nonnull());
1266
1267 if (auto immediate = parse_immediate_expression())
1268 return read_concat(immediate.release_nonnull());
1269
1270 auto inline_exec = parse_evaluate();
1271 if (inline_exec && !inline_exec->is_syntax_error())
1272 return read_concat(inline_exec.release_nonnull());
1273 return inline_exec;
1274 }
1275
1276 if (starting_char == '#')
1277 return parse_comment();
1278
1279 if (starting_char == '(') {
1280 consume();
1281 auto list = parse_list_expression();
1282 if (!expect(')')) {
1283 restore_to(*rule_start);
1284 return nullptr;
1285 }
1286 return read_concat(create<AST::CastToList>(move(list))); // Cast To List
1287 }
1288
1289 if (starting_char == '!' && m_in_interactive_mode) {
1290 if (auto designator = parse_history_designator())
1291 return designator;
1292 }
1293
1294 if (auto composite = parse_string_composite())
1295 return read_concat(composite.release_nonnull());
1296
1297 return nullptr;
1298}
1299
1300RefPtr<AST::Node> Parser::parse_string_composite()
1301{
1302 auto rule_start = push_start();
1303 if (auto string = parse_string()) {
1304 if (auto next_part = parse_string_composite())
1305 return create<AST::Juxtaposition>(string.release_nonnull(), next_part.release_nonnull()); // Concatenate String StringComposite
1306
1307 return string;
1308 }
1309
1310 if (auto variable = parse_variable()) {
1311 if (auto next_part = parse_string_composite())
1312 return create<AST::Juxtaposition>(variable.release_nonnull(), next_part.release_nonnull()); // Concatenate Variable StringComposite
1313
1314 return variable;
1315 }
1316
1317 if (auto glob = parse_glob()) {
1318 if (auto next_part = parse_string_composite())
1319 return create<AST::Juxtaposition>(glob.release_nonnull(), next_part.release_nonnull()); // Concatenate Glob StringComposite
1320
1321 return glob;
1322 }
1323
1324 if (auto expansion = parse_brace_expansion()) {
1325 if (auto next_part = parse_string_composite())
1326 return create<AST::Juxtaposition>(expansion.release_nonnull(), next_part.release_nonnull()); // Concatenate BraceExpansion StringComposite
1327
1328 return expansion;
1329 }
1330
1331 if (auto bareword = parse_bareword()) {
1332 if (auto next_part = parse_string_composite())
1333 return create<AST::Juxtaposition>(bareword.release_nonnull(), next_part.release_nonnull()); // Concatenate Bareword StringComposite
1334
1335 return bareword;
1336 }
1337
1338 if (auto inline_command = parse_evaluate()) {
1339 if (auto next_part = parse_string_composite())
1340 return create<AST::Juxtaposition>(inline_command.release_nonnull(), next_part.release_nonnull()); // Concatenate Execute StringComposite
1341
1342 return inline_command;
1343 }
1344
1345 if (auto heredoc = parse_heredoc_initiation_record()) {
1346 if (auto next_part = parse_string_composite())
1347 return create<AST::Juxtaposition>(heredoc.release_nonnull(), next_part.release_nonnull()); // Concatenate Heredoc StringComposite
1348
1349 return heredoc;
1350 }
1351
1352 return nullptr;
1353}
1354
1355RefPtr<AST::Node> Parser::parse_string()
1356{
1357 auto rule_start = push_start();
1358 if (at_end())
1359 return nullptr;
1360
1361 if (peek() == '"') {
1362 consume();
1363 auto inner = parse_string_inner(StringEndCondition::DoubleQuote);
1364 if (!inner)
1365 inner = create<AST::SyntaxError>("Unexpected EOF in string"_string.release_value_but_fixme_should_propagate_errors(), true);
1366 if (!expect('"')) {
1367 inner = create<AST::DoubleQuotedString>(move(inner));
1368 inner->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating double quote"_string.release_value_but_fixme_should_propagate_errors(), true));
1369 return inner;
1370 }
1371 return create<AST::DoubleQuotedString>(move(inner)); // Double Quoted String
1372 }
1373
1374 if (peek() == '\'') {
1375 consume();
1376 auto text = consume_while(is_not('\''));
1377 bool is_error = false;
1378 if (!expect('\''))
1379 is_error = true;
1380 auto result = create<AST::StringLiteral>(String::from_utf8(text).release_value_but_fixme_should_propagate_errors(), AST::StringLiteral::EnclosureType::SingleQuotes); // String Literal
1381 if (is_error)
1382 result->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating single quote"_string.release_value_but_fixme_should_propagate_errors(), true));
1383 return result;
1384 }
1385
1386 return nullptr;
1387}
1388
1389RefPtr<AST::Node> Parser::parse_string_inner(StringEndCondition condition)
1390{
1391 auto rule_start = push_start();
1392 if (at_end())
1393 return nullptr;
1394
1395 StringBuilder builder;
1396 while (!at_end()) {
1397 if (condition == StringEndCondition::DoubleQuote && peek() == '"') {
1398 break;
1399 }
1400
1401 if (peek() == '\\') {
1402 consume();
1403 if (at_end()) {
1404 break;
1405 }
1406 auto ch = consume();
1407 switch (ch) {
1408 case '\\':
1409 default:
1410 builder.append(ch);
1411 break;
1412 case 'x': {
1413 if (m_input.length() <= m_offset + 2)
1414 break;
1415 auto first_nibble = tolower(consume());
1416 auto second_nibble = tolower(consume());
1417 if (!isxdigit(first_nibble) || !isxdigit(second_nibble)) {
1418 builder.append(first_nibble);
1419 builder.append(second_nibble);
1420 break;
1421 }
1422 builder.append(to_byte(first_nibble, second_nibble));
1423 break;
1424 }
1425 case 'u': {
1426 if (m_input.length() <= m_offset + 8)
1427 break;
1428 size_t counter = 8;
1429 auto chars = consume_while([&](auto) { return counter-- > 0; });
1430 if (auto number = AK::StringUtils::convert_to_uint_from_hex(chars); number.has_value())
1431 builder.append(Utf32View { &number.value(), 1 });
1432 else
1433 builder.append(chars);
1434
1435 break;
1436 }
1437 case 'a':
1438 builder.append('\a');
1439 break;
1440 case 'b':
1441 builder.append('\b');
1442 break;
1443 case 'e':
1444 builder.append('\x1b');
1445 break;
1446 case 'f':
1447 builder.append('\f');
1448 break;
1449 case 'r':
1450 builder.append('\r');
1451 break;
1452 case 'n':
1453 builder.append('\n');
1454 break;
1455 case 't':
1456 builder.append('\t');
1457 break;
1458 }
1459 continue;
1460 }
1461 if (peek() == '$') {
1462 auto string_literal = create<AST::StringLiteral>(builder.to_string().release_value_but_fixme_should_propagate_errors(), AST::StringLiteral::EnclosureType::DoubleQuotes); // String Literal
1463 auto read_concat = [&](auto&& node) {
1464 auto inner = create<AST::StringPartCompose>(
1465 move(string_literal),
1466 move(node)); // Compose String Node
1467
1468 if (auto string = parse_string_inner(condition)) {
1469 return create<AST::StringPartCompose>(move(inner), string.release_nonnull()); // Compose Composition Composition
1470 }
1471
1472 return inner;
1473 };
1474
1475 if (auto variable = parse_variable())
1476 return read_concat(variable.release_nonnull());
1477
1478 if (auto immediate = parse_immediate_expression())
1479 return read_concat(immediate.release_nonnull());
1480
1481 if (auto evaluate = parse_evaluate())
1482 return read_concat(evaluate.release_nonnull());
1483 }
1484
1485 builder.append(consume());
1486 }
1487
1488 return create<AST::StringLiteral>(builder.to_string().release_value_but_fixme_should_propagate_errors(), AST::StringLiteral::EnclosureType::DoubleQuotes); // String Literal
1489}
1490
1491RefPtr<AST::Node> Parser::parse_variable()
1492{
1493 auto rule_start = push_start();
1494 auto ref = parse_variable_ref();
1495
1496 if (!ref)
1497 return nullptr;
1498
1499 auto variable = static_ptr_cast<AST::VariableNode>(ref);
1500 if (auto slice = parse_slice())
1501 variable->set_slice(slice.release_nonnull());
1502
1503 return variable;
1504}
1505
1506RefPtr<AST::Node> Parser::parse_variable_ref()
1507{
1508 auto rule_start = push_start();
1509 if (at_end())
1510 return nullptr;
1511
1512 if (peek() != '$')
1513 return nullptr;
1514
1515 consume();
1516 switch (peek()) {
1517 case '$':
1518 case '?':
1519 case '*':
1520 case '#':
1521 return create<AST::SpecialVariable>(consume()); // Variable Special
1522 default:
1523 break;
1524 }
1525
1526 auto name = consume_while(is_word_character);
1527
1528 if (name.length() == 0) {
1529 restore_to(rule_start->offset, rule_start->line);
1530 return nullptr;
1531 }
1532
1533 return create<AST::SimpleVariable>(String::from_utf8(name).release_value_but_fixme_should_propagate_errors()); // Variable Simple
1534}
1535
1536RefPtr<AST::Slice> Parser::parse_slice()
1537{
1538 auto rule_start = push_start();
1539 if (!next_is("["sv))
1540 return nullptr;
1541
1542 consume(); // [
1543
1544 ScopedValueRollback chars_change { m_extra_chars_not_allowed_in_barewords };
1545 m_extra_chars_not_allowed_in_barewords.append(']');
1546 auto spec = parse_brace_expansion_spec();
1547
1548 RefPtr<AST::SyntaxError> error;
1549
1550 if (peek() != ']')
1551 error = create<AST::SyntaxError>("Expected a close bracket ']' to end a variable slice"_string.release_value_but_fixme_should_propagate_errors());
1552 else
1553 consume();
1554
1555 if (!spec) {
1556 if (error)
1557 spec = move(error);
1558 else
1559 spec = create<AST::SyntaxError>("Expected either a range, or a comma-seprated list of selectors"_string.release_value_but_fixme_should_propagate_errors());
1560 }
1561
1562 auto node = create<AST::Slice>(spec.release_nonnull());
1563 if (error)
1564 node->set_is_syntax_error(*error);
1565 return node;
1566}
1567
1568RefPtr<AST::Node> Parser::parse_evaluate()
1569{
1570 auto rule_start = push_start();
1571 if (at_end())
1572 return nullptr;
1573
1574 if (peek() != '$')
1575 return nullptr;
1576
1577 consume();
1578 if (peek() == '(') {
1579 consume();
1580 auto inner = parse_pipe_sequence();
1581 if (!inner)
1582 inner = create<AST::SyntaxError>("Unexpected EOF in list"_string.release_value_but_fixme_should_propagate_errors(), true);
1583 if (!expect(')'))
1584 inner->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating close paren"_string.release_value_but_fixme_should_propagate_errors(), true));
1585
1586 return create<AST::Execute>(inner.release_nonnull(), true);
1587 }
1588 auto inner = parse_expression();
1589
1590 if (!inner) {
1591 inner = create<AST::SyntaxError>("Expected a command"_string.release_value_but_fixme_should_propagate_errors(), true);
1592 } else {
1593 if (inner->is_list()) {
1594 auto execute_inner = create<AST::Execute>(inner.release_nonnull(), true);
1595 inner = move(execute_inner);
1596 } else {
1597 auto dyn_inner = create<AST::DynamicEvaluate>(inner.release_nonnull());
1598 inner = move(dyn_inner);
1599 }
1600 }
1601
1602 return inner;
1603}
1604
1605RefPtr<AST::Node> Parser::parse_immediate_expression()
1606{
1607 auto rule_start = push_start();
1608 if (at_end())
1609 return nullptr;
1610
1611 if (peek() != '$')
1612 return nullptr;
1613
1614 consume();
1615
1616 if (peek() != '{') {
1617 restore_to(*rule_start);
1618 return nullptr;
1619 }
1620
1621 consume();
1622 consume_while(is_whitespace);
1623
1624 auto function_name_start_offset = current_position();
1625 auto function_name = consume_while(is_word_character);
1626 auto function_name_end_offset = current_position();
1627 AST::Position function_position {
1628 function_name_start_offset.offset,
1629 function_name_end_offset.offset,
1630 function_name_start_offset.line,
1631 function_name_end_offset.line,
1632 };
1633
1634 consume_while(is_whitespace);
1635
1636 Vector<NonnullRefPtr<AST::Node>> arguments;
1637 do {
1638 auto expr = parse_expression();
1639 if (!expr)
1640 break;
1641 arguments.append(expr.release_nonnull());
1642 } while (!consume_while(is_whitespace).is_empty());
1643
1644 auto ending_brace_start_offset = current_position();
1645 if (peek() == '}')
1646 consume();
1647
1648 auto ending_brace_end_offset = current_position();
1649
1650 auto ending_brace_position = ending_brace_start_offset.offset == ending_brace_end_offset.offset
1651 ? Optional<AST::Position> {}
1652 : Optional<AST::Position> {
1653 AST::Position {
1654 ending_brace_start_offset.offset,
1655 ending_brace_end_offset.offset,
1656 ending_brace_start_offset.line,
1657 ending_brace_end_offset.line,
1658 }
1659 };
1660
1661 auto node = create<AST::ImmediateExpression>(
1662 AST::NameWithPosition { String::from_utf8(function_name).release_value_but_fixme_should_propagate_errors(), move(function_position) },
1663 move(arguments),
1664 ending_brace_position);
1665
1666 if (!ending_brace_position.has_value())
1667 node->set_is_syntax_error(create<AST::SyntaxError>("Expected a closing brace '}' to end an immediate expression"_string.release_value_but_fixme_should_propagate_errors(), true));
1668 else if (node->function_name().is_empty())
1669 node->set_is_syntax_error(create<AST::SyntaxError>("Expected an immediate function name"_string.release_value_but_fixme_should_propagate_errors()));
1670
1671 return node;
1672}
1673
1674RefPtr<AST::Node> Parser::parse_history_designator()
1675{
1676 auto rule_start = push_start();
1677
1678 VERIFY(peek() == '!');
1679 consume();
1680
1681 // Event selector
1682 AST::HistorySelector selector;
1683 RefPtr<AST::SyntaxError> syntax_error;
1684 selector.event.kind = AST::HistorySelector::EventKind::StartingStringLookup;
1685 selector.event.text_position = { m_offset, m_offset, m_line, m_line };
1686 selector.word_selector_range = {
1687 AST::HistorySelector::WordSelector {
1688 AST::HistorySelector::WordSelectorKind::Index,
1689 0,
1690 { m_offset, m_offset, m_line, m_line },
1691 nullptr },
1692 AST::HistorySelector::WordSelector {
1693 AST::HistorySelector::WordSelectorKind::Last,
1694 0,
1695 { m_offset, m_offset, m_line, m_line },
1696 nullptr }
1697 };
1698
1699 bool is_word_selector = false;
1700
1701 switch (peek()) {
1702 case ':':
1703 consume();
1704 [[fallthrough]];
1705 case '^':
1706 case '$':
1707 case '*':
1708 is_word_selector = true;
1709 break;
1710 case '!':
1711 consume();
1712 selector.event.kind = AST::HistorySelector::EventKind::IndexFromEnd;
1713 selector.event.index = 0;
1714 selector.event.text = "!"_short_string;
1715 break;
1716 case '?':
1717 consume();
1718 selector.event.kind = AST::HistorySelector::EventKind::ContainingStringLookup;
1719 [[fallthrough]];
1720 default: {
1721 TemporaryChange chars_change { m_extra_chars_not_allowed_in_barewords, { ':', '^', '$', '*' } };
1722
1723 auto bareword = parse_bareword();
1724 if (!bareword || !bareword->is_bareword()) {
1725 restore_to(*rule_start);
1726 return nullptr;
1727 }
1728
1729 selector.event.text = static_ptr_cast<AST::BarewordLiteral>(bareword)->text();
1730 selector.event.text_position = bareword->position();
1731 auto selector_bytes = selector.event.text.bytes();
1732 auto it = selector_bytes.begin();
1733 bool is_negative = false;
1734 if (*it == '-') {
1735 ++it;
1736 is_negative = true;
1737 }
1738 if (it != selector_bytes.end() && all_of(it, selector_bytes.end(), is_digit)) {
1739 if (is_negative)
1740 selector.event.kind = AST::HistorySelector::EventKind::IndexFromEnd;
1741 else
1742 selector.event.kind = AST::HistorySelector::EventKind::IndexFromStart;
1743 auto number = abs(selector.event.text.bytes_as_string_view().to_int().value_or(0));
1744 if (number != 0)
1745 selector.event.index = number - 1;
1746 else
1747 syntax_error = create<AST::SyntaxError>("History entry index value invalid or out of range"_string.release_value_but_fixme_should_propagate_errors());
1748 }
1749 if (":^$*"sv.contains(peek())) {
1750 is_word_selector = true;
1751 if (peek() == ':')
1752 consume();
1753 }
1754 }
1755 }
1756
1757 if (!is_word_selector) {
1758 auto node = create<AST::HistoryEvent>(move(selector));
1759 if (syntax_error)
1760 node->set_is_syntax_error(*syntax_error);
1761 return node;
1762 }
1763
1764 // Word selectors
1765 auto parse_word_selector = [&]() -> Optional<AST::HistorySelector::WordSelector> {
1766 auto c = peek();
1767 AST::HistorySelector::WordSelectorKind word_selector_kind;
1768 ssize_t offset = -1;
1769 if (isdigit(c)) {
1770 auto num = consume_while(is_digit);
1771 auto value = num.to_uint();
1772 if (!value.has_value())
1773 return {};
1774 word_selector_kind = AST::HistorySelector::WordSelectorKind::Index;
1775 offset = value.value();
1776 } else if (c == '^') {
1777 consume();
1778 word_selector_kind = AST::HistorySelector::WordSelectorKind::Index;
1779 offset = 1;
1780 } else if (c == '$') {
1781 consume();
1782 word_selector_kind = AST::HistorySelector::WordSelectorKind::Last;
1783 offset = 0;
1784 }
1785 if (offset == -1)
1786 return {};
1787 return AST::HistorySelector::WordSelector {
1788 word_selector_kind,
1789 static_cast<size_t>(offset),
1790 { m_rule_start_offsets.last(), m_offset, m_rule_start_lines.last(), line() },
1791 syntax_error
1792 };
1793 };
1794
1795 auto make_word_selector = [&](AST::HistorySelector::WordSelectorKind word_selector_kind, size_t offset) {
1796 return AST::HistorySelector::WordSelector {
1797 word_selector_kind,
1798 offset,
1799 { m_rule_start_offsets.last(), m_offset, m_rule_start_lines.last(), line() },
1800 syntax_error
1801 };
1802 };
1803
1804 auto first_char = peek();
1805 if (!(is_digit(first_char) || "^$-*"sv.contains(first_char))) {
1806 if (!syntax_error)
1807 syntax_error = create<AST::SyntaxError>("Expected a word selector after ':' in a history event designator"_string.release_value_but_fixme_should_propagate_errors(), true);
1808 } else if (first_char == '*') {
1809 consume();
1810 selector.word_selector_range.start = make_word_selector(AST::HistorySelector::WordSelectorKind::Index, 1);
1811 selector.word_selector_range.end = make_word_selector(AST::HistorySelector::WordSelectorKind::Last, 0);
1812 } else if (first_char == '-') {
1813 consume();
1814 selector.word_selector_range.start = make_word_selector(AST::HistorySelector::WordSelectorKind::Index, 0);
1815 auto last_selector = parse_word_selector();
1816 if (!last_selector.has_value())
1817 selector.word_selector_range.end = make_word_selector(AST::HistorySelector::WordSelectorKind::Last, 1);
1818 else
1819 selector.word_selector_range.end = last_selector.release_value();
1820 } else {
1821 auto first_selector = parse_word_selector();
1822 // peek() should be a digit, ^, or $ here, so this should always have value.
1823 VERIFY(first_selector.has_value());
1824 selector.word_selector_range.start = first_selector.release_value();
1825 if (peek() == '-') {
1826 consume();
1827 auto last_selector = parse_word_selector();
1828 if (last_selector.has_value()) {
1829 selector.word_selector_range.end = last_selector.release_value();
1830 } else {
1831 selector.word_selector_range.end = make_word_selector(AST::HistorySelector::WordSelectorKind::Last, 1);
1832 }
1833 } else if (peek() == '*') {
1834 consume();
1835 selector.word_selector_range.end = make_word_selector(AST::HistorySelector::WordSelectorKind::Last, 0);
1836 } else {
1837 selector.word_selector_range.end.clear();
1838 }
1839 }
1840
1841 auto node = create<AST::HistoryEvent>(move(selector));
1842 if (syntax_error)
1843 node->set_is_syntax_error(*syntax_error);
1844 return node;
1845}
1846
1847RefPtr<AST::Node> Parser::parse_comment()
1848{
1849 if (at_end())
1850 return nullptr;
1851
1852 if (peek() != '#')
1853 return nullptr;
1854
1855 consume();
1856 auto text = consume_while(is_not('\n'));
1857 return create<AST::Comment>(String::from_utf8(text).release_value_but_fixme_should_propagate_errors()); // Comment
1858}
1859
1860RefPtr<AST::Node> Parser::parse_bareword()
1861{
1862 auto rule_start = push_start();
1863 StringBuilder builder;
1864 auto is_acceptable_bareword_character = [&](char c) {
1865 return strchr("\\\"'*$&#|(){} ?;<>\n", c) == nullptr
1866 && !m_extra_chars_not_allowed_in_barewords.contains_slow(c);
1867 };
1868 while (!at_end()) {
1869 char ch = peek();
1870 if (ch == '\\') {
1871 consume();
1872 if (!at_end()) {
1873 ch = consume();
1874 if (is_acceptable_bareword_character(ch))
1875 builder.append('\\');
1876 }
1877 builder.append(ch);
1878 continue;
1879 }
1880
1881 if (m_is_in_brace_expansion_spec && next_is(".."sv)) {
1882 // Don't eat '..' in a brace expansion spec.
1883 break;
1884 }
1885
1886 if (is_acceptable_bareword_character(ch)) {
1887 builder.append(consume());
1888 continue;
1889 }
1890
1891 break;
1892 }
1893
1894 if (builder.is_empty())
1895 return nullptr;
1896
1897 auto current_end = m_offset;
1898 auto current_line = line();
1899 auto string = builder.to_string().release_value_but_fixme_should_propagate_errors();
1900 if (string.starts_with('~')) {
1901 String username;
1902 RefPtr<AST::Node> tilde, text;
1903
1904 auto first_slash_index = string.find_byte_offset('/');
1905 if (first_slash_index.has_value()) {
1906 username = string.substring_from_byte_offset(1, *first_slash_index - 1).release_value_but_fixme_should_propagate_errors();
1907 string = string.substring_from_byte_offset(*first_slash_index).release_value_but_fixme_should_propagate_errors();
1908 } else {
1909 username = string.substring_from_byte_offset(1).release_value_but_fixme_should_propagate_errors();
1910 string = {};
1911 }
1912
1913 // Synthesize a Tilde Node with the correct positioning information.
1914 {
1915 restore_to(rule_start->offset, rule_start->line);
1916 auto ch = consume();
1917 VERIFY(ch == '~');
1918 auto username_length = username.bytes_as_string_view().length();
1919 tilde = create<AST::Tilde>(move(username));
1920 // Consume the username (if any)
1921 for (size_t i = 0; i < username_length; ++i)
1922 consume();
1923 }
1924
1925 if (string.is_empty())
1926 return tilde;
1927
1928 // Synthesize a BarewordLiteral Node with the correct positioning information.
1929 {
1930 auto text_start = push_start();
1931 restore_to(current_end, current_line);
1932 text = create<AST::BarewordLiteral>(move(string));
1933 }
1934
1935 return create<AST::Juxtaposition>(tilde.release_nonnull(), text.release_nonnull()); // Juxtaposition Variable Bareword
1936 }
1937
1938 if (string.starts_with_bytes("\\~"sv)) {
1939 // Un-escape the tilde, but only at the start (where it would be an expansion)
1940 string = string.substring_from_byte_offset(1).release_value_but_fixme_should_propagate_errors();
1941 }
1942
1943 return create<AST::BarewordLiteral>(move(string)); // Bareword Literal
1944}
1945
1946RefPtr<AST::Node> Parser::parse_glob()
1947{
1948 auto rule_start = push_start();
1949 auto bareword_part = parse_bareword();
1950
1951 if (at_end())
1952 return bareword_part;
1953
1954 char ch = peek();
1955 if (ch == '*' || ch == '?') {
1956 auto saved_offset = save_offset();
1957 consume();
1958 StringBuilder textbuilder;
1959 if (bareword_part) {
1960 StringView text;
1961 if (bareword_part->is_bareword()) {
1962 auto bareword = static_cast<AST::BarewordLiteral*>(bareword_part.ptr());
1963 text = bareword->text();
1964 } else {
1965 // FIXME: Allow composition of tilde+bareword with globs: '~/foo/bar/baz*'
1966 restore_to(saved_offset.offset, saved_offset.line);
1967 bareword_part->set_is_syntax_error(*create<AST::SyntaxError>(String::formatted("Unexpected {} inside a glob", bareword_part->class_name()).release_value_but_fixme_should_propagate_errors()));
1968 return bareword_part;
1969 }
1970 textbuilder.append(text);
1971 }
1972
1973 textbuilder.append(ch);
1974
1975 auto glob_after = parse_glob();
1976 if (glob_after) {
1977 if (glob_after->is_glob()) {
1978 auto glob = static_cast<AST::Glob*>(glob_after.ptr());
1979 textbuilder.append(glob->text());
1980 } else if (glob_after->is_bareword()) {
1981 auto bareword = static_cast<AST::BarewordLiteral*>(glob_after.ptr());
1982 textbuilder.append(bareword->text());
1983 } else if (glob_after->is_tilde()) {
1984 auto bareword = static_cast<AST::Tilde*>(glob_after.ptr());
1985 textbuilder.append('~');
1986 textbuilder.append(bareword->text());
1987 } else {
1988 return create<AST::SyntaxError>(String::formatted("Invalid node '{}' in glob position, escape shell special characters", glob_after->class_name()).release_value_but_fixme_should_propagate_errors());
1989 }
1990 }
1991
1992 return create<AST::Glob>(textbuilder.to_string().release_value_but_fixme_should_propagate_errors()); // Glob
1993 }
1994
1995 return bareword_part;
1996}
1997
1998RefPtr<AST::Node> Parser::parse_brace_expansion()
1999{
2000 auto rule_start = push_start();
2001
2002 if (!expect('{'))
2003 return nullptr;
2004
2005 if (auto spec = parse_brace_expansion_spec()) {
2006 if (!expect('}'))
2007 spec->set_is_syntax_error(create<AST::SyntaxError>("Expected a close brace '}' to end a brace expansion"_string.release_value_but_fixme_should_propagate_errors(), true));
2008
2009 return spec;
2010 }
2011
2012 restore_to(*rule_start);
2013 return nullptr;
2014}
2015
2016RefPtr<AST::Node> Parser::parse_brace_expansion_spec()
2017{
2018 TemporaryChange is_in_brace_expansion { m_is_in_brace_expansion_spec, true };
2019 ScopedValueRollback chars_change { m_extra_chars_not_allowed_in_barewords };
2020
2021 m_extra_chars_not_allowed_in_barewords.append(',');
2022
2023 auto rule_start = push_start();
2024 Vector<NonnullRefPtr<AST::Node>> subexpressions;
2025
2026 if (next_is(","sv)) {
2027 // Note that we don't consume the ',' here.
2028 subexpressions.append(create<AST::StringLiteral>(String {}, AST::StringLiteral::EnclosureType::None));
2029 } else {
2030 auto start_expr = parse_expression();
2031 if (start_expr) {
2032 if (expect(".."sv)) {
2033 if (auto end_expr = parse_expression()) {
2034 if (end_expr->position().start_offset != start_expr->position().end_offset + 2)
2035 end_expr->set_is_syntax_error(create<AST::SyntaxError>("Expected no whitespace between '..' and the following expression in brace expansion"_string.release_value_but_fixme_should_propagate_errors()));
2036
2037 return create<AST::Range>(start_expr.release_nonnull(), end_expr.release_nonnull());
2038 }
2039
2040 return create<AST::Range>(start_expr.release_nonnull(), create<AST::SyntaxError>("Expected an expression to end range brace expansion with"_string.release_value_but_fixme_should_propagate_errors(), true));
2041 }
2042 }
2043
2044 if (start_expr)
2045 subexpressions.append(start_expr.release_nonnull());
2046 }
2047
2048 while (expect(',')) {
2049 auto expr = parse_expression();
2050 if (expr) {
2051 subexpressions.append(expr.release_nonnull());
2052 } else {
2053 subexpressions.append(create<AST::StringLiteral>(String {}, AST::StringLiteral::EnclosureType::None));
2054 }
2055 }
2056
2057 if (subexpressions.is_empty())
2058 return nullptr;
2059
2060 return create<AST::BraceExpansion>(move(subexpressions));
2061}
2062
2063RefPtr<AST::Node> Parser::parse_heredoc_initiation_record()
2064{
2065 if (!next_is("<<"sv))
2066 return nullptr;
2067
2068 auto rule_start = push_start();
2069
2070 // '<' '<'
2071 consume();
2072 consume();
2073
2074 HeredocInitiationRecord record;
2075 record.end = "<error>"_string.release_value_but_fixme_should_propagate_errors();
2076
2077 RefPtr<AST::SyntaxError> syntax_error_node;
2078
2079 // '-' | '~'
2080 switch (peek()) {
2081 case '-':
2082 record.deindent = false;
2083 consume();
2084 break;
2085 case '~':
2086 record.deindent = true;
2087 consume();
2088 break;
2089 default:
2090 restore_to(*rule_start);
2091 return nullptr;
2092 }
2093
2094 // StringLiteral | bareword
2095 if (auto bareword = parse_bareword()) {
2096 if (!bareword->is_bareword()) {
2097 syntax_error_node = create<AST::SyntaxError>(String::formatted("Expected a bareword or a quoted string, not {}", bareword->class_name()).release_value_but_fixme_should_propagate_errors());
2098 } else {
2099 if (bareword->is_syntax_error())
2100 syntax_error_node = bareword->syntax_error_node();
2101 else
2102 record.end = static_cast<AST::BarewordLiteral*>(bareword.ptr())->text();
2103 }
2104
2105 record.interpolate = true;
2106 } else if (peek() == '\'') {
2107 consume();
2108 auto text = consume_while(is_not('\''));
2109 bool is_error = false;
2110 if (!expect('\''))
2111 is_error = true;
2112 if (is_error)
2113 syntax_error_node = create<AST::SyntaxError>("Expected a terminating single quote"_string.release_value_but_fixme_should_propagate_errors(), true);
2114
2115 record.end = String::from_utf8(text).release_value_but_fixme_should_propagate_errors();
2116 record.interpolate = false;
2117 } else {
2118 syntax_error_node = create<AST::SyntaxError>("Expected a bareword or a single-quoted string literal for heredoc end key"_string.release_value_but_fixme_should_propagate_errors(), true);
2119 }
2120
2121 auto node = create<AST::Heredoc>(record.end, record.interpolate, record.deindent);
2122 if (syntax_error_node)
2123 node->set_is_syntax_error(*syntax_error_node);
2124 else
2125 node->set_is_syntax_error(*create<AST::SyntaxError>(String::formatted("Expected heredoc contents for heredoc with end key '{}'", node->end()).release_value_but_fixme_should_propagate_errors(), true));
2126
2127 record.node = node;
2128 m_heredoc_initiations.append(move(record));
2129
2130 return node;
2131}
2132
2133bool Parser::parse_heredoc_entries()
2134{
2135 auto heredocs = move(m_heredoc_initiations);
2136 m_heredoc_initiations.clear();
2137 // Try to parse heredoc entries, as reverse recorded in the initiation records
2138 for (auto& record : heredocs) {
2139 auto rule_start = push_start();
2140 if (m_rule_start_offsets.size() > max_allowed_nested_rule_depth) {
2141 record.node->set_is_syntax_error(*create<AST::SyntaxError>(String::formatted("Expression nested too deep (max allowed is {})", max_allowed_nested_rule_depth).release_value_but_fixme_should_propagate_errors()));
2142 continue;
2143 }
2144 bool found_key = false;
2145 if (!record.interpolate) {
2146 // Since no interpolation is allowed, just read lines until we hit the key
2147 Optional<Offset> last_line_offset;
2148 for (;;) {
2149 if (at_end())
2150 break;
2151 if (peek() == '\n')
2152 consume();
2153 last_line_offset = current_position();
2154 auto line = consume_while(is_not('\n'));
2155 if (peek() == '\n')
2156 consume();
2157 if (line.trim_whitespace() == record.end) {
2158 found_key = true;
2159 break;
2160 }
2161 }
2162
2163 if (!last_line_offset.has_value())
2164 last_line_offset = current_position();
2165 // Now just wrap it in a StringLiteral and set it as the node's contents
2166 auto node = create<AST::StringLiteral>(
2167 String::from_utf8(m_input.substring_view(rule_start->offset, last_line_offset->offset - rule_start->offset)).release_value_but_fixme_should_propagate_errors(),
2168 AST::StringLiteral::EnclosureType::None);
2169 if (!found_key)
2170 node->set_is_syntax_error(*create<AST::SyntaxError>(String::formatted("Expected to find the heredoc key '{}', but found Eof", record.end).release_value_but_fixme_should_propagate_errors(), true));
2171 record.node->set_contents(move(node));
2172 } else {
2173 // Interpolation is allowed, so we're going to read doublequoted string innards
2174 // until we find a line that contains the key
2175 auto end_condition = move(m_end_condition);
2176 found_key = false;
2177 set_end_condition(make<Function<bool()>>([this, end = record.end, &found_key] {
2178 if (found_key)
2179 return true;
2180 auto offset = current_position();
2181 auto cond = move(m_end_condition);
2182 ScopeGuard guard {
2183 [&] {
2184 m_end_condition = move(cond);
2185 }
2186 };
2187 if (peek() == '\n') {
2188 consume();
2189 auto line = consume_while(is_not('\n'));
2190 if (peek() == '\n')
2191 consume();
2192 if (line.trim_whitespace() == end) {
2193 restore_to(offset.offset, offset.line);
2194 found_key = true;
2195 return true;
2196 }
2197 }
2198 restore_to(offset.offset, offset.line);
2199 return false;
2200 }));
2201
2202 auto expr = parse_string_inner(StringEndCondition::Heredoc);
2203 set_end_condition(move(end_condition));
2204
2205 if (found_key) {
2206 auto offset = current_position();
2207 if (peek() == '\n')
2208 consume();
2209 auto line = consume_while(is_not('\n'));
2210 if (peek() == '\n')
2211 consume();
2212 if (line.trim_whitespace() != record.end)
2213 restore_to(offset.offset, offset.line);
2214 }
2215
2216 if (!expr && found_key) {
2217 expr = create<AST::StringLiteral>(String {}, AST::StringLiteral::EnclosureType::None);
2218 } else if (!expr) {
2219 expr = create<AST::SyntaxError>(String::formatted("Expected to find a valid string inside a heredoc (with end key '{}')", record.end).release_value_but_fixme_should_propagate_errors(), true);
2220 } else if (!found_key) {
2221 expr->set_is_syntax_error(*create<AST::SyntaxError>(String::formatted("Expected to find the heredoc key '{}'", record.end).release_value_but_fixme_should_propagate_errors(), true));
2222 }
2223
2224 record.node->set_contents(create<AST::DoubleQuotedString>(move(expr)));
2225 }
2226 }
2227 return true;
2228}
2229
2230StringView Parser::consume_while(Function<bool(char)> condition)
2231{
2232 if (at_end())
2233 return {};
2234
2235 auto start_offset = m_offset;
2236
2237 while (!at_end() && condition(peek()))
2238 consume();
2239
2240 return m_input.substring_view(start_offset, m_offset - start_offset);
2241}
2242
2243bool Parser::next_is(StringView next)
2244{
2245 auto start = current_position();
2246 auto res = expect(next);
2247 restore_to(start.offset, start.line);
2248 return res;
2249}
2250
2251}