Coverage Report

Created: 2026-09-01 06:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/jsonnet/core/lexer.cpp
Line
Count
Source
1
/*
2
Copyright 2015 Google Inc. All rights reserved.
3
4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7
8
    http://www.apache.org/licenses/LICENSE-2.0
9
10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16
17
#include <cassert>
18
19
#include <map>
20
#include <sstream>
21
#include <string>
22
23
#include "lexer.h"
24
#include "static_error.h"
25
#include "unicode.h"
26
27
namespace jsonnet::internal {
28
29
static const std::vector<std::string> EMPTY;
30
31
/** Is the char whitespace (excluding \n). */
32
static bool is_horz_ws(char c)
33
609M
{
34
609M
    return c == ' ' || c == '\t' || c == '\r';
35
609M
}
36
37
/** Is the char whitespace. */
38
static bool is_ws(char c)
39
550M
{
40
550M
    return c == '\n' || is_horz_ws(c);
41
550M
}
42
43
/** Strip whitespace from both ends of a string, but only up to margin on the left hand side. */
44
static std::string strip_ws(const std::string &s, unsigned margin)
45
11.6M
{
46
11.6M
    if (s.size() == 0)
47
9.88M
        return s;  // Avoid underflow below.
48
1.78M
    size_t i = 0;
49
5.70M
    while (i < s.length() && is_horz_ws(s[i]) && i < margin)
50
3.92M
        i++;
51
1.78M
    size_t j = s.size();
52
4.98M
    while (j > i && is_horz_ws(s[j - 1])) {
53
3.20M
        j--;
54
3.20M
    }
55
1.78M
    return std::string(&s[i], &s[j]);
56
11.6M
}
57
58
/** Split a string by \n and also strip left (up to margin) & right whitespace from each line. */
59
static std::vector<std::string> line_split(const std::string &s, unsigned margin)
60
170k
{
61
170k
    std::vector<std::string> ret;
62
170k
    std::stringstream ss;
63
85.2M
    for (size_t i = 0; i < s.length(); ++i) {
64
85.0M
        if (s[i] == '\n') {
65
11.4M
            ret.emplace_back(strip_ws(ss.str(), margin));
66
11.4M
            ss.str("");
67
73.5M
        } else {
68
73.5M
            ss << s[i];
69
73.5M
        }
70
85.0M
    }
71
170k
    ret.emplace_back(strip_ws(ss.str(), margin));
72
170k
    return ret;
73
170k
}
74
75
/** Consume whitespace.
76
 *
77
 * Return number of \n and number of spaces after last \n.  Convert \t to spaces.
78
 */
79
static void lex_ws(const char *&c, unsigned &new_lines, unsigned &indent, const char *&line_start,
80
                   unsigned long &line_number)
81
236M
{
82
236M
    indent = 0;
83
236M
    new_lines = 0;
84
550M
    for (; *c != '\0' && is_ws(*c); c++) {
85
313M
        switch (*c) {
86
795k
            case '\r':
87
                // Ignore.
88
795k
                break;
89
90
36.4M
            case '\n':
91
36.4M
                indent = 0;
92
36.4M
                new_lines++;
93
36.4M
                line_number++;
94
36.4M
                line_start = c + 1;
95
36.4M
                break;
96
97
276M
            case ' ': indent += 1; break;
98
99
            // This only works for \t at the beginning of lines, but we strip it everywhere else
100
            // anyway.  The only case where this will cause a problem is spaces followed by \t
101
            // at the beginning of a line.  However that is rare, ill-advised, and if re-indentation
102
            // is enabled it will be fixed later.
103
48.0k
            case '\t': indent += 8; break;
104
313M
        }
105
313M
    }
106
236M
}
107
108
/**
109
# Consume all text until the end of the line, return number of newlines after that and indent
110
*/
111
static void lex_until_newline(const char *&c, std::string &text, unsigned &blanks, unsigned &indent,
112
                              const char *&line_start, unsigned long &line_number)
