plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


plum-tooling/tree-sitter-plum/src/scanner.c
f997bee 1
#include "tree_sitter/array.h"
f997bee 2
#include "tree_sitter/parser.h"
f997bee 3
f997bee 4
#include <assert.h>
f997bee 5
#include <stdint.h>
f997bee 6
#include <stdio.h>
f997bee 7
#include <string.h>
4bd2c1c 8
4bd2c1c 9
enum TokenType {
f997bee 10
    NEWLINE,
f997bee 11
    INDENT,
f997bee 12
    DEDENT,
f997bee 13
    STRING_START,
f997bee 14
    STRING_CONTENT,
f997bee 15
    ESCAPE_INTERPOLATION,
f997bee 16
    STRING_END,
f997bee 17
    COMMENT,
f997bee 18
    CLOSE_PAREN,
f997bee 19
    CLOSE_BRACKET,
f997bee 20
    CLOSE_BRACE,
3971d41 21
    ARM_BREAK,
4bd2c1c 22
};
4bd2c1c 23
f997bee 24
typedef enum {
f997bee 25
    SingleQuote = 1 << 0,
f997bee 26
    DoubleQuote = 1 << 1,
f997bee 27
    BackQuote = 1 << 2,
f997bee 28
    Raw = 1 << 3,
f997bee 29
    Format = 1 << 4,
f997bee 30
    Triple = 1 << 5,
f997bee 31
    Bytes = 1 << 6,
f997bee 32
} Flags;
f997bee 33
f997bee 34
typedef struct {
f997bee 35
    char flags;
f997bee 36
} Delimiter;
f997bee 37
f997bee 38
static inline Delimiter new_delimiter() { return (Delimiter){0}; }
f997bee 39
f997bee 40
static inline bool is_format(Delimiter *delimiter) { return delimiter->flags & Format; }
f997bee 41
f997bee 42
static inline bool is_raw(Delimiter *delimiter) { return delimiter->flags & Raw; }
f997bee 43
f997bee 44
static inline bool is_triple(Delimiter *delimiter) { return delimiter->flags & Triple; }
f997bee 45
f997bee 46
static inline bool is_bytes(Delimiter *delimiter) { return delimiter->flags & Bytes; }
f997bee 47
f997bee 48
static inline int32_t end_character(Delimiter *delimiter) {
f997bee 49
    if (delimiter->flags & SingleQuote) {
f997bee 50
        return '\'';
f997bee 51
    }
f997bee 52
    if (delimiter->flags & DoubleQuote) {
f997bee 53
        return '"';
f997bee 54
    }
f997bee 55
    if (delimiter->flags & BackQuote) {
f997bee 56
        return '`';
f997bee 57
    }
f997bee 58
    return 0;
f997bee 59
}
f997bee 60
f997bee 61
static inline void set_format(Delimiter *delimiter) { delimiter->flags |= Format; }
f997bee 62
f997bee 63
static inline void set_raw(Delimiter *delimiter) { delimiter->flags |= Raw; }
f997bee 64
f997bee 65
static inline void set_triple(Delimiter *delimiter) { delimiter->flags |= Triple; }
f997bee 66
f997bee 67
static inline void set_bytes(Delimiter *delimiter) { delimiter->flags |= Bytes; }
f997bee 68
f997bee 69
static inline void set_end_character(Delimiter *delimiter, int32_t character) {
f997bee 70
    switch (character) {
f997bee 71
        case '\'':
f997bee 72
            delimiter->flags |= SingleQuote;
f997bee 73
            break;
f997bee 74
        case '"':
f997bee 75
            delimiter->flags |= DoubleQuote;
f997bee 76
            break;
f997bee 77
        case '`':
f997bee 78
            delimiter->flags |= BackQuote;
f997bee 79
            break;
f997bee 80
        default:
f997bee 81
            assert(false);
f997bee 82
    }
f997bee 83
}
f997bee 84
f997bee 85
typedef struct {
f997bee 86
    Array(uint16_t) indents;
f997bee 87
    Array(Delimiter) delimiters;
f997bee 88
    bool inside_f_string;
f997bee 89
} Scanner;
f997bee 90
f997bee 91
static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
f997bee 92
f997bee 93
static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
f997bee 94
f997bee 95
bool tree_sitter_plum_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
f997bee 96
    Scanner *scanner = (Scanner *)payload;
