Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/gdscript/gdscript_tokenizer.cpp
10277 views
1
/**************************************************************************/
2
/* gdscript_tokenizer.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "gdscript_tokenizer.h"
32
33
#include "core/error/error_macros.h"
34
#include "core/string/char_utils.h"
35
36
#ifdef DEBUG_ENABLED
37
#include "servers/text_server.h"
38
#endif
39
40
#ifdef TOOLS_ENABLED
41
#include "editor/settings/editor_settings.h"
42
#endif
43
44
static const char *token_names[] = {
45
"Empty", // EMPTY,
46
// Basic
47
"Annotation", // ANNOTATION
48
"Identifier", // IDENTIFIER,
49
"Literal", // LITERAL,
50
// Comparison
51
"<", // LESS,
52
"<=", // LESS_EQUAL,
53
">", // GREATER,
54
">=", // GREATER_EQUAL,
55
"==", // EQUAL_EQUAL,
56
"!=", // BANG_EQUAL,
57
// Logical
58
"and", // AND,
59
"or", // OR,
60
"not", // NOT,
61
"&&", // AMPERSAND_AMPERSAND,
62
"||", // PIPE_PIPE,
63
"!", // BANG,
64
// Bitwise
65
"&", // AMPERSAND,
66
"|", // PIPE,
67
"~", // TILDE,
68
"^", // CARET,
69
"<<", // LESS_LESS,
70
">>", // GREATER_GREATER,
71
// Math
72
"+", // PLUS,
73
"-", // MINUS,
74
"*", // STAR,
75
"**", // STAR_STAR,
76
"/", // SLASH,
77
"%", // PERCENT,
78
// Assignment
79
"=", // EQUAL,
80
"+=", // PLUS_EQUAL,
81
"-=", // MINUS_EQUAL,
82
"*=", // STAR_EQUAL,
83
"**=", // STAR_STAR_EQUAL,
84
"/=", // SLASH_EQUAL,
85
"%=", // PERCENT_EQUAL,
86
"<<=", // LESS_LESS_EQUAL,
87
">>=", // GREATER_GREATER_EQUAL,
88
"&=", // AMPERSAND_EQUAL,
89
"|=", // PIPE_EQUAL,
90
"^=", // CARET_EQUAL,
91
// Control flow
92
"if", // IF,
93
"elif", // ELIF,
94
"else", // ELSE,
95
"for", // FOR,
96
"while", // WHILE,
97
"break", // BREAK,
98
"continue", // CONTINUE,
99
"pass", // PASS,
100
"return", // RETURN,
101
"match", // MATCH,
102
"when", // WHEN,
103
// Keywords
104
"as", // AS,
105
"assert", // ASSERT,
106
"await", // AWAIT,
107
"breakpoint", // BREAKPOINT,
108
"class", // CLASS,
109
"class_name", // CLASS_NAME,
110
"const", // TK_CONST,
111
"enum", // ENUM,
112
"extends", // EXTENDS,
113
"func", // FUNC,
114
"in", // TK_IN,
115
"is", // IS,
116
"namespace", // NAMESPACE
117
"preload", // PRELOAD,
118
"self", // SELF,
119
"signal", // SIGNAL,
120
"static", // STATIC,
121
"super", // SUPER,
122
"trait", // TRAIT,
123
"var", // VAR,
124
"void", // TK_VOID,
125
"yield", // YIELD,
126
// Punctuation
127
"[", // BRACKET_OPEN,
128
"]", // BRACKET_CLOSE,
129
"{", // BRACE_OPEN,
130
"}", // BRACE_CLOSE,
131
"(", // PARENTHESIS_OPEN,
132
")", // PARENTHESIS_CLOSE,
133
",", // COMMA,
134
";", // SEMICOLON,
135
".", // PERIOD,
136
"..", // PERIOD_PERIOD,
137
"...", // PERIOD_PERIOD_PERIOD,
138
":", // COLON,
139
"$", // DOLLAR,
140
"->", // FORWARD_ARROW,
141
"_", // UNDERSCORE,
142
// Whitespace
143
"Newline", // NEWLINE,
144
"Indent", // INDENT,
145
"Dedent", // DEDENT,
146
// Constants
147
"PI", // CONST_PI,
148
"TAU", // CONST_TAU,
149
"INF", // CONST_INF,
150
"NaN", // CONST_NAN,
151
// Error message improvement
152
"VCS conflict marker", // VCS_CONFLICT_MARKER,
153
"`", // BACKTICK,
154
"?", // QUESTION_MARK,
155
// Special
156
"Error", // ERROR,
157
"End of file", // EOF,
158
};
159
160
// Avoid desync.
161
static_assert(std::size(token_names) == GDScriptTokenizer::Token::TK_MAX, "Amount of token names don't match the amount of token types.");
162
163
const char *GDScriptTokenizer::Token::get_name() const {
164
ERR_FAIL_INDEX_V_MSG(type, TK_MAX, "<error>", "Using token type out of the enum.");
165
return token_names[type];
166
}
167
168
String GDScriptTokenizer::Token::get_debug_name() const {
169
switch (type) {
170
case IDENTIFIER:
171
return vformat(R"(identifier "%s")", source);
172
default:
173
return vformat(R"("%s")", get_name());
174
}
175
}
176
177
bool GDScriptTokenizer::Token::can_precede_bin_op() const {
178
switch (type) {
179
case IDENTIFIER:
180
case LITERAL:
181
case SELF:
182
case BRACKET_CLOSE:
183
case BRACE_CLOSE:
184
case PARENTHESIS_CLOSE:
185
case CONST_PI:
186
case CONST_TAU:
187
case CONST_INF:
188
case CONST_NAN:
189
return true;
190
default:
191
return false;
192
}
193
}
194
195
bool GDScriptTokenizer::Token::is_identifier() const {
196
// Note: Most keywords should not be recognized as identifiers.
197
// These are only exceptions for stuff that already is on the engine's API.
198
switch (type) {
199
case IDENTIFIER:
200
case MATCH: // Used in String.match().
201
case WHEN: // New keyword, avoid breaking existing code.
202
// Allow constants to be treated as regular identifiers.
203
case CONST_PI:
204
case CONST_INF:
205
case CONST_NAN:
206
case CONST_TAU:
207
return true;
208
default:
209
return false;
210
}
211
}
212
213
bool GDScriptTokenizer::Token::is_node_name() const {
214
// This is meant to allow keywords with the $ notation, but not as general identifiers.
215
switch (type) {
216
case IDENTIFIER:
217
case AND:
218
case AS:
219
case ASSERT:
220
case AWAIT:
221
case BREAK:
222
case BREAKPOINT:
223
case CLASS_NAME:
224
case CLASS:
225
case TK_CONST:
226
case CONST_PI:
227
case CONST_INF:
228
case CONST_NAN:
229
case CONST_TAU:
230
case CONTINUE:
231
case ELIF:
232
case ELSE:
233
case ENUM:
234
case EXTENDS:
235
case FOR:
236
case FUNC:
237
case IF:
238
case TK_IN:
239
case IS:
240
case MATCH:
241
case NAMESPACE:
242
case NOT:
243
case OR:
244
case PASS:
245
case PRELOAD:
246
case RETURN:
247
case SELF:
248
case SIGNAL:
249
case STATIC:
250
case SUPER:
251
case TRAIT:
252
case UNDERSCORE:
253
case VAR:
254
case TK_VOID:
255
case WHILE:
256
case WHEN:
257
case YIELD:
258
return true;
259
default:
260
return false;
261
}
262
}
263
264
String GDScriptTokenizer::get_token_name(Token::Type p_token_type) {
265
ERR_FAIL_INDEX_V_MSG(p_token_type, Token::TK_MAX, "<error>", "Using token type out of the enum.");
266
return token_names[p_token_type];
267
}
268
269
void GDScriptTokenizerText::set_source_code(const String &p_source_code) {
270
source = p_source_code;
271
_source = source.get_data();
272
_current = _source;
273
_start = _source;
274
line = 1;
275
column = 1;
276
length = p_source_code.length();
277
position = 0;
278
}
279
280
void GDScriptTokenizerText::set_cursor_position(int p_line, int p_column) {
281
cursor_line = p_line;
282
cursor_column = p_column;
283
}
284
285
void GDScriptTokenizerText::set_multiline_mode(bool p_state) {
286
multiline_mode = p_state;
287
}
288
289
void GDScriptTokenizerText::push_expression_indented_block() {
290
indent_stack_stack.push_back(indent_stack);
291
}
292
293
void GDScriptTokenizerText::pop_expression_indented_block() {
294
ERR_FAIL_COND(indent_stack_stack.is_empty());
295
indent_stack = indent_stack_stack.back()->get();
296
indent_stack_stack.pop_back();
297
}
298
299
int GDScriptTokenizerText::get_cursor_line() const {
300
return cursor_line;
301
}
302
303
int GDScriptTokenizerText::get_cursor_column() const {
304
return cursor_column;
305
}
306
307
bool GDScriptTokenizerText::is_past_cursor() const {
308
if (line < cursor_line) {
309
return false;
310
}
311
if (line > cursor_line) {
312
return true;
313
}
314
if (column < cursor_column) {
315
return false;
316
}
317
return true;
318
}
319
320
char32_t GDScriptTokenizerText::_advance() {
321
if (unlikely(_is_at_end())) {
322
return '\0';
323
}
324
_current++;
325
column++;
326
position++;
327
if (unlikely(_is_at_end())) {
328
// Add extra newline even if it's not there, to satisfy the parser.
329
newline(true);
330
// Also add needed unindent.
331
check_indent();
332
}
333
return _peek(-1);
334
}
335
336
void GDScriptTokenizerText::push_paren(char32_t p_char) {
337
paren_stack.push_back(p_char);
338
}
339
340
bool GDScriptTokenizerText::pop_paren(char32_t p_expected) {
341
if (paren_stack.is_empty()) {
342
return false;
343
}
344
char32_t actual = paren_stack.back()->get();
345
paren_stack.pop_back();
346
347
return actual == p_expected;
348
}
349
350
GDScriptTokenizer::Token GDScriptTokenizerText::pop_error() {
351
Token error = error_stack.back()->get();
352
error_stack.pop_back();
353
return error;
354
}
355
356
GDScriptTokenizer::Token GDScriptTokenizerText::make_token(Token::Type p_type) {
357
Token token(p_type);
358
token.start_line = start_line;
359
token.end_line = line;
360
token.start_column = start_column;
361
token.end_column = column;
362
token.source = String::utf32(Span(_start, _current - _start));
363
364
if (p_type != Token::ERROR && cursor_line > -1) {
365
// Also count whitespace after token.
366
int offset = 0;
367
while (_peek(offset) == ' ' || _peek(offset) == '\t') {
368
offset++;
369
}
370
int last_column = column + offset;
371
// Check cursor position in token.
372
if (start_line == line) {
373
// Single line token.
374
if (cursor_line == start_line && cursor_column >= start_column && cursor_column <= last_column) {
375
token.cursor_position = cursor_column - start_column;
376
if (cursor_column == start_column) {
377
token.cursor_place = CURSOR_BEGINNING;
378
} else if (cursor_column < column) {
379
token.cursor_place = CURSOR_MIDDLE;
380
} else {
381
token.cursor_place = CURSOR_END;
382
}
383
}
384
} else {
385
// Multi line token.
386
if (cursor_line == start_line && cursor_column >= start_column) {
387
// Is in first line.
388
token.cursor_position = cursor_column - start_column;
389
if (cursor_column == start_column) {
390
token.cursor_place = CURSOR_BEGINNING;
391
} else {
392
token.cursor_place = CURSOR_MIDDLE;
393
}
394
} else if (cursor_line == line && cursor_column <= last_column) {
395
// Is in last line.
396
token.cursor_position = cursor_column - start_column;
397
if (cursor_column < column) {
398
token.cursor_place = CURSOR_MIDDLE;
399
} else {
400
token.cursor_place = CURSOR_END;
401
}
402
} else if (cursor_line > start_line && cursor_line < line) {
403
// Is in middle line.
404
token.cursor_position = CURSOR_MIDDLE;
405
}
406
}
407
}
408
409
last_token = token;
410
return token;
411
}
412
413
GDScriptTokenizer::Token GDScriptTokenizerText::make_literal(const Variant &p_literal) {
414
Token token = make_token(Token::LITERAL);
415
token.literal = p_literal;
416
return token;
417
}
418
419
GDScriptTokenizer::Token GDScriptTokenizerText::make_identifier(const StringName &p_identifier) {
420
Token identifier = make_token(Token::IDENTIFIER);
421
identifier.literal = p_identifier;
422
return identifier;
423
}
424
425
GDScriptTokenizer::Token GDScriptTokenizerText::make_error(const String &p_message) {
426
Token error = make_token(Token::ERROR);
427
error.literal = p_message;
428
429
return error;
430
}
431
432
void GDScriptTokenizerText::push_error(const String &p_message) {
433
Token error = make_error(p_message);
434
error_stack.push_back(error);
435
}
436
437
void GDScriptTokenizerText::push_error(const Token &p_error) {
438
error_stack.push_back(p_error);
439
}
440
441
GDScriptTokenizer::Token GDScriptTokenizerText::make_paren_error(char32_t p_paren) {
442
if (paren_stack.is_empty()) {
443
return make_error(vformat("Closing \"%c\" doesn't have an opening counterpart.", p_paren));
444
}
445
Token error = make_error(vformat("Closing \"%c\" doesn't match the opening \"%c\".", p_paren, paren_stack.back()->get()));
446
paren_stack.pop_back(); // Remove opening one anyway.
447
return error;
448
}
449
450
GDScriptTokenizer::Token GDScriptTokenizerText::check_vcs_marker(char32_t p_test, Token::Type p_double_type) {
451
const char32_t *next = _current + 1;
452
int chars = 2; // Two already matched.
453
454
// Test before consuming characters, since we don't want to consume more than needed.
455
while (*next == p_test) {
456
chars++;
457
next++;
458
}
459
if (chars >= 7) {
460
// It is a VCS conflict marker.
461
while (chars > 1) {
462
// Consume all characters (first was already consumed by scan()).
463
_advance();
464
chars--;
465
}
466
return make_token(Token::VCS_CONFLICT_MARKER);
467
} else {
468
// It is only a regular double character token, so we consume the second character.
469
_advance();
470
return make_token(p_double_type);
471
}
472
}
473
474
GDScriptTokenizer::Token GDScriptTokenizerText::annotation() {
475
if (is_unicode_identifier_start(_peek())) {
476
_advance(); // Consume start character.
477
} else {
478
push_error("Expected annotation identifier after \"@\".");
479
}
480
while (is_unicode_identifier_continue(_peek())) {
481
// Consume all identifier characters.
482
_advance();
483
}
484
Token annotation = make_token(Token::ANNOTATION);
485
annotation.literal = StringName(annotation.source);
486
return annotation;
487
}
488
489
#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
490
KEYWORD_GROUP('a') \
491
KEYWORD("as", Token::AS) \
492
KEYWORD("and", Token::AND) \
493
KEYWORD("assert", Token::ASSERT) \
494
KEYWORD("await", Token::AWAIT) \
495
KEYWORD_GROUP('b') \
496
KEYWORD("break", Token::BREAK) \
497
KEYWORD("breakpoint", Token::BREAKPOINT) \
498
KEYWORD_GROUP('c') \
499
KEYWORD("class", Token::CLASS) \
500
KEYWORD("class_name", Token::CLASS_NAME) \
501
KEYWORD("const", Token::TK_CONST) \
502
KEYWORD("continue", Token::CONTINUE) \
503
KEYWORD_GROUP('e') \
504
KEYWORD("elif", Token::ELIF) \
505
KEYWORD("else", Token::ELSE) \
506
KEYWORD("enum", Token::ENUM) \
507
KEYWORD("extends", Token::EXTENDS) \
508
KEYWORD_GROUP('f') \
509
KEYWORD("for", Token::FOR) \
510
KEYWORD("func", Token::FUNC) \
511
KEYWORD_GROUP('i') \
512
KEYWORD("if", Token::IF) \
513
KEYWORD("in", Token::TK_IN) \
514
KEYWORD("is", Token::IS) \
515
KEYWORD_GROUP('m') \
516
KEYWORD("match", Token::MATCH) \
517
KEYWORD_GROUP('n') \
518
KEYWORD("namespace", Token::NAMESPACE) \
519
KEYWORD("not", Token::NOT) \
520
KEYWORD_GROUP('o') \
521
KEYWORD("or", Token::OR) \
522
KEYWORD_GROUP('p') \
523
KEYWORD("pass", Token::PASS) \
524
KEYWORD("preload", Token::PRELOAD) \
525
KEYWORD_GROUP('r') \
526
KEYWORD("return", Token::RETURN) \
527
KEYWORD_GROUP('s') \
528
KEYWORD("self", Token::SELF) \
529
KEYWORD("signal", Token::SIGNAL) \
530
KEYWORD("static", Token::STATIC) \
531
KEYWORD("super", Token::SUPER) \
532
KEYWORD_GROUP('t') \
533
KEYWORD("trait", Token::TRAIT) \
534
KEYWORD_GROUP('v') \
535
KEYWORD("var", Token::VAR) \
536
KEYWORD("void", Token::TK_VOID) \
537
KEYWORD_GROUP('w') \
538
KEYWORD("while", Token::WHILE) \
539
KEYWORD("when", Token::WHEN) \
540
KEYWORD_GROUP('y') \
541
KEYWORD("yield", Token::YIELD) \
542
KEYWORD_GROUP('I') \
543
KEYWORD("INF", Token::CONST_INF) \
544
KEYWORD_GROUP('N') \
545
KEYWORD("NAN", Token::CONST_NAN) \
546
KEYWORD_GROUP('P') \
547
KEYWORD("PI", Token::CONST_PI) \
548
KEYWORD_GROUP('T') \
549
KEYWORD("TAU", Token::CONST_TAU)
550
551
#define MIN_KEYWORD_LENGTH 2
552
#define MAX_KEYWORD_LENGTH 10
553
554
#ifdef DEBUG_ENABLED
555
void GDScriptTokenizerText::make_keyword_list() {
556
#define KEYWORD_LINE(keyword, token_type) keyword,
557
#define KEYWORD_GROUP_IGNORE(group)
558
keyword_list = {
559
KEYWORDS(KEYWORD_GROUP_IGNORE, KEYWORD_LINE)
560
};
561
#undef KEYWORD_LINE
562
#undef KEYWORD_GROUP_IGNORE
563
}
564
#endif // DEBUG_ENABLED
565
566
GDScriptTokenizer::Token GDScriptTokenizerText::potential_identifier() {
567
bool only_ascii = _peek(-1) < 128;
568
569
// Consume all identifier characters.
570
while (is_unicode_identifier_continue(_peek())) {
571
char32_t c = _advance();
572
only_ascii = only_ascii && c < 128;
573
}
574
575
int len = _current - _start;
576
577
if (len == 1 && _peek(-1) == '_') {
578
// Lone underscore.
579
Token token = make_token(Token::UNDERSCORE);
580
token.literal = "_";
581
return token;
582
}
583
584
String name = String::utf32(Span(_start, len));
585
if (len < MIN_KEYWORD_LENGTH || len > MAX_KEYWORD_LENGTH) {
586
// Cannot be a keyword, as the length doesn't match any.
587
return make_identifier(name);
588
}
589
590
if (!only_ascii) {
591
// Kept here in case the order with push_error matters.
592
Token id = make_identifier(name);
593
594
#ifdef DEBUG_ENABLED
595
// Additional checks for identifiers but only in debug and if it's available in TextServer.
596
if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
597
int64_t confusable = TS->is_confusable(name, keyword_list);
598
if (confusable >= 0) {
599
push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable]));
600
}
601
}
602
#endif // DEBUG_ENABLED
603
604
// Cannot be a keyword, as keywords are ASCII only.
605
return id;
606
}
607
608
// Define some helper macros for the switch case.
609
#define KEYWORD_GROUP_CASE(char) \
610
break; \
611
case char:
612
#define KEYWORD(keyword, token_type) \
613
{ \
614
const int keyword_length = sizeof(keyword) - 1; \
615
static_assert(keyword_length <= MAX_KEYWORD_LENGTH, "There's a keyword longer than the defined maximum length"); \
616
static_assert(keyword_length >= MIN_KEYWORD_LENGTH, "There's a keyword shorter than the defined minimum length"); \
617
if (keyword_length == len && name == keyword) { \
618
Token kw = make_token(token_type); \
619
kw.literal = name; \
620
return kw; \
621
} \
622
}
623
624
// Find if it's a keyword.
625
switch (_start[0]) {
626
default:
627
KEYWORDS(KEYWORD_GROUP_CASE, KEYWORD)
628
break;
629
}
630
631
// Check if it's a special literal
632
if (len == 4) {
633
if (name == "true") {
634
return make_literal(true);
635
} else if (name == "null") {
636
return make_literal(Variant());
637
}
638
} else if (len == 5) {
639
if (name == "false") {
640
return make_literal(false);
641
}
642
}
643
644
// Not a keyword, so must be an identifier.
645
return make_identifier(name);
646
647
#undef KEYWORD_GROUP_CASE
648
#undef KEYWORD
649
}
650
651
#undef MAX_KEYWORD_LENGTH
652
#undef MIN_KEYWORD_LENGTH
653
#undef KEYWORDS
654
655
void GDScriptTokenizerText::newline(bool p_make_token) {
656
// Don't overwrite previous newline, nor create if we want a line continuation.
657
if (p_make_token && !pending_newline && !line_continuation) {
658
Token newline(Token::NEWLINE);
659
newline.start_line = line;
660
newline.end_line = line;
661
newline.start_column = column - 1;
662
newline.end_column = column;
663
pending_newline = true;
664
last_token = newline;
665
last_newline = newline;
666
}
667
668
// Increment line/column counters.
669
line++;
670
column = 1;
671
}
672
673
GDScriptTokenizer::Token GDScriptTokenizerText::number() {
674
int base = 10;
675
bool has_decimal = false;
676
bool has_exponent = false;
677
bool has_error = false;
678
bool need_digits = false;
679
bool (*digit_check_func)(char32_t) = is_digit;
680
681
// Sign before hexadecimal or binary.
682
if ((_peek(-1) == '+' || _peek(-1) == '-') && _peek() == '0') {
683
_advance();
684
}
685
686
if (_peek(-1) == '.') {
687
has_decimal = true;
688
} else if (_peek(-1) == '0') {
689
if (_peek() == 'x' || _peek() == 'X') {
690
// Hexadecimal.
691
base = 16;
692
digit_check_func = is_hex_digit;
693
need_digits = true;
694
_advance();
695
} else if (_peek() == 'b' || _peek() == 'B') {
696
// Binary.
697
base = 2;
698
digit_check_func = is_binary_digit;
699
need_digits = true;
700
_advance();
701
}
702
}
703
704
if (base != 10 && is_underscore(_peek())) { // Disallow `0x_` and `0b_`.
705
Token error = make_error(vformat(R"(Unexpected underscore after "0%c".)", _peek(-1)));
706
error.start_column = column;
707
error.end_column = column + 1;
708
push_error(error);
709
has_error = true;
710
}
711
bool previous_was_underscore = false; // Allow `_` to be used in a number, for readability.
712
while (digit_check_func(_peek()) || is_underscore(_peek())) {
713
if (is_underscore(_peek())) {
714
if (previous_was_underscore) {
715
Token error = make_error(R"(Multiple underscores cannot be adjacent in a numeric literal.)");
716
error.start_column = column;
717
error.end_column = column + 1;
718
push_error(error);
719
}
720
previous_was_underscore = true;
721
} else {
722
need_digits = false;
723
previous_was_underscore = false;
724
}
725
_advance();
726
}
727
728
// It might be a ".." token (instead of decimal point) so we check if it's not.
729
if (_peek() == '.' && _peek(1) != '.') {
730
if (base == 10 && !has_decimal) {
731
has_decimal = true;
732
} else if (base == 10) {
733
Token error = make_error("Cannot use a decimal point twice in a number.");
734
error.start_column = column;
735
error.end_column = column + 1;
736
push_error(error);
737
has_error = true;
738
} else if (base == 16) {
739
Token error = make_error("Cannot use a decimal point in a hexadecimal number.");
740
error.start_column = column;
741
error.end_column = column + 1;
742
push_error(error);
743
has_error = true;
744
} else {
745
Token error = make_error("Cannot use a decimal point in a binary number.");
746
error.start_column = column;
747
error.end_column = column + 1;
748
push_error(error);
749
has_error = true;
750
}
751
if (!has_error) {
752
_advance();
753
754
// Consume decimal digits.
755
if (is_underscore(_peek())) { // Disallow `10._`, but allow `10.`.
756
Token error = make_error(R"(Unexpected underscore after decimal point.)");
757
error.start_column = column;
758
error.end_column = column + 1;
759
push_error(error);
760
has_error = true;
761
}
762
previous_was_underscore = false;
763
while (is_digit(_peek()) || is_underscore(_peek())) {
764
if (is_underscore(_peek())) {
765
if (previous_was_underscore) {
766
Token error = make_error(R"(Multiple underscores cannot be adjacent in a numeric literal.)");
767
error.start_column = column;
768
error.end_column = column + 1;
769
push_error(error);
770
}
771
previous_was_underscore = true;
772
} else {
773
previous_was_underscore = false;
774
}
775
_advance();
776
}
777
}
778
}
779
if (base == 10) {
780
if (_peek() == 'e' || _peek() == 'E') {
781
has_exponent = true;
782
_advance();
783
if (_peek() == '+' || _peek() == '-') {
784
// Exponent sign.
785
_advance();
786
}
787
// Consume exponent digits.
788
if (!is_digit(_peek())) {
789
Token error = make_error(R"(Expected exponent value after "e".)");
790
error.start_column = column;
791
error.end_column = column + 1;
792
push_error(error);
793
}
794
previous_was_underscore = false;
795
while (is_digit(_peek()) || is_underscore(_peek())) {
796
if (is_underscore(_peek())) {
797
if (previous_was_underscore) {
798
Token error = make_error(R"(Multiple underscores cannot be adjacent in a numeric literal.)");
799
error.start_column = column;
800
error.end_column = column + 1;
801
push_error(error);
802
}
803
previous_was_underscore = true;
804
} else {
805
previous_was_underscore = false;
806
}
807
_advance();
808
}
809
}
810
}
811
812
if (need_digits) {
813
// No digits in hex or bin literal.
814
Token error = make_error(vformat(R"(Expected %s digit after "0%c".)", (base == 16 ? "hexadecimal" : "binary"), (base == 16 ? 'x' : 'b')));
815
error.start_column = column;
816
error.end_column = column + 1;
817
return error;
818
}
819
820
// Detect extra decimal point.
821
if (!has_error && has_decimal && _peek() == '.' && _peek(1) != '.') {
822
Token error = make_error("Cannot use a decimal point twice in a number.");
823
error.start_column = column;
824
error.end_column = column + 1;
825
push_error(error);
826
has_error = true;
827
} else if (is_unicode_identifier_start(_peek()) || is_unicode_identifier_continue(_peek())) {
828
// Letter at the end of the number.
829
push_error("Invalid numeric notation.");
830
}
831
832
// Create a string with the whole number.
833
int len = _current - _start;
834
String number = String::utf32(Span(_start, len)).remove_char('_');
835
836
// Convert to the appropriate literal type.
837
if (base == 16) {
838
int64_t value = number.hex_to_int();
839
return make_literal(value);
840
} else if (base == 2) {
841
int64_t value = number.bin_to_int();
842
return make_literal(value);
843
} else if (has_decimal || has_exponent) {
844
double value = number.to_float();
845
return make_literal(value);
846
} else {
847
int64_t value = number.to_int();
848
return make_literal(value);
849
}
850
}
851
852
GDScriptTokenizer::Token GDScriptTokenizerText::string() {
853
enum StringType {
854
STRING_REGULAR,
855
STRING_NAME,
856
STRING_NODEPATH,
857
};
858
859
bool is_raw = false;
860
bool is_multiline = false;
861
StringType type = STRING_REGULAR;
862
863
if (_peek(-1) == 'r') {
864
is_raw = true;
865
_advance();
866
} else if (_peek(-1) == '&') {
867
type = STRING_NAME;
868
_advance();
869
} else if (_peek(-1) == '^') {
870
type = STRING_NODEPATH;
871
_advance();
872
}
873
874
char32_t quote_char = _peek(-1);
875
876
if (_peek() == quote_char && _peek(1) == quote_char) {
877
is_multiline = true;
878
// Consume all quotes.
879
_advance();
880
_advance();
881
}
882
883
String result;
884
char32_t prev = 0;
885
int prev_pos = 0;
886
887
for (;;) {
888
// Consume actual string.
889
if (_is_at_end()) {
890
return make_error("Unterminated string.");
891
}
892
893
char32_t ch = _peek();
894
895
if (ch == 0x200E || ch == 0x200F || (ch >= 0x202A && ch <= 0x202E) || (ch >= 0x2066 && ch <= 0x2069)) {
896
Token error;
897
if (is_raw) {
898
error = make_error("Invisible text direction control character present in the string, use regular string literal instead of r-string.");
899
} else {
900
error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion.");
901
}
902
error.start_column = column;
903
error.end_column = column + 1;
904
push_error(error);
905
}
906
907
if (ch == '\\') {
908
// Escape pattern.
909
_advance();
910
if (_is_at_end()) {
911
return make_error("Unterminated string.");
912
}
913
914
if (is_raw) {
915
if (_peek() == quote_char) {
916
_advance();
917
if (_is_at_end()) {
918
return make_error("Unterminated string.");
919
}
920
result += '\\';
921
result += quote_char;
922
} else if (_peek() == '\\') { // For `\\\"`.
923
_advance();
924
if (_is_at_end()) {
925
return make_error("Unterminated string.");
926
}
927
result += '\\';
928
result += '\\';
929
} else {
930
result += '\\';
931
}
932
} else {
933
// Grab escape character.
934
char32_t code = _peek();
935
_advance();
936
if (_is_at_end()) {
937
return make_error("Unterminated string.");
938
}
939
940
char32_t escaped = 0;
941
bool valid_escape = true;
942
943
switch (code) {
944
case 'a':
945
escaped = '\a';
946
break;
947
case 'b':
948
escaped = '\b';
949
break;
950
case 'f':
951
escaped = '\f';
952
break;
953
case 'n':
954
escaped = '\n';
955
break;
956
case 'r':
957
escaped = '\r';
958
break;
959
case 't':
960
escaped = '\t';
961
break;
962
case 'v':
963
escaped = '\v';
964
break;
965
case '\'':
966
escaped = '\'';
967
break;
968
case '\"':
969
escaped = '\"';
970
break;
971
case '\\':
972
escaped = '\\';
973
break;
974
case 'U':
975
case 'u': {
976
// Hexadecimal sequence.
977
int hex_len = (code == 'U') ? 6 : 4;
978
for (int j = 0; j < hex_len; j++) {
979
if (_is_at_end()) {
980
return make_error("Unterminated string.");
981
}
982
983
char32_t digit = _peek();
984
char32_t value = 0;
985
if (is_digit(digit)) {
986
value = digit - '0';
987
} else if (digit >= 'a' && digit <= 'f') {
988
value = digit - 'a';
989
value += 10;
990
} else if (digit >= 'A' && digit <= 'F') {
991
value = digit - 'A';
992
value += 10;
993
} else {
994
// Make error, but keep parsing the string.
995
Token error = make_error("Invalid hexadecimal digit in unicode escape sequence.");
996
error.start_column = column;
997
error.end_column = column + 1;
998
push_error(error);
999
valid_escape = false;
1000
break;
1001
}
1002
1003
escaped <<= 4;
1004
escaped |= value;
1005
1006
_advance();
1007
}
1008
} break;
1009
case '\r':
1010
if (_peek() != '\n') {
1011
// Carriage return without newline in string. (???)
1012
// Just add it to the string and keep going.
1013
result += ch;
1014
_advance();
1015
break;
1016
}
1017
[[fallthrough]];
1018
case '\n':
1019
// Escaping newline.
1020
newline(false);
1021
valid_escape = false; // Don't add to the string.
1022
break;
1023
default:
1024
Token error = make_error("Invalid escape in string.");
1025
error.start_column = column - 2;
1026
push_error(error);
1027
valid_escape = false;
1028
break;
1029
}
1030
// Parse UTF-16 pair.
1031
if (valid_escape) {
1032
if ((escaped & 0xfffffc00) == 0xd800) {
1033
if (prev == 0) {
1034
prev = escaped;
1035
prev_pos = column - 2;
1036
continue;
1037
} else {
1038
Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate.");
1039
error.start_column = column - 2;
1040
push_error(error);
1041
valid_escape = false;
1042
prev = 0;
1043
}
1044
} else if ((escaped & 0xfffffc00) == 0xdc00) {
1045
if (prev == 0) {
1046
Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate.");
1047
error.start_column = column - 2;
1048
push_error(error);
1049
valid_escape = false;
1050
} else {
1051
escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
1052
prev = 0;
1053
}
1054
}
1055
if (prev != 0) {
1056
Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate.");
1057
error.start_column = prev_pos;
1058
push_error(error);
1059
prev = 0;
1060
}
1061
}
1062
1063
if (valid_escape) {
1064
result += escaped;
1065
}
1066
}
1067
} else if (ch == quote_char) {
1068
if (prev != 0) {
1069
Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate");
1070
error.start_column = prev_pos;
1071
push_error(error);
1072
prev = 0;
1073
}
1074
_advance();
1075
if (is_multiline) {
1076
if (_peek() == quote_char && _peek(1) == quote_char) {
1077
// Ended the multiline string. Consume all quotes.
1078
_advance();
1079
_advance();
1080
break;
1081
} else {
1082
// Not a multiline string termination, add consumed quote.
1083
result += quote_char;
1084
}
1085
} else {
1086
// Ended single-line string.
1087
break;
1088
}
1089
} else {
1090
if (prev != 0) {
1091
Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate");
1092
error.start_column = prev_pos;
1093
push_error(error);
1094
prev = 0;
1095
}
1096
result += ch;
1097
_advance();
1098
if (ch == '\n') {
1099
newline(false);
1100
}
1101
}
1102
}
1103
if (prev != 0) {
1104
Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate");
1105
error.start_column = prev_pos;
1106
push_error(error);
1107
prev = 0;
1108
}
1109
1110
// Make the literal.
1111
Variant string;
1112
switch (type) {
1113
case STRING_NAME:
1114
string = StringName(result);
1115
break;
1116
case STRING_NODEPATH:
1117
string = NodePath(result);
1118
break;
1119
case STRING_REGULAR:
1120
string = result;
1121
break;
1122
}
1123
1124
return make_literal(string);
1125
}
1126
1127
void GDScriptTokenizerText::check_indent() {
1128
ERR_FAIL_COND_MSG(column != 1, "Checking tokenizer indentation in the middle of a line.");
1129
1130
if (_is_at_end()) {
1131
// Send dedents for every indent level.
1132
pending_indents -= indent_level();
1133
indent_stack.clear();
1134
return;
1135
}
1136
1137
for (;;) {
1138
char32_t current_indent_char = _peek();
1139
int indent_count = 0;
1140
1141
if (current_indent_char != ' ' && current_indent_char != '\t' && current_indent_char != '\r' && current_indent_char != '\n' && current_indent_char != '#') {
1142
// First character of the line is not whitespace, so we clear all indentation levels.
1143
// Unless we are in a continuation or in multiline mode (inside expression).
1144
if (line_continuation || multiline_mode) {
1145
return;
1146
}
1147
pending_indents -= indent_level();
1148
indent_stack.clear();
1149
return;
1150
}
1151
1152
if (_peek() == '\r') {
1153
_advance();
1154
if (_peek() != '\n') {
1155
push_error("Stray carriage return character in source code.");
1156
}
1157
}
1158
if (_peek() == '\n') {
1159
// Empty line, keep going.
1160
_advance();
1161
newline(false);
1162
continue;
1163
}
1164
1165
// Check indent level.
1166
bool mixed = false;
1167
while (!_is_at_end()) {
1168
char32_t space = _peek();
1169
if (space == '\t') {
1170
// Consider individual tab columns.
1171
column += tab_size - 1;
1172
indent_count += tab_size;
1173
} else if (space == ' ') {
1174
indent_count += 1;
1175
} else {
1176
break;
1177
}
1178
mixed = mixed || space != current_indent_char;
1179
_advance();
1180
}
1181
1182
if (_is_at_end()) {
1183
// Reached the end with an empty line, so just dedent as much as needed.
1184
pending_indents -= indent_level();
1185
indent_stack.clear();
1186
return;
1187
}
1188
1189
if (_peek() == '\r') {
1190
_advance();
1191
if (_peek() != '\n') {
1192
push_error("Stray carriage return character in source code.");
1193
}
1194
}
1195
if (_peek() == '\n') {
1196
// Empty line, keep going.
1197
_advance();
1198
newline(false);
1199
continue;
1200
}
1201
if (_peek() == '#') {
1202
// Comment. Advance to the next line.
1203
#ifdef TOOLS_ENABLED
1204
String comment;
1205
while (_peek() != '\n' && !_is_at_end()) {
1206
comment += _advance();
1207
}
1208
comments[line] = CommentData(comment, true);
1209
#else
1210
while (_peek() != '\n' && !_is_at_end()) {
1211
_advance();
1212
}
1213
#endif // TOOLS_ENABLED
1214
if (_is_at_end()) {
1215
// Reached the end with an empty line, so just dedent as much as needed.
1216
pending_indents -= indent_level();
1217
indent_stack.clear();
1218
return;
1219
}
1220
_advance(); // Consume '\n'.
1221
newline(false);
1222
continue;
1223
}
1224
1225
if (mixed && !line_continuation && !multiline_mode) {
1226
Token error = make_error("Mixed use of tabs and spaces for indentation.");
1227
error.start_line = line;
1228
error.start_column = 1;
1229
push_error(error);
1230
}
1231
1232
if (line_continuation || multiline_mode) {
1233
// We cleared up all the whitespace at the beginning of the line.
1234
// If this is a line continuation or we're in multiline mode then we don't want any indentation changes.
1235
return;
1236
}
1237
1238
// Check if indentation character is consistent.
1239
if (indent_char == '\0') {
1240
// First time indenting, choose character now.
1241
indent_char = current_indent_char;
1242
} else if (current_indent_char != indent_char) {
1243
Token error = make_error(vformat("Used %s character for indentation instead of %s as used before in the file.",
1244
_get_indent_char_name(current_indent_char), _get_indent_char_name(indent_char)));
1245
error.start_line = line;
1246
error.start_column = 1;
1247
push_error(error);
1248
}
1249
1250
// Now we can do actual indentation changes.
1251
1252
// Check if indent or dedent.
1253
int previous_indent = 0;
1254
if (indent_level() > 0) {
1255
previous_indent = indent_stack.back()->get();
1256
}
1257
if (indent_count == previous_indent) {
1258
// No change in indentation.
1259
return;
1260
}
1261
if (indent_count > previous_indent) {
1262
// Indentation increased.
1263
indent_stack.push_back(indent_count);
1264
pending_indents++;
1265
} else {
1266
// Indentation decreased (dedent).
1267
if (indent_level() == 0) {
1268
push_error("Tokenizer bug: trying to dedent without previous indent.");
1269
return;
1270
}
1271
while (indent_level() > 0 && indent_stack.back()->get() > indent_count) {
1272
indent_stack.pop_back();
1273
pending_indents--;
1274
}
1275
if ((indent_level() > 0 && indent_stack.back()->get() != indent_count) || (indent_level() == 0 && indent_count != 0)) {
1276
// Mismatched indentation alignment.
1277
Token error = make_error("Unindent doesn't match the previous indentation level.");
1278
error.start_line = line;
1279
error.start_column = 1;
1280
error.end_column = column + 1;
1281
push_error(error);
1282
// Still, we'll be lenient and keep going, so keep this level in the stack.
1283
indent_stack.push_back(indent_count);
1284
}
1285
}
1286
break; // Get out of the loop in any case.
1287
}
1288
}
1289
1290
String GDScriptTokenizerText::_get_indent_char_name(char32_t ch) {
1291
ERR_FAIL_COND_V(ch != ' ' && ch != '\t', String::chr(ch).c_escape());
1292
1293
return ch == ' ' ? "space" : "tab";
1294
}
1295
1296
void GDScriptTokenizerText::_skip_whitespace() {
1297
if (pending_indents != 0) {
1298
// Still have some indent/dedent tokens to give.
1299
return;
1300
}
1301
1302
bool is_bol = column == 1; // Beginning of line.
1303
1304
if (is_bol) {
1305
check_indent();
1306
return;
1307
}
1308
1309
for (;;) {
1310
char32_t c = _peek();
1311
switch (c) {
1312
case ' ':
1313
_advance();
1314
break;
1315
case '\t':
1316
_advance();
1317
// Consider individual tab columns.
1318
column += tab_size - 1;
1319
break;
1320
case '\r':
1321
_advance(); // Consume either way.
1322
if (_peek() != '\n') {
1323
push_error("Stray carriage return character in source code.");
1324
return;
1325
}
1326
break;
1327
case '\n':
1328
_advance();
1329
newline(!is_bol); // Don't create new line token if line is empty.
1330
check_indent();
1331
break;
1332
case '#': {
1333
// Comment.
1334
#ifdef TOOLS_ENABLED
1335
String comment;
1336
while (_peek() != '\n' && !_is_at_end()) {
1337
comment += _advance();
1338
}
1339
comments[line] = CommentData(comment, is_bol);
1340
#else
1341
while (_peek() != '\n' && !_is_at_end()) {
1342
_advance();
1343
}
1344
#endif // TOOLS_ENABLED
1345
if (_is_at_end()) {
1346
return;
1347
}
1348
_advance(); // Consume '\n'
1349
newline(!is_bol);
1350
check_indent();
1351
} break;
1352
default:
1353
return;
1354
}
1355
}
1356
}
1357
1358
GDScriptTokenizer::Token GDScriptTokenizerText::scan() {
1359
if (has_error()) {
1360
return pop_error();
1361
}
1362
1363
_skip_whitespace();
1364
1365
if (pending_newline) {
1366
pending_newline = false;
1367
if (!multiline_mode) {
1368
// Don't return newline tokens on multiline mode.
1369
return last_newline;
1370
}
1371
}
1372
1373
// Check for potential errors after skipping whitespace().
1374
if (has_error()) {
1375
return pop_error();
1376
}
1377
1378
_start = _current;
1379
start_line = line;
1380
start_column = column;
1381
1382
if (pending_indents != 0) {
1383
// Adjust position for indent.
1384
_start -= start_column - 1;
1385
start_column = 1;
1386
if (pending_indents > 0) {
1387
// Indents.
1388
pending_indents--;
1389
return make_token(Token::INDENT);
1390
} else {
1391
// Dedents.
1392
pending_indents++;
1393
Token dedent = make_token(Token::DEDENT);
1394
dedent.end_column += 1;
1395
return dedent;
1396
}
1397
}
1398
1399
if (_is_at_end()) {
1400
return make_token(Token::TK_EOF);
1401
}
1402
1403
const char32_t c = _advance();
1404
1405
if (c == '\\') {
1406
// Line continuation with backslash.
1407
if (_peek() == '\r') {
1408
if (_peek(1) != '\n') {
1409
return make_error("Unexpected carriage return character.");
1410
}
1411
_advance();
1412
}
1413
if (_peek() != '\n') {
1414
return make_error("Expected new line after \"\\\".");
1415
}
1416
_advance();
1417
newline(false);
1418
line_continuation = true;
1419
_skip_whitespace(); // Skip whitespace/comment lines after `\`. See GH-89403.
1420
continuation_lines.push_back(line);
1421
return scan(); // Recurse to get next token.
1422
}
1423
1424
line_continuation = false;
1425
1426
if (is_digit(c)) {
1427
return number();
1428
} else if (c == 'r' && (_peek() == '"' || _peek() == '\'')) {
1429
// Raw string literals.
1430
return string();
1431
} else if (is_unicode_identifier_start(c)) {
1432
return potential_identifier();
1433
}
1434
1435
switch (c) {
1436
// String literals.
1437
case '"':
1438
case '\'':
1439
return string();
1440
1441
// Annotation.
1442
case '@':
1443
return annotation();
1444
1445
// Single characters.
1446
case '~':
1447
return make_token(Token::TILDE);
1448
case ',':
1449
return make_token(Token::COMMA);
1450
case ':':
1451
return make_token(Token::COLON);
1452
case ';':
1453
return make_token(Token::SEMICOLON);
1454
case '$':
1455
return make_token(Token::DOLLAR);
1456
case '?':
1457
return make_token(Token::QUESTION_MARK);
1458
case '`':
1459
return make_token(Token::BACKTICK);
1460
1461
// Parens.
1462
case '(':
1463
push_paren('(');
1464
return make_token(Token::PARENTHESIS_OPEN);
1465
case '[':
1466
push_paren('[');
1467
return make_token(Token::BRACKET_OPEN);
1468
case '{':
1469
push_paren('{');
1470
return make_token(Token::BRACE_OPEN);
1471
case ')':
1472
if (!pop_paren('(')) {
1473
return make_paren_error(c);
1474
}
1475
return make_token(Token::PARENTHESIS_CLOSE);
1476
case ']':
1477
if (!pop_paren('[')) {
1478
return make_paren_error(c);
1479
}
1480
return make_token(Token::BRACKET_CLOSE);
1481
case '}':
1482
if (!pop_paren('{')) {
1483
return make_paren_error(c);
1484
}
1485
return make_token(Token::BRACE_CLOSE);
1486
1487
// Double characters.
1488
case '!':
1489
if (_peek() == '=') {
1490
_advance();
1491
return make_token(Token::BANG_EQUAL);
1492
} else {
1493
return make_token(Token::BANG);
1494
}
1495
case '.':
1496
if (_peek() == '.') {
1497
_advance();
1498
if (_peek() == '.') {
1499
_advance();
1500
return make_token(Token::PERIOD_PERIOD_PERIOD);
1501
}
1502
return make_token(Token::PERIOD_PERIOD);
1503
} else if (is_digit(_peek())) {
1504
// Number starting with '.'.
1505
return number();
1506
} else {
1507
return make_token(Token::PERIOD);
1508
}
1509
case '+':
1510
if (_peek() == '=') {
1511
_advance();
1512
return make_token(Token::PLUS_EQUAL);
1513
} else if (is_digit(_peek()) && !last_token.can_precede_bin_op()) {
1514
// Number starting with '+'.
1515
return number();
1516
} else {
1517
return make_token(Token::PLUS);
1518
}
1519
case '-':
1520
if (_peek() == '=') {
1521
_advance();
1522
return make_token(Token::MINUS_EQUAL);
1523
} else if (is_digit(_peek()) && !last_token.can_precede_bin_op()) {
1524
// Number starting with '-'.
1525
return number();
1526
} else if (_peek() == '>') {
1527
_advance();
1528
return make_token(Token::FORWARD_ARROW);
1529
} else {
1530
return make_token(Token::MINUS);
1531
}
1532
case '*':
1533
if (_peek() == '=') {
1534
_advance();
1535
return make_token(Token::STAR_EQUAL);
1536
} else if (_peek() == '*') {
1537
if (_peek(1) == '=') {
1538
_advance();
1539
_advance(); // Advance both '*' and '='
1540
return make_token(Token::STAR_STAR_EQUAL);
1541
}
1542
_advance();
1543
return make_token(Token::STAR_STAR);
1544
} else {
1545
return make_token(Token::STAR);
1546
}
1547
case '/':
1548
if (_peek() == '=') {
1549
_advance();
1550
return make_token(Token::SLASH_EQUAL);
1551
} else {
1552
return make_token(Token::SLASH);
1553
}
1554
case '%':
1555
if (_peek() == '=') {
1556
_advance();
1557
return make_token(Token::PERCENT_EQUAL);
1558
} else {
1559
return make_token(Token::PERCENT);
1560
}
1561
case '^':
1562
if (_peek() == '=') {
1563
_advance();
1564
return make_token(Token::CARET_EQUAL);
1565
} else if (_peek() == '"' || _peek() == '\'') {
1566
// Node path
1567
return string();
1568
} else {
1569
return make_token(Token::CARET);
1570
}
1571
case '&':
1572
if (_peek() == '&') {
1573
_advance();
1574
return make_token(Token::AMPERSAND_AMPERSAND);
1575
} else if (_peek() == '=') {
1576
_advance();
1577
return make_token(Token::AMPERSAND_EQUAL);
1578
} else if (_peek() == '"' || _peek() == '\'') {
1579
// String Name
1580
return string();
1581
} else {
1582
return make_token(Token::AMPERSAND);
1583
}
1584
case '|':
1585
if (_peek() == '|') {
1586
_advance();
1587
return make_token(Token::PIPE_PIPE);
1588
} else if (_peek() == '=') {
1589
_advance();
1590
return make_token(Token::PIPE_EQUAL);
1591
} else {
1592
return make_token(Token::PIPE);
1593
}
1594
1595
// Potential VCS conflict markers.
1596
case '=':
1597
if (_peek() == '=') {
1598
return check_vcs_marker('=', Token::EQUAL_EQUAL);
1599
} else {
1600
return make_token(Token::EQUAL);
1601
}
1602
case '<':
1603
if (_peek() == '=') {
1604
_advance();
1605
return make_token(Token::LESS_EQUAL);
1606
} else if (_peek() == '<') {
1607
if (_peek(1) == '=') {
1608
_advance();
1609
_advance(); // Advance both '<' and '='
1610
return make_token(Token::LESS_LESS_EQUAL);
1611
} else {
1612
return check_vcs_marker('<', Token::LESS_LESS);
1613
}
1614
} else {
1615
return make_token(Token::LESS);
1616
}
1617
case '>':
1618
if (_peek() == '=') {
1619
_advance();
1620
return make_token(Token::GREATER_EQUAL);
1621
} else if (_peek() == '>') {
1622
if (_peek(1) == '=') {
1623
_advance();
1624
_advance(); // Advance both '>' and '='
1625
return make_token(Token::GREATER_GREATER_EQUAL);
1626
} else {
1627
return check_vcs_marker('>', Token::GREATER_GREATER);
1628
}
1629
} else {
1630
return make_token(Token::GREATER);
1631
}
1632
1633
default:
1634
if (is_whitespace(c)) {
1635
return make_error(vformat(R"(Invalid white space character U+%04X.)", static_cast<int32_t>(c)));
1636
} else {
1637
return make_error(vformat(R"(Invalid character "%c" (U+%04X).)", c, static_cast<int32_t>(c)));
1638
}
1639
}
1640
}
1641
1642
GDScriptTokenizerText::GDScriptTokenizerText() {
1643
#ifdef TOOLS_ENABLED
1644
if (EditorSettings::get_singleton()) {
1645
tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
1646
}
1647
#endif // TOOLS_ENABLED
1648
#ifdef DEBUG_ENABLED
1649
make_keyword_list();
1650
#endif // DEBUG_ENABLED
1651
}
1652
1653