113
6.22M
{
114
6.22M
    const char *original_c = c;
115
6.22M
    const char *last_non_space = c;
116
91.5M
    for (; *c != '\0' && *c != '\n'; c++) {
117
85.2M
        if (!is_horz_ws(*c))
118
73.1M
            last_non_space = c;
119
85.2M
    }
120
6.22M
    text = std::string(original_c, last_non_space - original_c + 1);
121
    // Consume subsequent whitespace including the '\n'.
122
6.22M
    unsigned new_lines;
123
6.22M
    lex_ws(c, new_lines, indent, line_start, line_number);
124
6.22M
    blanks = new_lines == 0 ? 0 : new_lines - 1;
125
6.22M
}
126
127
static bool is_upper(char c)
128
656M
{
129
656M
    return c >= 'A' && c <= 'Z';
130
656M
}
131
132
static bool is_lower(char c)
133
645M
{
134
645M
    return c >= 'a' && c <= 'z';
135
645M
}
136
137
static bool is_number(char c)
138
118M
{
139
118M
    return c >= '0' && c <= '9';
140
118M
}
141
142
static bool is_identifier_first(char c)
143
656M
{
144
656M
    return is_upper(c) || is_lower(c) || c == '_';
145
656M
}
146
147
static bool is_identifier(char c)
148
528M
{
149
528M
    return is_identifier_first(c) || is_number(c);
150
528M
}
151
152
static bool is_symbol(char c)
153
112M
{
154
112M
    switch (c) {
155
2.54M
        case '!':
156
2.81M
        case '$':
157
14.7M
        case ':':
158
15.4M
        case '~':
159
31.2M
        case '+':
160
33.3M
        case '-':
161
35.5M
        case '&':
162
36.8M
        case '|':
163
36.8M
        case '^':
164
57.9M
        case '=':
165
59.9M
        case '<':
166
62.6M
        case '>':
167
79.7M
        case '*':
168
81.9M
        case '/':
169
83.0M
        case '%': return true;
170
112M
    }
171
29.4M
    return false;
172
112M
}
173
174
11.6M
bool allowed_at_end_of_operator(char c) {
175
11.6M
    switch (c) {
176
3.09M
        case '+':
177
3.35M
        case '-':
178
3.98M
        case '~':
179
4.59M
        case '!':
180
4.71M
        case '$': return false;
181
11.6M
    }
182
6.91M
    return true;
183
11.6M
}
184
185
static const std::map<std::string, Token::Kind> keywords = {
186
    {"assert", Token::ASSERT},
187
    {"else", Token::ELSE},
188
    {"error", Token::ERROR},
189
    {"false", Token::FALSE},
190
    {"for", Token::FOR},
191
    {"function", Token::FUNCTION},
192
    {"if", Token::IF},
193
    {"import", Token::IMPORT},
194
    {"importstr", Token::IMPORTSTR},
195
    {"importbin", Token::IMPORTBIN},
196
    {"in", Token::IN},
197
    {"local", Token::LOCAL},
198
    {"null", Token::NULL_LIT},
199
    {"self", Token::SELF},
200
    {"super", Token::SUPER},
201
    {"tailstrict", Token::TAILSTRICT},
202
    {"then", Token::THEN},
203
    {"true", Token::TRUE},
204
};
205
206
Token::Kind lex_get_keyword_kind(const std::string &identifier)
207
97.1M
{
208
97.1M
    auto it = keywords.find(identifier);
209
97.1M
    if (it == keywords.end())
210
71.1M
        return Token::IDENTIFIER;
211
26.0M
    return it->second;
212
97.1M
}
213
214
std::string lex_number(const char *&c, const std::string &filename, const Location &begin)
215
10.1M
{
216
    // This function should be understood with reference to the linked image:
217
    // https://www.json.org/img/number.png
218
219
    // Note, we deviate from the json.org documentation as follows:
220
    // * There is no reason to lex negative numbers as atomic tokens, it is better to parse them
221
    //   as a unary operator combined with a numeric literal.  This avoids x-1 being tokenized as
222
    //   <identifier> <number> instead of the intended <identifier> <binop> <number>.
223
    // * We support digit separators using the _ character for readability in
224
    //   large numeric literals.
225
226
10.1M
    enum State {
227
10.1M
        BEGIN,
228
10.1M
        AFTER_ZERO,
229
10.1M
        AFTER_ONE_TO_NINE,
230
10.1M
        AFTER_INT_UNDERSCORE,
231
10.1M
        AFTER_DOT,
232
10.1M
        AFTER_DIGIT,
233
10.1M
        AFTER_FRAC_UNDERSCORE,
234
10.1M
        AFTER_E,
235
10.1M
        AFTER_EXP_SIGN,
236
10.1M
        AFTER_EXP_DIGIT,
237
10.1M
        AFTER_EXP_UNDERSCORE
238
10.1M
    } state;
239
240
10.1M
    std::string r;
241
242
10.1M
    state = BEGIN;
243
22.5M
    while (true) {
244
22.5M
        switch (state) {
245
10.1M
            case BEGIN:
246
10.1M
                switch (*c) {
247
2.41M
                    case '0': state = AFTER_ZERO; break;
248
249
2.93M
                    case '1':
250
3.53M
                    case '2':
251
4.05M
                    case '3':
252
4.50M
                    case '4':
253
4.59M
                    case '5':
254
5.24M
                    case '6':
255
5.46M
                    case '7':
256
6.54M
                    case '8':
257
7.70M
                    case '9': state = AFTER_ONE_TO_NINE; break;
258
259
0
                    default: throw StaticError(filename, begin, "couldn't lex number");
260
10.1M
                }
261
10.1M
                break;
262
263
10.1M
            case AFTER_ZERO:
264
2.41M
                switch (*c) {
265
34.0k
                    case '.': state = AFTER_DOT; break;
266
267
647
                    case 'e':
268
2.12k
                    case 'E': state = AFTER_E; break;
269
270
2
                    case '_': {
271
2
                        std::stringstream ss;
272
2
                        ss << "couldn't lex number, _ not allowed after leading 0";
273
2
                        throw StaticError(filename, begin, ss.str());
274
647
                    }
275
276
2.37M
                    default: goto end;
277
2.41M
                }
278
36.1k
                break;
279
280
9.50M
            case AFTER_ONE_TO_NINE:
281
9.50M
                switch (*c) {
282
20.4k
                    case '.': state = AFTER_DOT; break;
283
284
1.60k
                    case 'e':
285
6.77k
                    case 'E': state = AFTER_E; break;
286
287
1.00M
                    case '0':
288
1.03M
                    case '1':
289
1.21M
                    case '2':
290
1.28M
                    case '3':
291
1.36M
                    case '4':
292
1.53M
                    case '5':
293
1.63M
                    case '6':
294
1.68M
                    case '7':
295
1.74M
                    case '8':
296
1.79M
                    case '9': state = AFTER_ONE_TO_NINE; break;
297
298
492
                    case '_': state = AFTER_INT_UNDERSCORE; goto skip_char;
299
300
7.68M
                    default: goto end;
301
9.50M
                }
302
1.81M
                break;
303
304
1.81M
            case AFTER_INT_UNDERSCORE:
305
492
                switch (*c) {
306
                    // The only valid transition from _ is to a digit.
307
306
                    case '0':
308
333
                    case '1':
309
338
                    case '2':
310
340
                    case '3':
311
353
                    case '4':
312
354
                    case '5':
313
354
                    case '6':
314
354
                    case '7':
315
471
                    case '8':
316
471
                    case '9': state = AFTER_ONE_TO_NINE; break;
317
318
21
                    default: {
319
21
                        std::stringstream ss;
320
21
                        ss << "couldn't lex number, junk after _: " << *c;
321
21
                        throw StaticError(filename, begin, ss.str());
322
471
                    }
323
492
                }
324
471
                break;
325
326
54.5k
            case AFTER_DOT:
327
54.5k
                switch (*c) {
328
1.47k
                    case '0':
329
17.9k
                    case '1':
330
18.6k
                    case '2':
331
19.5k
                    case '3':
332
20.4k
                    case '4':
333
52.8k
                    case '5':
334
53.4k
                    case '6':
335
53.4k
                    case '7':
336
54.2k
                    case '8':
337
54.5k
                    case '9': state = AFTER_DIGIT; break;
338
339
18
                    default: {
340
18
                        std::stringstream ss;
341
18
                        ss << "couldn't lex number, junk after decimal point: " << *c;
342
18
                        throw StaticError(filename, begin, ss.str());
343
54.2k
                    }
344
54.5k
                }
345
54.5k
                break;
346
347
388k
            case AFTER_DIGIT:
348
388k
                switch (*c) {
349
644
                    case 'e':
350
1.41k
                    case 'E': state = AFTER_E; break;
351
352
36.6k
                    case '0':
353
87.5k
                    case '1':
354
105k
                    case '2':
355
139k
                    case '3':
356
157k
                    case '4':
357
208k
                    case '5':
358
242k
                    case '6':
359
259k
                    case '7':
360
283k
                    case '8':
361
333k
                    case '9': state = AFTER_DIGIT; break;
362
363
460
                    case '_': state = AFTER_FRAC_UNDERSCORE; goto skip_char;
364
365
53.0k
                    default: goto end;
366
388k
                }
367
334k
                break;
368
369
334k
            case AFTER_FRAC_UNDERSCORE:
370
460
                switch (*c) {
371
                    // The only valid transition from _ is to a digit.
372
162
                    case '0':
373
201
                    case '1':
374
204
                    case '2':
375
204
                    case '3':
376
213
                    case '4':
377
219
                    case '5':
378
219
                    case '6':
379
219
                    case '7':
380
452
                    case '8':
381
452
                    case '9': state = AFTER_DIGIT; break;
382
383
8
                    default: {
384
8
                        std::stringstream ss;
385
8
                        ss << "couldn't lex number, junk after _: " << *c;
386
8
                        throw StaticError(filename, begin, ss.str());
387
452
                    }
388
460
                }
389
452
                break;
390
391
10.3k
            case AFTER_E:
392
10.3k
                switch (*c) {
393
991
                    case '+':
394
2.49k
                    case '-': state = AFTER_EXP_SIGN; break;
395
396
1.34k
                    case '0':
397
2.53k
                    case '1':
398
2.80k
                    case '2':
399
3.04k
                    case '3':
400
3.11k
                    case '4':
401
3.51k
                    case '5':
402
6.90k
                    case '6':
403
7.33k
                    case '7':
404
7.42k
                    case '8':
405
7.77k
                    case '9': state = AFTER_EXP_DIGIT; break;
406
407
54
                    default: {
408
54
                        std::stringstream ss;
409
54
                        ss << "couldn't lex number, junk after 'E': " << *c;
410
54
                        throw StaticError(filename, begin, ss.str());
411
7.42k
                    }
412
10.3k
                }
413
10.2k
                break;
414
415
10.2k
            case AFTER_EXP_SIGN:
416
2.49k
                switch (*c) {
417
854
                    case '0':
418
1.20k
                    case '1':
419
1.59k
                    case '2':
420
2.17k
                    case '3':
421
2.45k
                    case '4':
422
2.45k
                    case '5':
423
2.45k
                    case '6':
424
2.46k
                    case '7':
425
2.46k
                    case '8':
426
2.47k
                    case '9': state = AFTER_EXP_DIGIT; break;
427
428
12
                    default: {
429
12
                        std::stringstream ss;
430
12
                        ss << "couldn't lex number, junk after exponent sign: " << *c;
431
12
                        throw StaticError(filename, begin, ss.str());
432
2.46k
                    }
433
2.49k
                }
434
2.47k
                break;
435
436
42.2k
            case AFTER_EXP_DIGIT:
437
42.2k
                switch (*c) {
438
7.12k
                    case '0':
439
10.6k
                    case '1':
440
13.0k
                    case '2':
441
15.4k
                    case '3':
442
19.2k
                    case '4':
443
21.7k
                    case '5':
444
24.0k
                    case '6':
445
27.2k
                    case '7':
446
29.2k
                    case '8':
447
31.3k
                    case '9': state = AFTER_EXP_DIGIT; break;
448
449
613
                    case '_': state = AFTER_EXP_UNDERSCORE; goto skip_char;
450
451
10.2k
                    default: goto end;
452
42.2k
                }
453
31.3k
                break;
454
455
31.3k
            case AFTER_EXP_UNDERSCORE:
456
613
                switch (*c) {
457
                    // The only valid transition from _ is to a digit.
458
177
                    case '0':
459
431
                    case '1':
460
435
                    case '2':
461
437
                    case '3':
462
576
                    case '4':
463
577
                    case '5':
464
586
                    case '6':
465
588
                    case '7':
466
597
                    case '8':
467
603
                    case '9': state = AFTER_EXP_DIGIT; break;
468
469
10
                    default: {
470
10
                        std::stringstream ss;
471
10
                        ss << "couldn't lex number, junk after _: " << *c;
472
10
                        throw StaticError(filename, begin, ss.str());
473
597
                    }
474
613
                }
475
603
                break;
476
22.5M
        }
477
12.4M
        r += *c;
478
479
12.4M
skip_char:
480
12.4M
        c++;
481
12.4M
    }
482
10.1M
end:
483
10.1M
    return r;
484
10.1M
}
485
486
// Check that b has at least the same whitespace prefix as a and returns the amount of this
487
// whitespace, otherwise returns 0.  If a has no whitespace prefix than return 0.
488
static int whitespace_check(const char *a, const char *b)
489
25.0k
{
490
25.0k
    int i = 0;
491
255k
    while (a[i] == ' ' || a[i] == '\t') {
492
241k
        if (b[i] != a[i])
493
10.6k
            return 0;
494
230k
        i++;
495
230k
    }
496
14.3k
    return i;
497
25.0k
}
498
499
176
static void describe_whitespace(std::stringstream& msg, const std::string& ws) {
500
176
    int spaces = 0;
501
176
    int tabs = 0;
502
23.3k
    for (char c : ws) {
503
23.3k
        if (c == ' ')
504
1.27k
            spaces++;
505
22.0k
        else if (c == '\t')
506
22.0k
            tabs++;
507
23.3k
    }
508
176
    if (spaces > 0 && tabs > 0) {
509
40
        msg << spaces << (spaces == 1 ? " space" : " spaces") << " and " << tabs
510
40
            << (tabs == 1 ? " tab" : " tabs");
511
136
    } else if (spaces > 0) {
512
58
        msg << spaces << (spaces == 1 ? " space" : " spaces");
513
78
    } else if (tabs > 0) {
514
78
        msg << tabs << (tabs == 1 ? " tab" : " tabs");
515
78
    } else {
516
0
        msg << "no indentation";
517
0
    }
518
176
}
519
520
Tokens jsonnet_lex(const std::string &filename, const char *input)
521
31.8k
{
522
31.8k
    unsigned long line_number = 1;
523
31.8k
    const char *line_start = input;
524
525
31.8k
    Tokens r;
526
527
31.8k
    const char *c = input;
528
529
31.8k
    Fodder fodder;
530
31.8k
    bool fresh_line = true;  // Are we tokenizing from the beginning of a new line?
531
532
230M
    while (*c != '\0') {
533
        // Used to ensure we have actually advanced the pointer by the end of the iteration.
534
230M
        const char *original_c = c;
535
536
230M
        Token::Kind kind;
537
230M
        std::string data;
538
230M
        std::string string_block_indent;
539
230M
        std::string string_block_term_indent;
540
541
230M
        unsigned new_lines, indent;
542
230M
        lex_ws(c, new_lines, indent, line_start, line_number);
543
544
        // If it's the end of the file, discard final whitespace.
545
230M
        if (*c == '\0')
546
16.5k
            break;
547
548
230M
        if (new_lines > 0) {
549
            // Otherwise store whitespace in fodder.
550
24.3M
            unsigned blanks = new_lines - 1;
551
24.3M
            fodder.emplace_back(FodderElement::LINE_END, blanks, indent, EMPTY);
552
24.3M
            fresh_line = true;
553
24.3M
        }
554
555
230M
        Location begin(line_number, c - line_start + 1);
556
557
230M
        switch (*c) {
558
            // The following operators should never be combined with subsequent symbols.
559
962k
            case '{':
560
962k
                kind = Token::BRACE_L;
561
962k
                c++;
562
962k
                break;
563
564
947k
            case '}':
565
947k
                kind = Token::BRACE_R;
566
947k
                c++;
567
947k
                break;
568
569
5.21M
            case '[':
570
5.21M
                kind = Token::BRACKET_L;
571
5.21M
                c++;
572
5.21M
                break;
573
574
5.19M
            case ']':
575
5.19M
                kind = Token::BRACKET_R;
576
5.19M
                c++;
577
5.19M
                break;
578
579
20.1M
            case ',':
580
20.1M
                kind = Token::COMMA;
581
20.1M
                c++;
582
20.1M
                break;
583
584
11.6M
            case '.':
585
11.6M
                kind = Token::DOT;
586
11.6M
                c++;
587
11.6M
                break;
588
589
17.5M
            case '(':
590
17.5M
                kind = Token::PAREN_L;
591
17.5M
                c++;
592
17.5M
                break;
593
594
17.5M
            case ')':
595
17.5M
                kind = Token::PAREN_R;
596
17.5M
                c++;
597
17.5M
                break;
598
599
4.58M
            case ';':
600
4.58M
                kind = Token::SEMICOLON;
601
4.58M
                c++;
602
4.58M
                break;
603
604
            // Numeric literals.
605
2.41M
            case '0':
606
5.34M
            case '1':
607
5.94M
            case '2':
608
6.46M
            case '3':
609
6.92M
            case '4':
610
7.01M
            case '5':
611
7.65M
            case '6':
612
7.87M
            case '7':
613
8.96M
            case '8':
614
10.1M
            case '9':
615
10.1M
                kind = Token::NUMBER;
616
10.1M
                data = lex_number(c, filename, begin);
617
10.1M
                break;
618
619
            // UString literals.
620
339k
            case '"': {
621
339k
                c++;
622
58.7M
                for (;; ++c) {
623
58.7M
                    if (*c == '\0') {
624
54
                        throw StaticError(filename, begin, "unterminated string");
625
54
                    }
626
58.7M
                    if (*c == '"') {
627
339k
                        break;
628
339k
                    }
629
58.4M
                    if (*c == '\\' && *(c + 1) != '\0') {
630
159k
                        data += *c;
631
159k
                        ++c;
632
159k
                    }
633
58.4M
                    if (*c == '\n') {
634
                        // Maintain line/column counters.
635
3.92M
                        line_number++;
636
3.92M
                        line_start = c + 1;
637
3.92M
                    }
638
58.4M
                    data += *c;
639
58.4M
                }
640
339k
                c++;  // Advance beyond the ".
641
339k
                kind = Token::STRING_DOUBLE;
642
339k
            } break;
643
644
            // UString literals.
645
7.61M
            case '\'': {
646
7.61M
                c++;
647
103M
                for (;; ++c) {
648
103M
                    if (*c == '\0') {
649
49
                        throw StaticError(filename, begin, "unterminated string");
650
49
                    }
651
103M
                    if (*c == '\'') {
652
7.61M
                        break;
653
7.61M
                    }
654
95.4M
                    if (*c == '\\' && *(c + 1) != '\0') {
655
862k
                        data += *c;
656
862k
                        ++c;
657
862k
                    }
658
95.4M
                    if (*c == '\n') {
659
                        // Maintain line/column counters.
660
2.23M
                        line_number++;
661
2.23M
                        line_start = c + 1;
662
2.23M
                    }
663
95.4M
                    data += *c;
664
95.4M
                }
665
7.61M
                c++;  // Advance beyond the '.
666
7.61M
                kind = Token::STRING_SINGLE;
667
7.61M
            } break;
668
669
            // Verbatim string literals.
670
            // ' and " quoting is interpreted here, unlike non-verbatim strings
671
            // where it is done later by jsonnet_string_unescape.  This is OK
672
            // in this case because no information is lost by resoving the
673
            // repeated quote into a single quote, so we can go back to the
674
            // original form in the formatter.
675
6.54k
            case '@': {
676
6.54k
                c++;
677
6.54k
                if (*c != '"' && *c != '\'') {
678
37
                    std::stringstream ss;
679
37
                    ss << "couldn't lex verbatim string, junk after '@': " << *c;
680
37
                    throw StaticError(filename, begin, ss.str());
681
37
                }
682
6.50k
                const char quot = *c;
683
6.50k
                c++;  // Advance beyond the opening quote.
684
51.6k
                for (;; ++c) {
685
51.6k
                    if (*c == '\0') {
686
53
                        throw StaticError(filename, begin, "unterminated verbatim string");
687
53
                    }
688
51.5k
                    if (*c == quot) {
689
8.60k
                        if (*(c + 1) == quot) {
690
2.15k
                            c++;
691
6.45k
                        } else {
692
6.45k
                            break;
693
6.45k
                        }
694
8.60k
                    }
695
45.1k
                    data += *c;
696
45.1k
                }
697
6.45k
                c++;  // Advance beyond the closing quote.
698
6.45k
                if (quot == '"') {
699
3.24k
                    kind = Token::VERBATIM_STRING_DOUBLE;
700
3.24k
                } else {
701
3.20k
                    kind = Token::VERBATIM_STRING_SINGLE;
702
3.20k
                }
703
6.45k
            } break;
704
705
            // Keywords
706
128M
            default:
707
128M
                if (is_identifier_first(*c)) {
708
97.1M
                    std::string id;
709
528M
                    for (; is_identifier(*c); ++c)
710
431M
                        id += *c;
711
97.1M
                    kind = lex_get_keyword_kind(id);
712
97.1M
                    data = id;
713
714
97.1M
                } else if (is_symbol(*c) || *c == '#') {
715
                    // Single line C++ and Python style comments.
716
31.2M
                    if (*c == '#' || (*c == '/' && *(c + 1) == '/')) {
717
6.22M
                        std::vector<std::string> comment(1);
718
6.22M
                        unsigned blanks;
719
6.22M
                        unsigned indent;
720
6.22M
                        lex_until_newline(c, comment[0], blanks, indent, line_start, line_number);
721
6.22M
                        auto kind = fresh_line ? FodderElement::PARAGRAPH : FodderElement::LINE_END;
722
6.22M
                        fodder.emplace_back(kind, blanks, indent, comment);
723
6.22M
                        fresh_line = true;
724
6.22M
                        continue;  // We've not got a token, just fodder, so keep scanning.
725
6.22M
                    }
726
727
                    // Multi-line C style comment.
728
25.0M
                    if (*c == '/' && *(c + 1) == '*') {
729
260k
                        unsigned margin = c - line_start;
730
731
260k
                        const char *initial_c = c;
732
260k
                        c += 2;  // Avoid matching /*/: skip the /* before starting the search for
733
                                 // */.
734
735
93.5M
                        while (!(*c == '*' && *(c + 1) == '/')) {
736
93.2M
                            if (*c == '\0') {
737
153
                                auto msg = "multi-line comment has no terminating */.";
738
153
                                throw StaticError(filename, begin, msg);
739
153
                            }
740
93.2M
                            if (*c == '\n') {
741
                                // Just keep track of the line / column counters.
742
11.5M
                                line_number++;
743
11.5M
                                line_start = c + 1;
744
11.5M
                            }
745
93.2M
                            ++c;
746
93.2M
                        }
747
260k
                        c += 2;  // Move the pointer to the char after the closing '/'.
748
749
260k
                        std::string comment(initial_c,
750
260k
                                            c - initial_c);  // Includes the "/*" and "*/".
751
752
                        // Lex whitespace after comment
753
260k
                        unsigned new_lines_after, indent_after;
754
260k
                        lex_ws(c, new_lines_after, indent_after, line_start, line_number);
755
260k
                        std::vector<std::string> lines;
756
260k
                        if (comment.find('\n') >= comment.length()) {
757
                            // Comment looks like /* foo */
758
89.7k
                            lines.push_back(comment);
759
89.7k
                            fodder.emplace_back(FodderElement::INTERSTITIAL, 0, 0, lines);
760
89.7k
                            if (new_lines_after > 0) {
761
67.8k
                                fodder.emplace_back(FodderElement::LINE_END,
762
67.8k
                                                    new_lines_after - 1,
763
67.8k
                                                    indent_after,
764
67.8k
                                                    EMPTY);
765
67.8k
                                fresh_line = true;
766
67.8k
                            }
767
170k
                        } else {
768
170k
                            lines = line_split(comment, margin);
769
170k
                            assert(lines[0][0] == '/');
770
                            // Little hack to support PARAGRAPHs with * down the LHS:
771
                            // Add a space to lines that start with a '*'
772
170k
                            bool all_star = true;
773
11.6M
                            for (auto &l : lines) {
774
11.6M
                                if (l[0] != '*')
775
11.5M
                                    all_star = false;
776
11.6M
                            }
777
170k
                            if (all_star) {
778
0
                                for (auto &l : lines) {
779
0
                                    if (l[0] == '*')
780
0
                                        l = " " + l;
781
0
                                }
782
0
                            }
783
170k
                            if (new_lines_after == 0) {
784
                                // Ensure a line end after the paragraph.
785
14.5k
                                new_lines_after = 1;
786
14.5k
                                indent_after = 0;
787
14.5k
                            }
788
170k
                            fodder_push_back(fodder,
789
170k
                                             FodderElement(FodderElement::PARAGRAPH,
790
170k
                                                           new_lines_after - 1,
791
170k
                                                           indent_after,
792
170k
                                                           lines));
793
170k
                            fresh_line = true;
794
170k
                        }
795
260k
                        continue;  // We've not got a token, just fodder, so keep scanning.
796
260k
                    }
797
798
                    // Text block
799
24.8M
                    if (*c == '|' && *(c + 1) == '|' && *(c + 2) == '|') {
800
10.8k
                        c += 3;  // Skip the "|||".
801
802
10.8k
                        bool chomp_trailing_nl = false;
803
10.8k
                        if (*c == '-') {
804
702
                            chomp_trailing_nl = true;
805
702
                            c++;
806
702
                        }
807
808
13.9k
                        while (is_horz_ws(*c)) ++c;  // Chomp whitespace at end of line.
809
10.8k
                        if (*c != '\n') {
810
88
                            auto msg = "text block syntax requires new line after |||.";
811
88
                            throw StaticError(filename, begin, msg);
812
88
                        }
813
10.8k
                        std::stringstream block;
814
10.8k
                        c++;  // Skip the "\n"
815
10.8k
                        line_number++;
816
                        // Skip any blank lines at the beginning of the block.
817
13.2k
                        while (*c == '\n') {
818
2.46k
                            line_number++;
819
2.46k
                            ++c;
820
2.46k
                            block << '\n';
821
2.46k
                        }
822
10.8k
                        line_start = c;
823
10.8k
                        const char *first_line = c;
824
10.8k
                        int ws_chars = whitespace_check(first_line, c);
825
10.8k
                        string_block_indent = std::string(first_line, ws_chars);
826
10.8k
                        if (ws_chars == 0) {
827
49
                            auto msg = "text block's first line must start with whitespace.";
828
49
                            throw StaticError(filename, begin, msg);
829
49
                        }
830
14.3k
                        while (true) {
831
14.3k
                            assert(ws_chars > 0);
832
                            // Read up to the \n
833
184k
                            for (c = &c[ws_chars]; *c != '\n'; ++c) {
834
169k
                                if (*c == '\0')
835
110
                                    throw StaticError(filename, begin, "unexpected EOF");
836
169k
                                block << *c;
837
169k
                            }
838
                            // Add the \n
839
14.2k
                            block << '\n';
840
14.2k
                            ++c;
841
14.2k
                            line_number++;
842
14.2k
                            line_start = c;
843
                            // Skip any blank lines
844
16.6k
                            while (*c == '\n') {
845
2.43k
                                line_number++;
846
2.43k
                                ++c;
847
2.43k
                                block << '\n';
848
2.43k
                            }
849
                            // Examine next line
850
14.2k
                            ws_chars = whitespace_check(first_line, c);
851
14.2k
                            if (ws_chars == 0) {
852
                                // End of text block (or indentation error).
853
                                // Count actual whitespace on this line.
854
10.6k
                                int actual_ws = 0;
855
80.9k
                                while (c[actual_ws] == ' ' ||
856
70.3k
                                       c[actual_ws] == '\t') {
857
70.3k
                                    actual_ws++;
858
70.3k
                                }
859
860
                                // Check if this is the terminator |||
861
10.6k
                                bool is_terminator = (
862
10.6k
                                    c[actual_ws] == '|' &&
863
10.5k
                                    c[actual_ws + 1] == '|' &&
864
10.4k
                                    c[actual_ws + 2] == '|');
865
866
10.6k
                                if (!is_terminator) {
867
                                    // Not a terminator - check if it's an
868
                                    // indentation issue.
869
181
                                    if (actual_ws > 0) {
870
                                        // Has whitespace but doesn't match expected
871
                                        // indentation.
872
88
                                        std::stringstream msg;
873
88
                                        msg << "text block indentation mismatch: "
874
88
                                                "expected at least ";
875
88
                                        describe_whitespace(msg, string_block_indent);
876
88
                                        msg << ", found ";
877
88
                                        describe_whitespace(msg, std::string(c, actual_ws));
878
88
                                        throw StaticError(filename, begin, msg.str());
879
93
                                    } else {
880
                                        // No whitespace and no ||| - missing
881
                                        // terminator.
882
93
                                        auto msg =
883
93
                                            "text block not terminated with |||";
884
93
                                        throw StaticError(filename, begin, msg);
885
93
                                    }
886
181
                                }
887
888
                                // Valid termination - skip over any whitespace.
889
67.7k
                                while (*c == ' ' || *c == '\t') {
890
57.2k
                                    string_block_term_indent += *c;
891
57.2k
                                    ++c;
892
57.2k
                                }
893
                                // Skip the |||
894
10.4k
                                c += 3;  // Leave after the last |
895
10.4k
                                data = block.str();
896
10.4k
                                kind = Token::STRING_BLOCK;
897
10.4k
                                if (chomp_trailing_nl) {
898
677
                                    assert(data.back() == '\n');
899
677
                                    data.pop_back();
900
677
                                }
901
10.4k
                                break;  // Out of the while loop.
902
10.4k
                            }
903
14.2k
                        }
904
905
10.4k
                        break;  // Out of the switch.
906
10.7k
                    }
907
908
24.7M
                    const char *operator_begin = c;
909
81.2M
                    for (; is_symbol(*c); ++c) {
910
                        // Not allowed // in operators
911
56.4M
                        if (*c == '/' && *(c + 1) == '/')
912
720
                            break;
913
                        // Not allowed /* in operators
914
56.4M
                        if (*c == '/' && *(c + 1) == '*')
915
1.07k
                            break;
916
                        // Not allowed ||| in operators
917
56.4M
                        if (*c == '|' && *(c + 1) == '|' && *(c + 2) == '|')
918
3.72k
                            break;
919
56.4M
                    }
920
                    // Not allowed to end with a + - ~ ! unless a single char.
921
                    // So, wind it back if we need to (but not too far).
922
29.5M
                    while (c > operator_begin + 1 && !allowed_at_end_of_operator(*(c - 1))) {
923
4.71M
                        c--;
924
4.71M
                    }
925
24.7M
                    data += std::string(operator_begin, c);
926
24.7M
                    if (data == "$") {
927
71.8k
                        kind = Token::DOLLAR;
928
71.8k
                        data = "";
929
24.7M
                    } else {
930
24.7M
                        kind = Token::OPERATOR;
931
24.7M
                    }
932
24.7M
                } else {
933
238
                    std::stringstream ss;
934
238
                    ss << "Could not lex the character ";
935
238
                    auto uc = (unsigned char)(*c);
936
238
                    if (*c < 32)
937
219
                        ss << "code " << unsigned(uc);
938
19
                    else
939
19
                        ss << "'" << *c << "'";
940
238
                    throw StaticError(filename, begin, ss.str());
941
238
                }
942
230M
        }
943
944
        // Ensure that a bug in the above code does not cause an infinite memory consuming loop due
945
        // to pushing empty tokens.
946
223M
        if (c == original_c) {
947
0
            throw StaticError(filename, begin, "internal lexing error:  pointer did not advance");
948
0
        }
949
950
223M
        Location end(line_number, (c + 1) - line_start);
951
223M
        r.emplace_back(kind,
952
223M
                       fodder,
953
223M
                       data,
954
223M
                       string_block_indent,
955
223M
                       string_block_term_indent,
956
223M
                       LocationRange(filename, begin, end));
957
223M
        fodder.clear();
958
223M
        fresh_line = false;
959
223M
    }
960
961
30.7k
    Location begin(line_number, c - line_start + 1);
962
30.7k
    Location end(line_number, (c + 1) - line_start + 1);
963
30.7k
    r.emplace_back(Token::END_OF_FILE, fodder, "", "", "", LocationRange(filename, begin, end));
964
30.7k
    return r;
965
31.8k
}
966
967
std::string jsonnet_unlex(const Tokens &tokens)
968
0
{
969
0
    std::stringstream ss;
970
0
    for (const auto &t : tokens) {
971
0
        for (const auto &f : t.fodder) {
972
0
            switch (f.kind) {
973
0
                case FodderElement::LINE_END: {
974
0
                    if (f.comment.size() > 0) {
975
0
                        ss << "LineEnd(" << f.blanks << ", " << f.indent << ", " << f.comment[0]
976
0
                           << ")\n";
977
0
                    } else {
978
0
                        ss << "LineEnd(" << f.blanks << ", " << f.indent << ")\n";
979
0
                    }
980
0
                } break;
981
982
0
                case FodderElement::INTERSTITIAL: {
983
0
                    ss << "Interstitial(" << f.comment[0] << ")\n";
984
0
                } break;
985
986
0
                case FodderElement::PARAGRAPH: {
987
0
                    ss << "Paragraph(\n";
988
0
                    for (const auto &line : f.comment) {
989
0
                        ss << "    " << line << '\n';
990
0
                    }
991
0
                    ss << ")" << f.blanks << "\n";
992
0
                } break;
993
0
            }
994
0
        }
995
0
        if (t.kind == Token::END_OF_FILE) {
996
0
            ss << "EOF\n";
997
0
            break;
998
0
        }
999
0
        if (t.kind == Token::STRING_DOUBLE) {
1000
0
            ss << "\"" << t.data << "\"\n";
1001
0
        } else if (t.kind == Token::STRING_SINGLE) {
1002
0
            ss << "'" << t.data << "'\n";
1003
0
        } else if (t.kind == Token::STRING_BLOCK) {
1004
0
            ss << "|||\n";
1005
0
            ss << t.stringBlockIndent;
1006
0
            for (const char *cp = t.data.c_str(); *cp != '\0'; ++cp) {
1007
0
                ss << *cp;
1008
0
                if (*cp == '\n' && *(cp + 1) != '\n' && *(cp + 1) != '\0') {
1009
0
                    ss << t.stringBlockIndent;
1010
0
                }
1011
0
            }
1012
0
            ss << t.stringBlockTermIndent << "|||\n";
1013
0
        } else {
1014
0
            ss << t.data << "\n";
1015
0
        }
1016
0
    }
1017
0
    return ss.str();
1018
0
}
1019
1020
}  // namespace jsonnet::internal