f997bee 97
f997bee 98
    bool error_recovery_mode = valid_symbols[STRING_CONTENT] && valid_symbols[INDENT];
f997bee 99
    bool within_brackets = valid_symbols[CLOSE_BRACE] || valid_symbols[CLOSE_PAREN] || valid_symbols[CLOSE_BRACKET];
f997bee 100
f997bee 101
    bool advanced_once = false;
f997bee 102
    if (valid_symbols[ESCAPE_INTERPOLATION] && scanner->delimiters.size > 0 &&
f997bee 103
        (lexer->lookahead == '{' || lexer->lookahead == '}') && !error_recovery_mode) {
f997bee 104
        Delimiter *delimiter = array_back(&scanner->delimiters);
f997bee 105
        if (is_format(delimiter)) {
f997bee 106
            lexer->mark_end(lexer);
f997bee 107
            bool is_left_brace = lexer->lookahead == '{';
f997bee 108
            advance(lexer);
f997bee 109
            advanced_once = true;
f997bee 110
            if ((lexer->lookahead == '{' && is_left_brace) || (lexer->lookahead == '}' && !is_left_brace)) {
f997bee 111
                advance(lexer);
f997bee 112
                lexer->mark_end(lexer);
f997bee 113
                lexer->result_symbol = ESCAPE_INTERPOLATION;
f997bee 114
                return true;
f997bee 115
            }
f997bee 116
            return false;
f997bee 117
        }
f997bee 118
    }
f997bee 119
f997bee 120
    if (valid_symbols[STRING_CONTENT] && scanner->delimiters.size > 0 && !error_recovery_mode) {
f997bee 121
        Delimiter *delimiter = array_back(&scanner->delimiters);
f997bee 122
        int32_t end_char = end_character(delimiter);
f997bee 123
        bool has_content = advanced_once;
f997bee 124
        while (lexer->lookahead) {
f997bee 125
            if ((advanced_once || lexer->lookahead == '{' || lexer->lookahead == '}') && is_format(delimiter)) {
f997bee 126
                lexer->mark_end(lexer);
f997bee 127
                lexer->result_symbol = STRING_CONTENT;
f997bee 128
                return has_content;
f997bee 129
            }
f997bee 130
            if (lexer->lookahead == '\\') {
f997bee 131
                if (is_raw(delimiter)) {
f997bee 132
                    // Step over the backslash.
f997bee 133
                    advance(lexer);
f997bee 134
                    // Step over any escaped quotes.
f997bee 135
                    if (lexer->lookahead == end_character(delimiter) || lexer->lookahead == '\\') {
f997bee 136
                        advance(lexer);
f997bee 137
                    }
f997bee 138
                    // Step over newlines
f997bee 139
                    if (lexer->lookahead == '\r') {
f997bee 140
                        advance(lexer);
f997bee 141
                        if (lexer->lookahead == '\n') {
f997bee 142
                            advance(lexer);
f997bee 143
                        }
f997bee 144
                    } else if (lexer->lookahead == '\n') {
f997bee 145
                        advance(lexer);
f997bee 146
                    }
f997bee 147
                    continue;
f997bee 148
                }
f997bee 149
                if (is_bytes(delimiter)) {
f997bee 150
                    lexer->mark_end(lexer);
f997bee 151
                    advance(lexer);
f997bee 152
                    if (lexer->lookahead == 'N' || lexer->lookahead == 'u' || lexer->lookahead == 'U') {
f997bee 153
                        // In bytes string, \N{...}, \uXXXX and \UXXXXXXXX are
f997bee 154
                        // not escape sequences
f997bee 155
                        // https://docs.plum.org/3/reference/lexical_analysis.html#string-and-bytes-literals
f997bee 156
                        advance(lexer);
f997bee 157
                    } else {
f997bee 158
                        lexer->result_symbol = STRING_CONTENT;
f997bee 159
                        return has_content;
f997bee 160
                    }
f997bee 161
                } else {
f997bee 162
                    lexer->mark_end(lexer);
f997bee 163
                    lexer->result_symbol = STRING_CONTENT;
f997bee 164
                    return has_content;
f997bee 165
                }
f997bee 166
            } else if (lexer->lookahead == end_char) {
f997bee 167
                if (is_triple(delimiter)) {
f997bee 168
                    lexer->mark_end(lexer);
f997bee 169
                    advance(lexer);
f997bee 170
                    if (lexer->lookahead == end_char) {
f997bee 171
                        advance(lexer);
f997bee 172
                        if (lexer->lookahead == end_char) {
f997bee 173
                            if (has_content) {
f997bee 174
                                lexer->result_symbol = STRING_CONTENT;
f997bee 175
                            } else {
f997bee 176
                                advance(lexer);
f997bee 177
                                lexer->mark_end(lexer);
f997bee 178
                                array_pop(&scanner->delimiters);
f997bee 179
                                lexer->result_symbol = STRING_END;
f997bee 180
                                scanner->inside_f_string = false;
f997bee 181
                            }
f997bee 182
                            return true;
f997bee 183
                        }
f997bee 184
                        lexer->mark_end(lexer);
f997bee 185
                        lexer->result_symbol = STRING_CONTENT;
f997bee 186
                        return true;
f997bee 187
                    }
f997bee 188
                    lexer->mark_end(lexer);
f997bee 189
                    lexer->result_symbol = STRING_CONTENT;
f997bee 190
                    return true;
f997bee 191
                }
f997bee 192
                if (has_content) {
f997bee 193
                    lexer->result_symbol = STRING_CONTENT;
f997bee 194
                } else {
f997bee 195
                    advance(lexer);
f997bee 196
                    array_pop(&scanner->delimiters);
f997bee 197
                    lexer->result_symbol = STRING_END;
f997bee 198
                    scanner->inside_f_string = false;
f997bee 199
                }
f997bee 200
                lexer->mark_end(lexer);
f997bee 201
                return true;
f997bee 202
f997bee 203
            } else if (lexer->lookahead == '\n' && has_content && !is_triple(delimiter)) {
f997bee 204
                return false;
f997bee 205
            }
f997bee 206
            advance(lexer);
f997bee 207
            has_content = true;
f997bee 208
        }
f997bee 209
    }
