Coverage Report

Created: 2026-08-13 06:16

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
759M
{
34
759M
    return c == ' ' || c == '\t' || c == '\r';
35
759M
}
36
37
/** Is the char whitespace. */
38
static bool is_ws(char c)
39
681M
{
40
681M
    return c == '\n' || is_horz_ws(c);
41
681M
}
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
17.0M
{
46
17.0M
    if (s.size() == 0)
47
14.5M
        return s;  // Avoid underflow below.
48
2.52M
    size_t i = 0;
49
7.39M
    while (i < s.length() && is_horz_ws(s[i]) && i < margin)
50
4.86M
        i++;
51
2.52M
    size_t j = s.size();
52
6.25M
    while (j > i && is_horz_ws(s[j - 1])) {
53
3.73M
        j--;
54
3.73M
    }
55
2.52M
    return std::string(&s[i], &s[j]);
56
17.0M
}
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
252k
{
61
252k
    std::vector<std::string> ret;
62
252k
    std::stringstream ss;
63
111M
    for (size_t i = 0; i < s.length(); ++i) {
64
111M
        if (s[i] == '\n') {
65
16.7M
            ret.emplace_back(strip_ws(ss.str(), margin));
66
16.7M
            ss.str("");
67
94.3M
        } else {
68
94.3M
            ss << s[i];
69
94.3M
        }
70
111M
    }
71
252k
    ret.emplace_back(strip_ws(ss.str(), margin));
72
252k
    return ret;
73
252k
}
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
294M
{
82
294M
    indent = 0;
83
294M
    new_lines = 0;
84
681M
    for (; *c != '\0' && is_ws(*c); c++) {
85
386M
        switch (*c) {
86
1.22M
            case '\r':
87
                // Ignore.
88
1.22M
                break;
89
90
42.6M
            case '\n':
91
42.6M
                indent = 0;
92
42.6M
                new_lines++;
93
42.6M
                line_number++;
94
42.6M
                line_start = c + 1;
95
42.6M
                break;
96
97
342M
            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.4k
            case '\t': indent += 8; break;
104
386M
        }
105
386M
    }
106
294M
}
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
7.89M
{
114
7.89M
    const char *original_c = c;
115
7.89M
    const char *last_non_space = c;
116
114M
    for (; *c != '\0' && *c != '\n'; c++) {
117
106M
        if (!is_horz_ws(*c))
118
91.8M
            last_non_space = c;
119
106M
    }
120
7.89M
    text = std::string(original_c, last_non_space - original_c + 1);
121
    // Consume subsequent whitespace including the '\n'.
122
7.89M
    unsigned new_lines;
123
7.89M
    lex_ws(c, new_lines, indent, line_start, line_number);
124
7.89M
    blanks = new_lines == 0 ? 0 : new_lines - 1;
125
7.89M
}
126
127
static bool is_upper(char c)
128
815M
{
129
815M
    return c >= 'A' && c <= 'Z';
130
815M
}
131
132
static bool is_lower(char c)
133
801M
{
134
801M
    return c >= 'a' && c <= 'z';
135
801M
}
136
137
static bool is_number(char c)
138
144M
{
139
144M
    return c >= '0' && c <= '9';
140
144M
}
141
142
static bool is_identifier_first(char c)
143
815M
{
144
815M
    return is_upper(c) || is_lower(c) || c == '_';
145
815M
}
146
147
static bool is_identifier(char c)
148
655M
{
149
655M
    return is_identifier_first(c) || is_number(c);
150
655M
}
151
152
static bool is_symbol(char c)
153
141M
{
154
141M
    switch (c) {
155
2.92M
        case '!':
156
3.32M
        case '$':
157
18.1M
        case ':':
158
19.0M
        case '~':
159
38.0M
        case '+':
160
40.9M
        case '-':
161
43.6M
        case '&':
162
45.1M
        case '|':
163
45.1M
        case '^':
164
73.1M
        case '=':
165
75.6M
        case '<':
166
79.0M
        case '>':
167
100M
        case '*':
168
103M
        case '/':
169
105M
        case '%': return true;
170
141M
    }
171
36.8M
    return false;
172
141M
}
173
174
14.0M
bool allowed_at_end_of_operator(char c) {
175
14.0M
    switch (c) {
176
3.25M
        case '+':
177
3.81M
        case '-':
178
4.70M
        case '~':
179
5.22M
        case '!':
180
5.40M
        case '$': return false;
181
14.0M
    }
182
8.60M
    return true;
183
14.0M
}
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
120M
{
208
120M
    auto it = keywords.find(identifier);
209
120M
    if (it == keywords.end())
210
88.3M
        return Token::IDENTIFIER;
211
32.4M
    return it->second;
212
120M
}
213
214
std::string lex_number(const char *&c, const std::string &filename, const Location &begin)
215
12.3M
{
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
12.3M
    enum State {
227
12.3M
        BEGIN,
228
12.3M
        AFTER_ZERO,
229
12.3M
        AFTER_ONE_TO_NINE,
230
12.3M
        AFTER_INT_UNDERSCORE,
231
12.3M
        AFTER_DOT,
232
12.3M
        AFTER_DIGIT,
233
12.3M
        AFTER_FRAC_UNDERSCORE,
234
12.3M
        AFTER_E,
235
12.3M
        AFTER_EXP_SIGN,
236
12.3M
        AFTER_EXP_DIGIT,
237
12.3M
        AFTER_EXP_UNDERSCORE
238
12.3M
    } state;
239
240
12.3M
    std::string r;
241
242
12.3M
    state = BEGIN;
243
28.8M
    while (true) {
244
28.8M
        switch (state) {
245
12.3M
            case BEGIN:
246
12.3M
                switch (*c) {
247
2.86M
                    case '0': state = AFTER_ZERO; break;
248
249
3.66M
                    case '1':
250
4.41M
                    case '2':
251
5.04M
                    case '3':
252
5.81M
                    case '4':
253
5.96M
                    case '5':
254
6.56M
                    case '6':
255
6.82M
                    case '7':
256
8.14M
                    case '8':
257
9.44M
                    case '9': state = AFTER_ONE_TO_NINE; break;
258
259
0
                    default: throw StaticError(filename, begin, "couldn't lex number");
260
12.3M
                }
261
12.3M
                break;
262
263
12.3M
            case AFTER_ZERO:
264
2.86M
                switch (*c) {
265
42.6k
                    case '.': state = AFTER_DOT; break;
266
267
1.79k
                    case 'e':
268
2.83k
                    case 'E': state = AFTER_E; break;
269
270
4
                    case '_': {
271
4
                        std::stringstream ss;
272
4
                        ss << "couldn't lex number, _ not allowed after leading 0";
273
4
                        throw StaticError(filename, begin, ss.str());
274
1.79k
                    }
275
276
2.81M
                    default: goto end;
277
2.86M
                }
278
45.4k
                break;
279
280
12.8M
            case AFTER_ONE_TO_NINE:
281
12.8M
                switch (*c) {
282
25.9k
                    case '.': state = AFTER_DOT; break;
283
284
3.28k
                    case 'e':
285
6.25k
                    case 'E': state = AFTER_E; break;
286
287
1.71M
                    case '0':
288
1.75M
                    case '1':
289
1.99M
                    case '2':
290
2.07M
                    case '3':
291
2.18M
                    case '4':
292
2.38M
                    case '5':
293
2.51M
                    case '6':
294
2.58M
                    case '7':
295
2.65M
                    case '8':
296
3.42M
                    case '9': state = AFTER_ONE_TO_NINE; break;
297
298
716
                    case '_': state = AFTER_INT_UNDERSCORE; goto skip_char;
299
300
9.40M
                    default: goto end;
301
12.8M
                }
302
3.45M
                break;
303
304
3.45M
            case AFTER_INT_UNDERSCORE:
305
716
                switch (*c) {
306
                    // The only valid transition from _ is to a digit.
307
384
                    case '0':
308
411
                    case '1':
309
436
                    case '2':
310
436
                    case '3':
311
445
                    case '4':
312
600
                    case '5':
313
600
                    case '6':
314
604
                    case '7':
315
689
                    case '8':
316
692
                    case '9': state = AFTER_ONE_TO_NINE; break;
317
318
24
                    default: {
319
24
                        std::stringstream ss;
320
24
                        ss << "couldn't lex number, junk after _: " << *c;
321
24
                        throw StaticError(filename, begin, ss.str());
322
689
                    }
323
716
                }
324
692
                break;
325
326
68.6k
            case AFTER_DOT:
327
68.6k
                switch (*c) {
328
2.13k
                    case '0':
329
23.7k
                    case '1':
330
24.5k
                    case '2':
331
25.4k
                    case '3':
332
26.4k
                    case '4':
333
66.8k
                    case '5':
334
67.6k
                    case '6':
335
67.7k
                    case '7':
336
68.2k
                    case '8':
337
68.5k
                    case '9': state = AFTER_DIGIT; break;
338
339
19
                    default: {
340
19
                        std::stringstream ss;
341
19
                        ss << "couldn't lex number, junk after decimal point: " << *c;
342
19
                        throw StaticError(filename, begin, ss.str());
343
68.2k
                    }
344
68.6k
                }
345
68.5k
                break;
346
347
544k
            case AFTER_DIGIT:
348
544k
                switch (*c) {
349
987
                    case 'e':
350
2.10k
                    case 'E': state = AFTER_E; break;
351
352
103k
                    case '0':
353
168k
                    case '1':
354
192k
                    case '2':
355
235k
                    case '3':
356
259k
                    case '4':
357
323k
                    case '5':
358
365k
                    case '6':
359
388k
                    case '7':
360
412k
                    case '8':
361
475k
                    case '9': state = AFTER_DIGIT; break;
362
363
1.17k
                    case '_': state = AFTER_FRAC_UNDERSCORE; goto skip_char;
364
365
66.4k
                    default: goto end;
366
544k
                }
367
477k
                break;
368
369
477k
            case AFTER_FRAC_UNDERSCORE:
370
1.17k
                switch (*c) {
371
                    // The only valid transition from _ is to a digit.
372
384
                    case '0':
373
423
                    case '1':
374
849
                    case '2':
375
849
                    case '3':
376
915
                    case '4':
377
1.08k
                    case '5':
378
1.08k
                    case '6':
379
1.08k
                    case '7':
380
1.16k
                    case '8':
381
1.16k
                    case '9': state = AFTER_DIGIT; break;
382
383
16
                    default: {
384
16
                        std::stringstream ss;
385
16
                        ss << "couldn't lex number, junk after _: " << *c;
386
16
                        throw StaticError(filename, begin, ss.str());
387
1.16k
                    }
388
1.17k
                }
389
1.16k
                break;
390
391
11.1k
            case AFTER_E:
392
11.1k
                switch (*c) {
393
1.40k
                    case '+':
394
3.45k
                    case '-': state = AFTER_EXP_SIGN; break;
395
396
2.08k
                    case '0':
397
3.19k
                    case '1':
398
4.53k
                    case '2':
399
5.47k
                    case '3':
400
5.53k
                    case '4':
401
5.88k
                    case '5':
402
6.37k
                    case '6':
403
6.45k
                    case '7':
404
7.31k
                    case '8':
405
7.66k
                    case '9': state = AFTER_EXP_DIGIT; break;
406
407
64
                    default: {
408
64
                        std::stringstream ss;
409
64
                        ss << "couldn't lex number, junk after 'E': " << *c;
410
64
                        throw StaticError(filename, begin, ss.str());
411
7.31k
                    }
412
11.1k
                }
413
11.1k
                break;
414
415
11.1k
            case AFTER_EXP_SIGN:
416
3.45k
                switch (*c) {
417
859
                    case '0':
418
1.29k
                    case '1':
419
1.61k
                    case '2':
420
2.87k
                    case '3':
421
3.21k
                    case '4':
422
3.21k
                    case '5':
423
3.29k
                    case '6':
424
3.32k
                    case '7':
425
3.41k
                    case '8':
426
3.43k
                    case '9': state = AFTER_EXP_DIGIT; break;
427
428
17
                    default: {
429
17
                        std::stringstream ss;
430
17
                        ss << "couldn't lex number, junk after exponent sign: " << *c;
431
17
                        throw StaticError(filename, begin, ss.str());
432
3.41k
                    }
433
3.45k
                }
434
3.43k
                break;
435
436
211k
            case AFTER_EXP_DIGIT:
437
211k
                switch (*c) {
438
165k
                    case '0':
439
170k
                    case '1':
440
174k
                    case '2':
441
177k
                    case '3':
442
182k
                    case '4':
443
185k
                    case '5':
444
187k
                    case '6':
445
191k
                    case '7':
446
196k
                    case '8':
447
199k
                    case '9': state = AFTER_EXP_DIGIT; break;
448
449
697
                    case '_': state = AFTER_EXP_UNDERSCORE; goto skip_char;
450
451
11.0k
                    default: goto end;
452
211k
                }
453
199k
                break;
454
455
199k
            case AFTER_EXP_UNDERSCORE:
456
697
                switch (*c) {
457
                    // The only valid transition from _ is to a digit.
458
203
                    case '0':
459
496
                    case '1':
460
500
                    case '2':
461
501
                    case '3':
462
636
                    case '4':
463
639
                    case '5':
464
650
                    case '6':
465
652
                    case '7':
466
678
                    case '8':
467
684
                    case '9': state = AFTER_EXP_DIGIT; break;
468
469
13
                    default: {
470
13
                        std::stringstream ss;
471
13
                        ss << "couldn't lex number, junk after _: " << *c;
472
13
                        throw StaticError(filename, begin, ss.str());
473
678
                    }
474
697
                }
475
684
                break;
476
28.8M
        }
477
16.5M
        r += *c;
478
479
16.5M
skip_char:
480
16.5M
        c++;
481
16.5M
    }
482
12.3M
end:
483
12.3M
    return r;
484
12.3M
}
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
37.2k
{
490
37.2k
    int i = 0;
491
1.05M
    while (a[i] == ' ' || a[i] == '\t') {
492
1.03M
        if (b[i] != a[i])
493
15.9k
            return 0;
494
1.01M
        i++;
495
1.01M
    }
496
21.3k
    return i;
497
37.2k
}
498
499
234
static void describe_whitespace(std::stringstream& msg, const std::string& ws) {
500
234
    int spaces = 0;
501
234
    int tabs = 0;
502
459k
    for (char c : ws) {
503
459k
        if (c == ' ')
504
34.8k
            spaces++;
505
425k
        else if (c == '\t')
506
425k
            tabs++;
507
459k
    }
508
234
    if (spaces > 0 && tabs > 0) {
509
68
        msg << spaces << (spaces == 1 ? " space" : " spaces") << " and " << tabs
510
68
            << (tabs == 1 ? " tab" : " tabs");
511
166
    } else if (spaces > 0) {
512
73
        msg << spaces << (spaces == 1 ? " space" : " spaces");
513
93
    } else if (tabs > 0) {
514
93
        msg << tabs << (tabs == 1 ? " tab" : " tabs");
515
93
    } else {
516
0
        msg << "no indentation";
517
0
    }
518
234
}
519
520
Tokens jsonnet_lex(const std::string &filename, const char *input)
521
40.2k
{
522
40.2k
    unsigned long line_number = 1;
523
40.2k
    const char *line_start = input;
524
525
40.2k
    Tokens r;
526
527
40.2k
    const char *c = input;
528
529
40.2k
    Fodder fodder;
530
40.2k
    bool fresh_line = true;  // Are we tokenizing from the beginning of a new line?
531
532
286M
    while (*c != '\0') {
533
        // Used to ensure we have actually advanced the pointer by the end of the iteration.
534
286M
        const char *original_c = c;
535
536
286M
        Token::Kind kind;
537
286M
        std::string data;
538
286M
        std::string string_block_indent;
539
286M
        std::string string_block_term_indent;
540
541
286M
        unsigned new_lines, indent;
542
286M
        lex_ws(c, new_lines, indent, line_start, line_number);
543
544
        // If it's the end of the file, discard final whitespace.
545
286M
        if (*c == '\0')
546
20.6k
            break;
547
548
286M
        if (new_lines > 0) {
549
            // Otherwise store whitespace in fodder.
550
30.2M
            unsigned blanks = new_lines - 1;
551
30.2M
            fodder.emplace_back(FodderElement::LINE_END, blanks, indent, EMPTY);
552
30.2M
            fresh_line = true;
553
30.2M
        }
554
555
286M
        Location begin(line_number, c - line_start + 1);
556
557
286M
        switch (*c) {
558
            // The following operators should never be combined with subsequent symbols.
559
1.22M
            case '{':
560
1.22M
                kind = Token::BRACE_L;
561
1.22M
                c++;
562
1.22M
                break;
563
564
1.20M
            case '}':
565
1.20M
                kind = Token::BRACE_R;
566
1.20M
                c++;
567
1.20M
                break;
568
569
6.44M
            case '[':
570
6.44M
                kind = Token::BRACKET_L;
571
6.44M
                c++;
572
6.44M
                break;
573
574
6.41M
            case ']':
575
6.41M
                kind = Token::BRACKET_R;
576
6.41M
                c++;
577
6.41M
                break;
578
579
24.8M
            case ',':
580
24.8M
                kind = Token::COMMA;
581
24.8M
                c++;
582
24.8M
                break;
583
584
14.4M
            case '.':
585
14.4M
                kind = Token::DOT;
586
14.4M
                c++;
587
14.4M
                break;
588
589
21.7M
            case '(':
590
21.7M
                kind = Token::PAREN_L;
591
21.7M
                c++;
592
21.7M
                break;
593
594
21.7M
            case ')':
595
21.7M
                kind = Token::PAREN_R;
596
21.7M
                c++;
597
21.7M
                break;
598
599
5.69M
            case ';':
600
5.69M
                kind = Token::SEMICOLON;
601
5.69M
                c++;
602
5.69M
                break;
603
604
            // Numeric literals.
605
2.86M
            case '0':
606
6.52M
            case '1':
607
7.27M
            case '2':
608
7.90M
            case '3':
609
8.67M
            case '4':
610
8.83M
            case '5':
611
9.43M
            case '6':
612
9.69M
            case '7':
613
11.0M
            case '8':
614
12.3M
            case '9':
615
12.3M
                kind = Token::NUMBER;
616
12.3M
                data = lex_number(c, filename, begin);
617
12.3M
                break;
618
619
            // UString literals.
620
395k
            case '"': {
621
395k
                c++;
622
74.5M
                for (;; ++c) {
623
74.5M
                    if (*c == '\0') {
624
74
                        throw StaticError(filename, begin, "unterminated string");
625
74
                    }
626
74.5M
                    if (*c == '"') {
627
395k
                        break;
628
395k
                    }
629
74.1M
                    if (*c == '\\' && *(c + 1) != '\0') {
630
216k
                        data += *c;
631
216k
                        ++c;
632
216k
                    }
633
74.1M
                    if (*c == '\n') {
634
                        // Maintain line/column counters.
635
6.16M
                        line_number++;
636
6.16M
                        line_start = c + 1;
637
6.16M
                    }
638
74.1M
                    data += *c;
639
74.1M
                }
640
395k
                c++;  // Advance beyond the ".
641
395k
                kind = Token::STRING_DOUBLE;
642
395k
            } break;
643
644
            // UString literals.
645
9.48M
            case '\'': {
646
9.48M
                c++;
647
128M
                for (;; ++c) {
648
128M
                    if (*c == '\0') {
649
66
                        throw StaticError(filename, begin, "unterminated string");
650
66
                    }
651
128M
                    if (*c == '\'') {
652
9.48M
                        break;
653
9.48M
                    }
654
119M
                    if (*c == '\\' && *(c + 1) != '\0') {
655
1.06M
                        data += *c;
656
1.06M
                        ++c;
657
1.06M
                    }
658
119M
                    if (*c == '\n') {
659
                        // Maintain line/column counters.
660
3.32M
                        line_number++;
661
3.32M
                        line_start = c + 1;
662
3.32M
                    }
663
119M
                    data += *c;
664
119M
                }
665
9.48M
                c++;  // Advance beyond the '.
666
9.48M
                kind = Token::STRING_SINGLE;
667
9.48M
            } 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
12.4k
            case '@': {
676
12.4k
                c++;
677
12.4k
                if (*c != '"' && *c != '\'') {
678
42
                    std::stringstream ss;
679
42
                    ss << "couldn't lex verbatim string, junk after '@': " << *c;
680
42
                    throw StaticError(filename, begin, ss.str());
681
42
                }
682
12.4k
                const char quot = *c;
683
12.4k
                c++;  // Advance beyond the opening quote.
684
196k
                for (;; ++c) {
685
196k
                    if (*c == '\0') {
686
69
                        throw StaticError(filename, begin, "unterminated verbatim string");
687
69
                    }
688
196k
                    if (*c == quot) {
689
15.2k
                        if (*(c + 1) == quot) {
690
2.84k
                            c++;
691
12.3k
                        } else {
692
12.3k
                            break;
693
12.3k
                        }
694
15.2k
                    }
695
183k
                    data += *c;
696
183k
                }
697
12.3k
                c++;  // Advance beyond the closing quote.
698
12.3k
                if (quot == '"') {
699
7.45k
                    kind = Token::VERBATIM_STRING_DOUBLE;
700
7.45k
                } else {
701
4.92k
                    kind = Token::VERBATIM_STRING_SINGLE;
702
4.92k
                }
703
12.3k
            } break;
704
705
            // Keywords
706
160M
            default:
707
160M
                if (is_identifier_first(*c)) {
708
120M
                    std::string id;
709
655M
                    for (; is_identifier(*c); ++c)
710
534M
                        id += *c;
711
120M
                    kind = lex_get_keyword_kind(id);
712
120M
                    data = id;
713
714
120M
                } else if (is_symbol(*c) || *c == '#') {
715
                    // Single line C++ and Python style comments.
716
39.3M
                    if (*c == '#' || (*c == '/' && *(c + 1) == '/')) {
717
7.89M
                        std::vector<std::string> comment(1);
718
7.89M
                        unsigned blanks;
719
7.89M
                        unsigned indent;
720
7.89M
                        lex_until_newline(c, comment[0], blanks, indent, line_start, line_number);
721
7.89M
                        auto kind = fresh_line ? FodderElement::PARAGRAPH : FodderElement::LINE_END;
722
7.89M
                        fodder.emplace_back(kind, blanks, indent, comment);
723
7.89M
                        fresh_line = true;
724
7.89M
                        continue;  // We've not got a token, just fodder, so keep scanning.
725
7.89M
                    }
726
727
                    // Multi-line C style comment.
728
31.4M
                    if (*c == '/' && *(c + 1) == '*') {
729
596k
                        unsigned margin = c - line_start;
730
731
596k
                        const char *initial_c = c;
732
596k
                        c += 2;  // Avoid matching /*/: skip the /* before starting the search for
733
                                 // */.
734
735
121M
                        while (!(*c == '*' && *(c + 1) == '/')) {
736
120M
                            if (*c == '\0') {
737
214
                                auto msg = "multi-line comment has no terminating */.";
738
214
                                throw StaticError(filename, begin, msg);
739
214
                            }
740
120M
                            if (*c == '\n') {
741
                                // Just keep track of the line / column counters.
742
16.7M
                                line_number++;
743
16.7M
                                line_start = c + 1;
744
16.7M
                            }
745
120M
                            ++c;
746
120M
                        }
747
596k
                        c += 2;  // Move the pointer to the char after the closing '/'.
748
749
596k
                        std::string comment(initial_c,
750
596k
                                            c - initial_c);  // Includes the "/*" and "*/".
751
752
                        // Lex whitespace after comment
753
596k
                        unsigned new_lines_after, indent_after;
754
596k
                        lex_ws(c, new_lines_after, indent_after, line_start, line_number);
755
596k
                        std::vector<std::string> lines;
756
596k
                        if (comment.find('\n') >= comment.length()) {
757
                            // Comment looks like /* foo */
758
344k
                            lines.push_back(comment);
759
344k
                            fodder.emplace_back(FodderElement::INTERSTITIAL, 0, 0, lines);
760
344k
                            if (new_lines_after > 0) {
761
329k
                                fodder.emplace_back(FodderElement::LINE_END,
762
329k
                                                    new_lines_after - 1,
763
329k
                                                    indent_after,
764
329k
                                                    EMPTY);
765
329k
                                fresh_line = true;
766
329k
                            }
767
344k
                        } else {
768
252k
                            lines = line_split(comment, margin);
769
252k
                            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
252k
                            bool all_star = true;
773
17.0M
                            for (auto &l : lines) {
774
17.0M
                                if (l[0] != '*')
775
16.8M
                                    all_star = false;
776
17.0M
                            }
777
252k
                            if (all_star) {
778
0
                                for (auto &l : lines) {
779
0
                                    if (l[0] == '*')
780
0
                                        l = " " + l;
781
0
                                }
782
0
                            }
783
252k
                            if (new_lines_after == 0) {
784
                                // Ensure a line end after the paragraph.
785
28.3k
                                new_lines_after = 1;
786
28.3k
                                indent_after = 0;
787
28.3k
                            }
788
252k
                            fodder_push_back(fodder,
789
252k
                                             FodderElement(FodderElement::PARAGRAPH,
790
252k
                                                           new_lines_after - 1,
791
252k
                                                           indent_after,
792
252k
                                                           lines));
793
252k
                            fresh_line = true;
794
252k
                        }
795
596k
                        continue;  // We've not got a token, just fodder, so keep scanning.
796
596k
                    }
797
798
                    // Text block
799
30.8M
                    if (*c == '|' && *(c + 1) == '|' && *(c + 2) == '|') {
800
16.3k
                        c += 3;  // Skip the "|||".
801
802
16.3k
                        bool chomp_trailing_nl = false;
803
16.3k
                        if (*c == '-') {
804
1.13k
                            chomp_trailing_nl = true;
805
1.13k
                            c++;
806
1.13k
                        }
807
808
20.9k
                        while (is_horz_ws(*c)) ++c;  // Chomp whitespace at end of line.
809
16.3k
                        if (*c != '\n') {
810
111
                            auto msg = "text block syntax requires new line after |||.";
811
111
                            throw StaticError(filename, begin, msg);
812
111
                        }
813
16.2k
                        std::stringstream block;
814
16.2k
                        c++;  // Skip the "\n"
815
16.2k
                        line_number++;
816
                        // Skip any blank lines at the beginning of the block.
817
20.2k
                        while (*c == '\n') {
818
3.98k
                            line_number++;
819
3.98k
                            ++c;
820
3.98k
                            block << '\n';
821
3.98k
                        }
822
16.2k
                        line_start = c;
823
16.2k
                        const char *first_line = c;
824
16.2k
                        int ws_chars = whitespace_check(first_line, c);
825
16.2k
                        string_block_indent = std::string(first_line, ws_chars);
826
16.2k
                        if (ws_chars == 0) {
827
64
                            auto msg = "text block's first line must start with whitespace.";
828
64
                            throw StaticError(filename, begin, msg);
829
64
                        }
830
21.2k
                        while (true) {
831
21.2k
                            assert(ws_chars > 0);
832
                            // Read up to the \n
833
9.30M
                            for (c = &c[ws_chars]; *c != '\n'; ++c) {
834
9.28M
                                if (*c == '\0')
835
185
                                    throw StaticError(filename, begin, "unexpected EOF");
836
9.28M
                                block << *c;
837
9.28M
                            }
838
                            // Add the \n
839
21.0k
                            block << '\n';
840
21.0k
                            ++c;
841
21.0k
                            line_number++;
842
21.0k
                            line_start = c;
843
                            // Skip any blank lines
844
24.5k
                            while (*c == '\n') {
845
3.47k
                                line_number++;
846
3.47k
                                ++c;
847
3.47k
                                block << '\n';
848
3.47k
                            }
849
                            // Examine next line
850
21.0k
                            ws_chars = whitespace_check(first_line, c);
851
21.0k
                            if (ws_chars == 0) {
852
                                // End of text block (or indentation error).
853
                                // Count actual whitespace on this line.
854
15.9k
                                int actual_ws = 0;
855
237k
                                while (c[actual_ws] == ' ' ||
856
221k
                                       c[actual_ws] == '\t') {
857
221k
                                    actual_ws++;
858
221k
                                }
859
860
                                // Check if this is the terminator |||
861
15.9k
                                bool is_terminator = (
862
15.9k
                                    c[actual_ws] == '|' &&
863
15.7k
                                    c[actual_ws + 1] == '|' &&
864
15.7k
                                    c[actual_ws + 2] == '|');
865
866
15.9k
                                if (!is_terminator) {
867
                                    // Not a terminator - check if it's an
868
                                    // indentation issue.
869
232
                                    if (actual_ws > 0) {
870
                                        // Has whitespace but doesn't match expected
871
                                        // indentation.
872
117
                                        std::stringstream msg;
873
117
                                        msg << "text block indentation mismatch: "
874
117
                                                "expected at least ";
875
117
                                        describe_whitespace(msg, string_block_indent);
876
117
                                        msg << ", found ";
877
117
                                        describe_whitespace(msg, std::string(c, actual_ws));
878
117
                                        throw StaticError(filename, begin, msg.str());
879
117
                                    } else {
880
                                        // No whitespace and no ||| - missing
881
                                        // terminator.
882
115
                                        auto msg =
883
115
                                            "text block not terminated with |||";
884
115
                                        throw StaticError(filename, begin, msg);
885
115
                                    }
886
232
                                }
887
888
                                // Valid termination - skip over any whitespace.
889
133k
                                while (*c == ' ' || *c == '\t') {
890
117k
                                    string_block_term_indent += *c;
891
117k
                                    ++c;
892
117k
                                }
893
                                // Skip the |||
894
15.7k
                                c += 3;  // Leave after the last |
895
15.7k
                                data = block.str();
896
15.7k
                                kind = Token::STRING_BLOCK;
897
15.7k
                                if (chomp_trailing_nl) {
898
1.09k
                                    assert(data.back() == '\n');
899
1.09k
                                    data.pop_back();
900
1.09k
                                }
901
15.7k
                                break;  // Out of the while loop.
902
15.7k
                            }
903
21.0k
                        }
904
905
15.7k
                        break;  // Out of the switch.
906
16.1k
                    }
907
908
30.8M
                    const char *operator_begin = c;
909
102M
                    for (; is_symbol(*c); ++c) {
910
                        // Not allowed // in operators
911
71.7M
                        if (*c == '/' && *(c + 1) == '/')
912
1.03k
                            break;
913
                        // Not allowed /* in operators
914
71.7M
                        if (*c == '/' && *(c + 1) == '*')
915
1.58k
                            break;
916
                        // Not allowed ||| in operators
917
71.7M
                        if (*c == '|' && *(c + 1) == '|' && *(c + 2) == '|')
918
3.43k
                            break;
919
71.7M
                    }
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
36.2M
                    while (c > operator_begin + 1 && !allowed_at_end_of_operator(*(c - 1))) {
923
5.40M
                        c--;
924
5.40M
                    }
925
30.8M
                    data += std::string(operator_begin, c);
926
30.8M
                    if (data == "$") {
927
105k
                        kind = Token::DOLLAR;
928
105k
                        data = "";
929
30.6M
                    } else {
930
30.6M
                        kind = Token::OPERATOR;
931
30.6M
                    }
932
30.8M
                } else {
933
276
                    std::stringstream ss;
934
276
                    ss << "Could not lex the character ";
935
276
                    auto uc = (unsigned char)(*c);
936
276
                    if (*c < 32)
937
253
                        ss << "code " << unsigned(uc);
938
23
                    else
939
23
                        ss << "'" << *c << "'";
940
276
                    throw StaticError(filename, begin, ss.str());
941
276
                }
942
286M
        }
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
277M
        if (c == original_c) {
947
0
            throw StaticError(filename, begin, "internal lexing error:  pointer did not advance");
948
0
        }
949
950
277M
        Location end(line_number, (c + 1) - line_start);
951
277M
        r.emplace_back(kind,
952
277M
                       fodder,
953
277M
                       data,
954
277M
                       string_block_indent,
955
277M
                       string_block_term_indent,
956
277M
                       LocationRange(filename, begin, end));
957
277M
        fodder.clear();
958
277M
        fresh_line = false;
959
277M
    }
960
961
38.7k
    Location begin(line_number, c - line_start + 1);
962
38.7k
    Location end(line_number, (c + 1) - line_start + 1);
963
38.7k
    r.emplace_back(Token::END_OF_FILE, fodder, "", "", "", LocationRange(filename, begin, end));
964
38.7k
    return r;
965
40.2k
}
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