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