f997bee 210
f997bee 211
    lexer->mark_end(lexer);
f997bee 212
f997bee 213
    bool found_end_of_line = false;
f997bee 214
    uint32_t indent_length = 0;
f997bee 215
    int32_t first_comment_indent_length = -1;
f997bee 216
    for (;;) {
f997bee 217
        if (lexer->lookahead == '\n') {
f997bee 218
            found_end_of_line = true;
f997bee 219
            indent_length = 0;
f997bee 220
            skip(lexer);
f997bee 221
        } else if (lexer->lookahead == ' ') {
f997bee 222
            indent_length++;
f997bee 223
            skip(lexer);
f997bee 224
        } else if (lexer->lookahead == '\r' || lexer->lookahead == '\f') {
f997bee 225
            indent_length = 0;
f997bee 226
            skip(lexer);
f997bee 227
        } else if (lexer->lookahead == '\t') {
f997bee 228
            indent_length += 8;
f997bee 229
            skip(lexer);
f997bee 230
        } else if (lexer->lookahead == '#' && (valid_symbols[INDENT] || valid_symbols[DEDENT] ||
660674c 231
                                               valid_symbols[NEWLINE])) {
f997bee 232
            // If we haven't found an EOL yet,
f997bee 233
            // then this is a comment after an expression:
f997bee 234
            //   foo = bar # comment
f997bee 235
            // Just return, since we don't want to generate an indent/dedent
f997bee 236
            // token.
f997bee 237
            if (!found_end_of_line) {
f997bee 238
                return false;
f997bee 239
            }
f997bee 240
            if (first_comment_indent_length == -1) {
f997bee 241
                first_comment_indent_length = (int32_t)indent_length;
f997bee 242
            }
f997bee 243
            while (lexer->lookahead && lexer->lookahead != '\n') {
f997bee 244
                skip(lexer);
f997bee 245
            }
f997bee 246
            skip(lexer);
f997bee 247
            indent_length = 0;
f997bee 248
        } else if (lexer->lookahead == '\\') {
f997bee 249
            skip(lexer);
f997bee 250
            if (lexer->lookahead == '\r') {
f997bee 251
                skip(lexer);
f997bee 252
            }
f997bee 253
            if (lexer->lookahead == '\n' || lexer->eof(lexer)) {
f997bee 254
                skip(lexer);
f997bee 255
            } else {
f997bee 256
                return false;
f997bee 257
            }
f997bee 258
        } else if (lexer->eof(lexer)) {
f997bee 259
            indent_length = 0;
f997bee 260
            found_end_of_line = true;
f997bee 261
            break;
f997bee 262
        } else {
f997bee 263
            break;
f997bee 264
        }
f997bee 265
    }
