Coverage Report

Created: 2026-07-25 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibShell/PosixParser.h
Line
Count
Source
1
/*
2
 * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#pragma once
8
9
#include <LibShell/AST.h>
10
#include <LibShell/PosixLexer.h>
11
12
namespace Shell::Posix {
13
14
class Parser {
15
public:
16
    Parser(StringView input, bool interactive = false, Optional<Reduction> starting_reduction = {})
17
715k
        : m_lexer(input)
18
715k
        , m_in_interactive_mode(interactive)
19
715k
        , m_eof_token(Token::eof())
20
715k
    {
21
715k
        (void)fill_token_buffer(starting_reduction);
22
715k
    }
23
24
    enum class AllowNewlines {
25
        No,
26
        Yes,
27
    };
28
29
    RefPtr<AST::Node> parse();
30
    RefPtr<AST::Node> parse_word_list(AllowNewlines = AllowNewlines::No);
31
32
    struct Error {
33
        ByteString message;
34
        Optional<AST::Position> position;
35
    };
36
0
    auto& errors() const { return m_errors; }
37
38
private:
39
    ErrorOr<Optional<Token>> next_expanded_token(Optional<Reduction> starting_reduction = {});
40
    Vector<Token> perform_expansions(Vector<Token> tokens);
41
    ErrorOr<void> fill_token_buffer(Optional<Reduction> starting_reduction = {});
42
    void handle_heredoc_contents();
43
44
    Token const& peek()
45
139M
    {
46
139M
        if (eof())
47
15.0M
            return m_eof_token;
48
124M
        handle_heredoc_contents();
49
124M
        return m_token_buffer[m_token_index];
50
139M
    }
51
    Token const& consume()
52
9.92M
    {
53
9.92M
        if (eof())
54
2.04k
            return m_eof_token;
55
9.92M
        handle_heredoc_contents();
56
9.92M
        return m_token_buffer[m_token_index++];
57
9.92M
    }
58
    void skip()
59
2.64M
    {
60
2.64M
        if (eof())
61
0
            return;
62
2.64M
        handle_heredoc_contents();
63
2.64M
        m_token_index++;
64
2.64M
    }
65
    bool eof() const
66
292M
    {
67
292M
        return m_token_index == m_token_buffer.size() || m_token_buffer[m_token_index].type == Token::Type::Eof;
68
292M
    }
69
70
    struct CaseItemsResult {
71
        Vector<AST::Position> pipe_positions;
72
        Vector<NonnullRefPtr<AST::Node>> nodes;
73
    };
74
75
    ErrorOr<RefPtr<AST::Node>> parse_complete_command();
76
    ErrorOr<RefPtr<AST::Node>> parse_list();
77
    ErrorOr<RefPtr<AST::Node>> parse_and_or();
78
    ErrorOr<RefPtr<AST::Node>> parse_pipeline();
79
    ErrorOr<RefPtr<AST::Node>> parse_pipe_sequence(bool is_negated);
80
    ErrorOr<RefPtr<AST::Node>> parse_command();
81
    ErrorOr<RefPtr<AST::Node>> parse_compound_command();
82
    ErrorOr<RefPtr<AST::Node>> parse_subshell();
83
    ErrorOr<RefPtr<AST::Node>> parse_compound_list();
84
    ErrorOr<RefPtr<AST::Node>> parse_term();
85
    ErrorOr<RefPtr<AST::Node>> parse_for_clause();
86
    ErrorOr<RefPtr<AST::Node>> parse_case_clause();
87
    ErrorOr<RefPtr<AST::Node>> parse_if_clause();
88
    ErrorOr<RefPtr<AST::Node>> parse_while_clause();
89
    ErrorOr<RefPtr<AST::Node>> parse_until_clause();
90
    ErrorOr<RefPtr<AST::Node>> parse_function_definition();
91
    ErrorOr<RefPtr<AST::Node>> parse_function_body();
92
    ErrorOr<RefPtr<AST::Node>> parse_brace_group();
93
    ErrorOr<RefPtr<AST::Node>> parse_do_group();
94
    ErrorOr<RefPtr<AST::Node>> parse_simple_command();
95
    ErrorOr<RefPtr<AST::Node>> parse_prefix();
96
    ErrorOr<RefPtr<AST::Node>> parse_suffix();
97
    ErrorOr<RefPtr<AST::Node>> parse_io_redirect();
98
    ErrorOr<RefPtr<AST::Node>> parse_redirect_list();
99
    ErrorOr<RefPtr<AST::Node>> parse_io_file(AST::Position, Optional<int> fd);
100
    ErrorOr<RefPtr<AST::Node>> parse_io_here(AST::Position, Optional<int> fd);
101
    ErrorOr<RefPtr<AST::Node>> parse_word();
102
    ErrorOr<RefPtr<AST::Node>> parse_bash_like_list();
103
    ErrorOr<CaseItemsResult> parse_case_list();
104
105
    template<typename... Ts>
106
    void error(Token const& token, CheckedFormatString<Ts...> fmt, Ts&&... args)
107
942k
    {
108
942k
        m_errors.append(Error {
109
942k
            ByteString::formatted(fmt.view(), forward<Ts>(args)...),
110
942k
            token.position,
111
942k
        });
112
942k
    }
void Shell::Posix::Parser::error<AK::StringView&>(Shell::Posix::Token const&, AK::Format::Detail::CheckedFormatString<AK::Detail::__IdentityType<AK::StringView&>::Type>, AK::StringView&)
Line
Count
Source
107
60
    {
108
60
        m_errors.append(Error {
109
60
            ByteString::formatted(fmt.view(), forward<Ts>(args)...),
110
60
            token.position,
111
60
        });
112
60
    }
void Shell::Posix::Parser::error<AK::String&>(Shell::Posix::Token const&, AK::Format::Detail::CheckedFormatString<AK::Detail::__IdentityType<AK::String&>::Type>, AK::String&)
Line
Count
Source
107
309
    {
108
309
        m_errors.append(Error {
109
309
            ByteString::formatted(fmt.view(), forward<Ts>(args)...),
110
309
            token.position,
111
309
        });
112
309
    }
void Shell::Posix::Parser::error<AK::StringView>(Shell::Posix::Token const&, AK::Format::Detail::CheckedFormatString<AK::Detail::__IdentityType<AK::StringView>::Type>, AK::StringView&&)
Line
Count
Source
107
941k
    {
108
941k
        m_errors.append(Error {
109
941k
            ByteString::formatted(fmt.view(), forward<Ts>(args)...),
110
941k
            token.position,
111
941k
        });
112
941k
    }
113
114
    Lexer m_lexer;
115
    bool m_in_interactive_mode { false };
116
    Vector<Token, 2> m_token_buffer;
117
    size_t m_token_index { 0 };
118
    Vector<Token> m_previous_token_buffer;
119
120
    Vector<Error> m_errors;
121
    HashMap<String, NonnullRefPtr<AST::Heredoc>> m_unprocessed_heredoc_entries;
122
123
    Token m_eof_token;
124
125
    bool m_disallow_command_prefix { true };
126
};
127
128
}