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