f997bee 266
35af6cf 267
    // A closure literal's indented body, used directly as a call argument, can be
35af6cf 268
    // immediately followed by a closing bracket with no intervening newline at all
35af6cf 269
    // (`each(|v|\n  v)`), since the body's last statement and the bracket that ends
35af6cf 270
    // the enclosing call are on the same line. The loop above only ever sets
35af6cf 271
    // `found_end_of_line` by actually consuming a `\n`, so that shape reaches here
35af6cf 272
    // with `found_end_of_line == false` and no dedent is ever considered — even
35af6cf 273
    // though the grammar, mid-parse, is already asking for one (`valid_symbols[DEDENT]`
35af6cf 274
    // is true here exactly when a `body` node needs to close before the bracket can be
35af6cf 275
    // shifted). Treat that specific situation as equivalent to "found a blank new
35af6cf 276
    // line" for dedent purposes only: there's no partial line indentation to measure
35af6cf 277
    // (we're mid-expression, not at a line start), so it behaves as `indent_length == 0`,
35af6cf 278
    // which is `< current_indent_length` for any real indented block. Never treat it as
35af6cf 279
    // a NEWLINE — the grammar isn't expecting one at a raw closing bracket.
35af6cf 280
    //
35af6cf 281
    // (A comma continuing the argument list on the same line, e.g.
35af6cf 282
    // `each(|v|\n  v, extra)`, is the same underlying shape but isn't handled here:
35af6cf 283
    // at that exact position the parser's automaton doesn't have `valid_symbols[DEDENT]`
35af6cf 284
    // true, so there's no signal to act on without guessing. Out of scope for now —
35af6cf 285
    // put the closure last, or give it its own line, to work around it.)
35af6cf 286
    bool at_closing_bracket_without_newline =
35af6cf 287
        !found_end_of_line && within_brackets && valid_symbols[DEDENT] &&
35af6cf 288
        (lexer->lookahead == ')' || lexer->lookahead == ']' || lexer->lookahead == '}');
35af6cf 289
    if (at_closing_bracket_without_newline) {
35af6cf 290
        indent_length = 0;
35af6cf 291
    }
