Coverage Report

Created: 2026-11-13 08:07

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