35af6cf 292
35af6cf 293
    if (found_end_of_line || at_closing_bracket_without_newline) {
f997bee 294
        if (scanner->indents.size > 0) {
f997bee 295
            uint16_t current_indent_length = *array_back(&scanner->indents);
f997bee 296
f997bee 297
            if (valid_symbols[INDENT] && indent_length > current_indent_length) {
f997bee 298
                array_push(&scanner->indents, indent_length);
f997bee 299
                lexer->result_symbol = INDENT;
f997bee 300
                return true;
f997bee 301
            }
f997bee 302
f997bee 303
            bool next_tok_is_string_start =
f997bee 304
                lexer->lookahead == '\"' || lexer->lookahead == '\'' || lexer->lookahead == '`';
f997bee 305
f997bee 306
            if ((valid_symbols[DEDENT] ||
f997bee 307
                 (!valid_symbols[NEWLINE] && !(valid_symbols[STRING_START] && next_tok_is_string_start) &&
f997bee 308
                  !within_brackets)) &&
f997bee 309
                indent_length < current_indent_length && !scanner->inside_f_string &&
f997bee 310
f997bee 311
                // Wait to create a dedent token until we've consumed any
f997bee 312
                // comments
f997bee 313
                // whose indentation matches the current block.
f997bee 314
                first_comment_indent_length < (int32_t)current_indent_length) {
f997bee 315
                array_pop(&scanner->indents);
f997bee 316
                lexer->result_symbol = DEDENT;
f997bee 317
                return true;
f997bee 318
            }
3971d41 319
3971d41 320
            // `guard_case` arms are subject-less match clauses that all start with the
3971d41 321
            // same `|` token that also denotes the bitwise-or operator (and, for an
3971d41 322
            // inline expression body, could otherwise be mistaken for a continuation of
3971d41 323
            // that body's own binary expression, e.g. `| x > y => 1` immediately
3971d41 324
            // followed by `| x == y => 0` parsing as `1 | x == y`). Since the grammar
3971d41 325
            // never asks for `_newline`/`_dedent` between two arms at the *same*
3971d41 326
            // indentation (nothing to indent into, nothing to dedent out of), there is
3971d41 327
            // otherwise no signal at all marking that boundary — the raw `\r?\n` is a
3971d41 328
            // grammar-level extra and is silently transparent. ARM_BREAK plugs that gap:
3971d41 329
            // requested only from the guard_case-repeat position, it fires whenever the
3971d41 330
            // next significant token starts a fresh line at exactly the current block's
3971d41 331
            // indentation, forcing the parser to end the current arm there instead of
3971d41 332
            // extending its expression across the line boundary.
3971d41 333
            if (valid_symbols[ARM_BREAK] && found_end_of_line && !error_recovery_mode &&
3971d41 334
                indent_length == current_indent_length) {
3971d41 335
                lexer->result_symbol = ARM_BREAK;
3971d41 336
                return true;
3971d41 337
            }
f997bee 338
        }
f997bee 339
35af6cf 340
        if (found_end_of_line && valid_symbols[NEWLINE] && !error_recovery_mode) {
f997bee 341
            lexer->result_symbol = NEWLINE;
f997bee 342
            return true;
f997bee 343
        }
f997bee 344
    }
f997bee 345
f997bee 346
    if (first_comment_indent_length == -1 && valid_symbols[STRING_START]) {
f997bee 347
        Delimiter delimiter = new_delimiter();
f997bee 348
f997bee 349
        bool has_flags = false;
0d43379 350
        // while (lexer->lookahead) {
0d43379 351
        //     if (lexer->lookahead == 'f' || lexer->lookahead == 'F') {
0d43379 352
        //         set_format(&delimiter);
0d43379 353
        //     } else if (lexer->lookahead == 'r' || lexer->lookahead == 'R') {
0d43379 354
        //         set_raw(&delimiter);
0d43379 355
        //     } else if (lexer->lookahead == 'b' || lexer->lookahead == 'B') {
0d43379 356
        //         set_bytes(&delimiter);
0d43379 357
        //     } else if (lexer->lookahead != 'u' && lexer->lookahead != 'U') {
0d43379 358
        //         break;
0d43379 359
        //     }
0d43379 360
        //     has_flags = true;
0d43379 361
        //     advance(lexer);
0d43379 362
        // }
f997bee 363
f997bee 364
        if (lexer->lookahead == '`') {
f997bee 365
            set_end_character(&delimiter, '`');
f997bee 366
            advance(lexer);
f997bee 367
            lexer->mark_end(lexer);
f997bee 368
        } else if (lexer->lookahead == '\'') {
f997bee 369
            set_end_character(&delimiter, '\'');
f997bee 370
            advance(lexer);
f997bee 371
            lexer->mark_end(lexer);
f997bee 372
            if (lexer->lookahead == '\'') {
f997bee 373
                advance(lexer);
f997bee 374
                if (lexer->lookahead == '\'') {
f997bee 375
                    advance(lexer);
f997bee 376
                    lexer->mark_end(lexer);
f997bee 377
                    set_triple(&delimiter);
f997bee 378
                }
f997bee 379
            }
f997bee 380
        } else if (lexer->lookahead == '"') {
0d43379 381
            set_format(&delimiter);
f997bee 382
            set_end_character(&delimiter, '"');
f997bee 383
            advance(lexer);
f997bee 384
            lexer->mark_end(lexer);
f997bee 385
            if (lexer->lookahead == '"') {
f997bee 386
                advance(lexer);
f997bee 387
                if (lexer->lookahead == '"') {
f997bee 388
                    advance(lexer);
f997bee 389
                    lexer->mark_end(lexer);
f997bee 390
                    set_triple(&delimiter);
f997bee 391
                }
f997bee 392
            }
f997bee 393
        }
f997bee 394
f997bee 395
        if (end_character(&delimiter)) {
f997bee 396
            array_push(&scanner->delimiters, delimiter);
f997bee 397
            lexer->result_symbol = STRING_START;
f997bee 398
            scanner->inside_f_string = is_format(&delimiter);
f997bee 399
            return true;
f997bee 400
        }
f997bee 401
        if (has_flags) {
f997bee 402
            return false;
f997bee 403
        }
f997bee 404
    }
f997bee 405
f997bee 406
    return false;
f997bee 407
}
f997bee 408
f997bee 409
unsigned tree_sitter_plum_external_scanner_serialize(void *payload, char *buffer) {
f997bee 410
    Scanner *scanner = (Scanner *)payload;
f997bee 411
f997bee 412
    size_t size = 0;
f997bee 413
f997bee 414
    buffer[size++] = (char)scanner->inside_f_string;
f997bee 415
f997bee 416
    size_t delimiter_count = scanner->delimiters.size;
f997bee 417
    if (delimiter_count > UINT8_MAX) {
f997bee 418
        delimiter_count = UINT8_MAX;
f997bee 419
    }
f997bee 420
    buffer[size++] = (char)delimiter_count;
f997bee 421
f997bee 422
    if (delimiter_count > 0) {
f997bee 423
        memcpy(&buffer[size], scanner->delimiters.contents, delimiter_count);
f997bee 424
    }
f997bee 425
    size += delimiter_count;
f997bee 426
f997bee 427
    uint32_t iter = 1;
f997bee 428
    for (; iter < scanner->indents.size && size < TREE_SITTER_SERIALIZATION_BUFFER_SIZE; ++iter) {
f997bee 429
        buffer[size++] = (char)*array_get(&scanner->indents, iter);
f997bee 430
    }
f997bee 431
f997bee 432
    return size;
f997bee 433
}
f997bee 434
f997bee 435
void tree_sitter_plum_external_scanner_deserialize(void *payload, const char *buffer, unsigned length) {
f997bee 436
    Scanner *scanner = (Scanner *)payload;
f997bee 437
f997bee 438
    array_delete(&scanner->delimiters);
f997bee 439
    array_delete(&scanner->indents);
f997bee 440
    array_push(&scanner->indents, 0);
f997bee 441
f997bee 442
    if (length > 0) {
f997bee 443
        size_t size = 0;
f997bee 444
f997bee 445
        scanner->inside_f_string = (bool)buffer[size++];
f997bee 446
f997bee 447
        size_t delimiter_count = (uint8_t)buffer[size++];
f997bee 448
        if (delimiter_count > 0) {
f997bee 449
            array_reserve(&scanner->delimiters, delimiter_count);
f997bee 450
            scanner->delimiters.size = delimiter_count;
f997bee 451
            memcpy(scanner->delimiters.contents, &buffer[size], delimiter_count);
f997bee 452
            size += delimiter_count;
f997bee 453
        }
f997bee 454
f997bee 455
        for (; size < length; size++) {
f997bee 456
            array_push(&scanner->indents, (unsigned char)buffer[size]);
f997bee 457
        }
f997bee 458
    }
f997bee 459
}
f997bee 460
f997bee 461
void *tree_sitter_plum_external_scanner_create() {
f997bee 462
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
f997bee 463
    _Static_assert(sizeof(Delimiter) == sizeof(char), "");
f997bee 464
#else
f997bee 465
    assert(sizeof(Delimiter) == sizeof(char));
f997bee 466
#endif
f997bee 467
    Scanner *scanner = calloc(1, sizeof(Scanner));
f997bee 468
    array_init(&scanner->indents);
f997bee 469
    array_init(&scanner->delimiters);
f997bee 470
    tree_sitter_plum_external_scanner_deserialize(scanner, NULL, 0);
f997bee 471
    return scanner;
f997bee 472
}
f997bee 473
f997bee 474
void tree_sitter_plum_external_scanner_destroy(void *payload) {
f997bee 475
    Scanner *scanner = (Scanner *)payload;
f997bee 476
    array_delete(&scanner->indents);
f997bee 477
    array_delete(&scanner->delimiters);
f997bee 478
    free(scanner);
5d12d71 479
}