1 | /**************************************************************************/ |
2 | /* gdscript_parser.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_parser.h" |
32 | |
33 | #include "gdscript.h" |
34 | |
35 | #ifdef DEBUG_ENABLED |
36 | #include "gdscript_warning.h" |
37 | #endif |
38 | |
39 | #include "core/config/project_settings.h" |
40 | #include "core/io/file_access.h" |
41 | #include "core/io/resource_loader.h" |
42 | #include "core/math/math_defs.h" |
43 | #include "scene/main/multiplayer_api.h" |
44 | |
45 | #ifdef DEBUG_ENABLED |
46 | #include "core/os/os.h" |
47 | #include "core/string/string_builder.h" |
48 | #include "servers/text_server.h" |
49 | #endif |
50 | |
51 | #ifdef TOOLS_ENABLED |
52 | #include "editor/editor_settings.h" |
53 | #endif |
54 | |
55 | static HashMap<StringName, Variant::Type> builtin_types; |
56 | Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { |
57 | if (builtin_types.is_empty()) { |
58 | for (int i = 1; i < Variant::VARIANT_MAX; i++) { |
59 | builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; |
60 | } |
61 | } |
62 | |
63 | if (builtin_types.has(p_type)) { |
64 | return builtin_types[p_type]; |
65 | } |
66 | return Variant::VARIANT_MAX; |
67 | } |
68 | |
69 | #ifdef TOOLS_ENABLED |
70 | HashMap<String, String> GDScriptParser::theme_color_names; |
71 | #endif |
72 | |
73 | void GDScriptParser::cleanup() { |
74 | builtin_types.clear(); |
75 | } |
76 | |
77 | void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { |
78 | for (const KeyValue<StringName, AnnotationInfo> &E : valid_annotations) { |
79 | r_annotations->push_back(E.value.info); |
80 | } |
81 | } |
82 | |
83 | bool GDScriptParser::annotation_exists(const String &p_annotation_name) const { |
84 | return valid_annotations.has(p_annotation_name); |
85 | } |
86 | |
87 | GDScriptParser::GDScriptParser() { |
88 | // Register valid annotations. |
89 | // TODO: Should this be static? |
90 | register_annotation(MethodInfo("@tool" ), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); |
91 | register_annotation(MethodInfo("@icon" , PropertyInfo(Variant::STRING, "icon_path" )), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); |
92 | register_annotation(MethodInfo("@static_unload" ), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation); |
93 | |
94 | register_annotation(MethodInfo("@onready" ), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); |
95 | // Export annotations. |
96 | register_annotation(MethodInfo("@export" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); |
97 | register_annotation(MethodInfo("@export_enum" , PropertyInfo(Variant::STRING, "names" )), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true); |
98 | register_annotation(MethodInfo("@export_file" , PropertyInfo(Variant::STRING, "filter" )), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray("" ), true); |
99 | register_annotation(MethodInfo("@export_dir" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); |
100 | register_annotation(MethodInfo("@export_global_file" , PropertyInfo(Variant::STRING, "filter" )), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray("" ), true); |
101 | register_annotation(MethodInfo("@export_global_dir" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>); |
102 | register_annotation(MethodInfo("@export_multiline" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); |
103 | register_annotation(MethodInfo("@export_placeholder" , PropertyInfo(Variant::STRING, "placeholder" )), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); |
104 | register_annotation(MethodInfo("@export_range" , PropertyInfo(Variant::FLOAT, "min" ), PropertyInfo(Variant::FLOAT, "max" ), PropertyInfo(Variant::FLOAT, "step" ), PropertyInfo(Variant::STRING, "extra_hints" )), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, "" ), true); |
105 | register_annotation(MethodInfo("@export_exp_easing" , PropertyInfo(Variant::STRING, "hints" )), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray("" ), true); |
106 | register_annotation(MethodInfo("@export_color_no_alpha" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); |
107 | register_annotation(MethodInfo("@export_node_path" , PropertyInfo(Variant::STRING, "type" )), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray("" ), true); |
108 | register_annotation(MethodInfo("@export_flags" , PropertyInfo(Variant::STRING, "names" )), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true); |
109 | register_annotation(MethodInfo("@export_flags_2d_render" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>); |
110 | register_annotation(MethodInfo("@export_flags_2d_physics" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>); |
111 | register_annotation(MethodInfo("@export_flags_2d_navigation" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>); |
112 | register_annotation(MethodInfo("@export_flags_3d_render" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); |
113 | register_annotation(MethodInfo("@export_flags_3d_physics" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); |
114 | register_annotation(MethodInfo("@export_flags_3d_navigation" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); |
115 | register_annotation(MethodInfo("@export_flags_avoidance" ), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); |
116 | // Export grouping annotations. |
117 | register_annotation(MethodInfo("@export_category" , PropertyInfo(Variant::STRING, "name" )), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); |
118 | register_annotation(MethodInfo("@export_group" , PropertyInfo(Variant::STRING, "name" ), PropertyInfo(Variant::STRING, "prefix" )), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray("" )); |
119 | register_annotation(MethodInfo("@export_subgroup" , PropertyInfo(Variant::STRING, "name" ), PropertyInfo(Variant::STRING, "prefix" )), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray("" )); |
120 | // Warning annotations. |
121 | register_annotation(MethodInfo("@warning_ignore" , PropertyInfo(Variant::STRING, "warning" )), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); |
122 | // Networking. |
123 | register_annotation(MethodInfo("@rpc" , PropertyInfo(Variant::STRING, "mode" ), PropertyInfo(Variant::STRING, "sync" ), PropertyInfo(Variant::STRING, "transfer_mode" ), PropertyInfo(Variant::INT, "transfer_channel" )), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority" , "call_remote" , "unreliable" , 0)); |
124 | |
125 | #ifdef DEBUG_ENABLED |
126 | is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable" ); |
127 | #endif |
128 | |
129 | #ifdef TOOLS_ENABLED |
130 | if (theme_color_names.is_empty()) { |
131 | theme_color_names.insert("x" , "axis_x_color" ); |
132 | theme_color_names.insert("y" , "axis_y_color" ); |
133 | theme_color_names.insert("z" , "axis_z_color" ); |
134 | theme_color_names.insert("w" , "axis_w_color" ); |
135 | } |
136 | #endif |
137 | } |
138 | |
139 | GDScriptParser::~GDScriptParser() { |
140 | clear(); |
141 | } |
142 | |
143 | void GDScriptParser::clear() { |
144 | while (list != nullptr) { |
145 | Node *element = list; |
146 | list = list->next; |
147 | memdelete(element); |
148 | } |
149 | |
150 | head = nullptr; |
151 | list = nullptr; |
152 | _is_tool = false; |
153 | for_completion = false; |
154 | errors.clear(); |
155 | multiline_stack.clear(); |
156 | nodes_in_progress.clear(); |
157 | } |
158 | |
159 | void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { |
160 | // TODO: Improve error reporting by pointing at source code. |
161 | // TODO: Errors might point at more than one place at once (e.g. show previous declaration). |
162 | panic_mode = true; |
163 | // TODO: Improve positional information. |
164 | if (p_origin == nullptr) { |
165 | errors.push_back({ p_message, current.start_line, current.start_column }); |
166 | } else { |
167 | errors.push_back({ p_message, p_origin->start_line, p_origin->leftmost_column }); |
168 | } |
169 | } |
170 | |
171 | #ifdef DEBUG_ENABLED |
172 | void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) { |
173 | ERR_FAIL_COND(p_source == nullptr); |
174 | if (is_ignoring_warnings) { |
175 | return; |
176 | } |
177 | if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons" ).booleanize() && script_path.begins_with("res://addons/" )) { |
178 | return; |
179 | } |
180 | |
181 | if (ignored_warnings.has(p_code)) { |
182 | return; |
183 | } |
184 | |
185 | int warn_level = (int)GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(p_code)); |
186 | if (!warn_level) { |
187 | return; |
188 | } |
189 | |
190 | GDScriptWarning warning; |
191 | warning.code = p_code; |
192 | warning.symbols = p_symbols; |
193 | warning.start_line = p_source->start_line; |
194 | warning.end_line = p_source->end_line; |
195 | warning.leftmost_column = p_source->leftmost_column; |
196 | warning.rightmost_column = p_source->rightmost_column; |
197 | |
198 | if (warn_level == GDScriptWarning::WarnLevel::ERROR) { |
199 | push_error(warning.get_message() + String(" (Warning treated as error.)" ), p_source); |
200 | return; |
201 | } |
202 | |
203 | List<GDScriptWarning>::Element *before = nullptr; |
204 | for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { |
205 | if (E->get().start_line > warning.start_line) { |
206 | break; |
207 | } |
208 | before = E; |
209 | } |
210 | if (before) { |
211 | warnings.insert_after(before, warning); |
212 | } else { |
213 | warnings.push_front(warning); |
214 | } |
215 | } |
216 | #endif |
217 | |
218 | void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { |
219 | if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { |
220 | return; |
221 | } |
222 | if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) { |
223 | return; |
224 | } |
225 | CompletionContext context; |
226 | context.type = p_type; |
227 | context.current_class = current_class; |
228 | context.current_function = current_function; |
229 | context.current_suite = current_suite; |
230 | context.current_line = tokenizer.get_cursor_line(); |
231 | context.current_argument = p_argument; |
232 | context.node = p_node; |
233 | completion_context = context; |
234 | } |
235 | |
236 | void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) { |
237 | if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { |
238 | return; |
239 | } |
240 | if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) { |
241 | return; |
242 | } |
243 | CompletionContext context; |
244 | context.type = p_type; |
245 | context.current_class = current_class; |
246 | context.current_function = current_function; |
247 | context.current_suite = current_suite; |
248 | context.current_line = tokenizer.get_cursor_line(); |
249 | context.builtin_type = p_builtin_type; |
250 | completion_context = context; |
251 | } |
252 | |
253 | void GDScriptParser::push_completion_call(Node *p_call) { |
254 | if (!for_completion) { |
255 | return; |
256 | } |
257 | CompletionCall call; |
258 | call.call = p_call; |
259 | call.argument = 0; |
260 | completion_call_stack.push_back(call); |
261 | if (previous.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) { |
262 | completion_call = call; |
263 | } |
264 | } |
265 | |
266 | void GDScriptParser::pop_completion_call() { |
267 | if (!for_completion) { |
268 | return; |
269 | } |
270 | ERR_FAIL_COND_MSG(completion_call_stack.is_empty(), "Trying to pop empty completion call stack" ); |
271 | completion_call_stack.pop_back(); |
272 | } |
273 | |
274 | void GDScriptParser::set_last_completion_call_arg(int p_argument) { |
275 | if (!for_completion || passed_cursor) { |
276 | return; |
277 | } |
278 | ERR_FAIL_COND_MSG(completion_call_stack.is_empty(), "Trying to set argument on empty completion call stack" ); |
279 | completion_call_stack.back()->get().argument = p_argument; |
280 | } |
281 | |
282 | Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) { |
283 | clear(); |
284 | |
285 | String source = p_source_code; |
286 | int cursor_line = -1; |
287 | int cursor_column = -1; |
288 | for_completion = p_for_completion; |
289 | |
290 | int tab_size = 4; |
291 | #ifdef TOOLS_ENABLED |
292 | if (EditorSettings::get_singleton()) { |
293 | tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size" ); |
294 | } |
295 | #endif // TOOLS_ENABLED |
296 | |
297 | if (p_for_completion) { |
298 | // Remove cursor sentinel char. |
299 | const Vector<String> lines = p_source_code.split("\n" ); |
300 | cursor_line = 1; |
301 | cursor_column = 1; |
302 | for (int i = 0; i < lines.size(); i++) { |
303 | bool found = false; |
304 | const String &line = lines[i]; |
305 | for (int j = 0; j < line.size(); j++) { |
306 | if (line[j] == char32_t(0xFFFF)) { |
307 | found = true; |
308 | break; |
309 | } else if (line[j] == '\t') { |
310 | cursor_column += tab_size - 1; |
311 | } |
312 | cursor_column++; |
313 | } |
314 | if (found) { |
315 | break; |
316 | } |
317 | cursor_line++; |
318 | cursor_column = 1; |
319 | } |
320 | |
321 | source = source.replace_first(String::chr(0xFFFF), String()); |
322 | } |
323 | |
324 | tokenizer.set_source_code(source); |
325 | tokenizer.set_cursor_position(cursor_line, cursor_column); |
326 | script_path = p_script_path; |
327 | current = tokenizer.scan(); |
328 | // Avoid error or newline as the first token. |
329 | // The latter can mess with the parser when opening files filled exclusively with comments and newlines. |
330 | while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) { |
331 | if (current.type == GDScriptTokenizer::Token::ERROR) { |
332 | push_error(current.literal); |
333 | } |
334 | current = tokenizer.scan(); |
335 | } |
336 | |
337 | #ifdef DEBUG_ENABLED |
338 | // Warn about parsing an empty script file: |
339 | if (current.type == GDScriptTokenizer::Token::TK_EOF) { |
340 | // Create a dummy Node for the warning, pointing to the very beginning of the file |
341 | Node *nd = alloc_node<PassNode>(); |
342 | nd->start_line = 1; |
343 | nd->start_column = 0; |
344 | nd->end_line = 1; |
345 | nd->leftmost_column = 0; |
346 | nd->rightmost_column = 0; |
347 | push_warning(nd, GDScriptWarning::EMPTY_FILE); |
348 | } |
349 | #endif |
350 | |
351 | push_multiline(false); // Keep one for the whole parsing. |
352 | parse_program(); |
353 | pop_multiline(); |
354 | |
355 | #ifdef DEBUG_ENABLED |
356 | if (multiline_stack.size() > 0) { |
357 | ERR_PRINT("Parser bug: Imbalanced multiline stack." ); |
358 | } |
359 | #endif |
360 | |
361 | if (errors.is_empty()) { |
362 | return OK; |
363 | } else { |
364 | return ERR_PARSE_ERROR; |
365 | } |
366 | } |
367 | |
368 | GDScriptTokenizer::Token GDScriptParser::advance() { |
369 | lambda_ended = false; // Empty marker since we're past the end in any case. |
370 | |
371 | if (current.type == GDScriptTokenizer::Token::TK_EOF) { |
372 | ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream." ); |
373 | } |
374 | if (for_completion && !completion_call_stack.is_empty()) { |
375 | if (completion_call.call == nullptr && tokenizer.is_past_cursor()) { |
376 | completion_call = completion_call_stack.back()->get(); |
377 | passed_cursor = true; |
378 | } |
379 | } |
380 | previous = current; |
381 | current = tokenizer.scan(); |
382 | while (current.type == GDScriptTokenizer::Token::ERROR) { |
383 | push_error(current.literal); |
384 | current = tokenizer.scan(); |
385 | } |
386 | for (Node *n : nodes_in_progress) { |
387 | update_extents(n); |
388 | } |
389 | return previous; |
390 | } |
391 | |
392 | bool GDScriptParser::match(GDScriptTokenizer::Token::Type p_token_type) { |
393 | if (!check(p_token_type)) { |
394 | return false; |
395 | } |
396 | advance(); |
397 | return true; |
398 | } |
399 | |
400 | bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) const { |
401 | if (p_token_type == GDScriptTokenizer::Token::IDENTIFIER) { |
402 | return current.is_identifier(); |
403 | } |
404 | return current.type == p_token_type; |
405 | } |
406 | |
407 | bool GDScriptParser::consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message) { |
408 | if (match(p_token_type)) { |
409 | return true; |
410 | } |
411 | push_error(p_error_message); |
412 | return false; |
413 | } |
414 | |
415 | bool GDScriptParser::is_at_end() const { |
416 | return check(GDScriptTokenizer::Token::TK_EOF); |
417 | } |
418 | |
419 | void GDScriptParser::synchronize() { |
420 | panic_mode = false; |
421 | while (!is_at_end()) { |
422 | if (previous.type == GDScriptTokenizer::Token::NEWLINE || previous.type == GDScriptTokenizer::Token::SEMICOLON) { |
423 | return; |
424 | } |
425 | |
426 | switch (current.type) { |
427 | case GDScriptTokenizer::Token::CLASS: |
428 | case GDScriptTokenizer::Token::FUNC: |
429 | case GDScriptTokenizer::Token::STATIC: |
430 | case GDScriptTokenizer::Token::VAR: |
431 | case GDScriptTokenizer::Token::CONST: |
432 | case GDScriptTokenizer::Token::SIGNAL: |
433 | //case GDScriptTokenizer::Token::IF: // Can also be inside expressions. |
434 | case GDScriptTokenizer::Token::FOR: |
435 | case GDScriptTokenizer::Token::WHILE: |
436 | case GDScriptTokenizer::Token::MATCH: |
437 | case GDScriptTokenizer::Token::RETURN: |
438 | case GDScriptTokenizer::Token::ANNOTATION: |
439 | return; |
440 | default: |
441 | // Do nothing. |
442 | break; |
443 | } |
444 | |
445 | advance(); |
446 | } |
447 | } |
448 | |
449 | void GDScriptParser::push_multiline(bool p_state) { |
450 | multiline_stack.push_back(p_state); |
451 | tokenizer.set_multiline_mode(p_state); |
452 | if (p_state) { |
453 | // Consume potential whitespace tokens already waiting in line. |
454 | while (current.type == GDScriptTokenizer::Token::NEWLINE || current.type == GDScriptTokenizer::Token::INDENT || current.type == GDScriptTokenizer::Token::DEDENT) { |
455 | current = tokenizer.scan(); // Don't call advance() here, as we don't want to change the previous token. |
456 | } |
457 | } |
458 | } |
459 | |
460 | void GDScriptParser::pop_multiline() { |
461 | ERR_FAIL_COND_MSG(multiline_stack.size() == 0, "Parser bug: trying to pop from multiline stack without available value." ); |
462 | multiline_stack.pop_back(); |
463 | tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false); |
464 | } |
465 | |
466 | bool GDScriptParser::is_statement_end_token() const { |
467 | return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF); |
468 | } |
469 | |
470 | bool GDScriptParser::is_statement_end() const { |
471 | return lambda_ended || in_lambda || is_statement_end_token(); |
472 | } |
473 | |
474 | void GDScriptParser::end_statement(const String &p_context) { |
475 | bool found = false; |
476 | while (is_statement_end() && !is_at_end()) { |
477 | // Remove sequential newlines/semicolons. |
478 | if (is_statement_end_token()) { |
479 | // Only consume if this is an actual token. |
480 | advance(); |
481 | } else if (lambda_ended) { |
482 | lambda_ended = false; // Consume this "token". |
483 | found = true; |
484 | break; |
485 | } else { |
486 | if (!found) { |
487 | lambda_ended = true; // Mark the lambda as done since we found something else to end the statement. |
488 | found = true; |
489 | } |
490 | break; |
491 | } |
492 | |
493 | found = true; |
494 | } |
495 | if (!found && !is_at_end()) { |
496 | push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)" , p_context, current.get_name())); |
497 | } |
498 | } |
499 | |
500 | void GDScriptParser::parse_program() { |
501 | head = alloc_node<ClassNode>(); |
502 | head->fqcn = script_path; |
503 | current_class = head; |
504 | bool can_have_class_or_extends = true; |
505 | |
506 | while (!check(GDScriptTokenizer::Token::TK_EOF)) { |
507 | if (match(GDScriptTokenizer::Token::ANNOTATION)) { |
508 | AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); |
509 | if (annotation != nullptr) { |
510 | if (annotation->applies_to(AnnotationInfo::SCRIPT)) { |
511 | // `@icon` needs to be applied in the parser. See GH-72444. |
512 | if (annotation->name == SNAME("@icon" )) { |
513 | annotation->apply(this, head); |
514 | } else { |
515 | head->annotations.push_back(annotation); |
516 | } |
517 | } else { |
518 | annotation_stack.push_back(annotation); |
519 | // This annotation must appear after script-level annotations |
520 | // and class_name/extends (ex: could be @onready or @export), |
521 | // so we stop looking for script-level stuff. |
522 | can_have_class_or_extends = false; |
523 | break; |
524 | } |
525 | } |
526 | } else if (check(GDScriptTokenizer::Token::LITERAL) && current.literal.get_type() == Variant::STRING) { |
527 | // Allow strings in class body as multiline comments. |
528 | advance(); |
529 | if (!match(GDScriptTokenizer::Token::NEWLINE)) { |
530 | push_error("Expected newline after comment string." ); |
531 | } |
532 | } else { |
533 | break; |
534 | } |
535 | } |
536 | |
537 | while (can_have_class_or_extends) { |
538 | // Order here doesn't matter, but there should be only one of each at most. |
539 | switch (current.type) { |
540 | case GDScriptTokenizer::Token::CLASS_NAME: |
541 | advance(); |
542 | if (head->identifier != nullptr) { |
543 | push_error(R"("class_name" can only be used once.)" ); |
544 | } else { |
545 | parse_class_name(); |
546 | } |
547 | break; |
548 | case GDScriptTokenizer::Token::EXTENDS: |
549 | advance(); |
550 | if (head->extends_used) { |
551 | push_error(R"("extends" can only be used once.)" ); |
552 | } else { |
553 | parse_extends(); |
554 | end_statement("superclass" ); |
555 | } |
556 | break; |
557 | case GDScriptTokenizer::Token::LITERAL: |
558 | if (current.literal.get_type() == Variant::STRING) { |
559 | // Allow strings in class body as multiline comments. |
560 | advance(); |
561 | if (!match(GDScriptTokenizer::Token::NEWLINE)) { |
562 | push_error("Expected newline after comment string." ); |
563 | } |
564 | break; |
565 | } |
566 | [[fallthrough]]; |
567 | default: |
568 | // No tokens are allowed between script annotations and class/extends. |
569 | can_have_class_or_extends = false; |
570 | break; |
571 | } |
572 | |
573 | if (panic_mode) { |
574 | synchronize(); |
575 | } |
576 | } |
577 | |
578 | parse_class_body(true); |
579 | complete_extents(head); |
580 | |
581 | #ifdef TOOLS_ENABLED |
582 | for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) { |
583 | if (E.value.new_line && E.value.comment.begins_with("##" )) { |
584 | class_doc_line = MIN(class_doc_line, E.key); |
585 | } |
586 | } |
587 | if (has_comment(class_doc_line, true)) { |
588 | head->doc_data = parse_class_doc_comment(class_doc_line, false); |
589 | } |
590 | #endif // TOOLS_ENABLED |
591 | |
592 | if (!check(GDScriptTokenizer::Token::TK_EOF)) { |
593 | push_error("Expected end of file." ); |
594 | } |
595 | |
596 | clear_unused_annotations(); |
597 | } |
598 | |
599 | GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_name) const { |
600 | String first = p_qualified_name.get_slice("::" , 0); |
601 | |
602 | Vector<String> class_names; |
603 | GDScriptParser::ClassNode *result = nullptr; |
604 | // Empty initial name means start at the head. |
605 | if (first.is_empty() || (head->identifier && first == head->identifier->name)) { |
606 | class_names = p_qualified_name.split("::" ); |
607 | result = head; |
608 | } else if (p_qualified_name.begins_with(script_path)) { |
609 | // Script path could have a class path separator("::") in it. |
610 | class_names = p_qualified_name.trim_prefix(script_path).split("::" ); |
611 | result = head; |
612 | } else if (head->has_member(first)) { |
613 | class_names = p_qualified_name.split("::" ); |
614 | GDScriptParser::ClassNode::Member member = head->get_member(first); |
615 | if (member.type == GDScriptParser::ClassNode::Member::CLASS) { |
616 | result = member.m_class; |
617 | } |
618 | } |
619 | |
620 | // Starts at index 1 because index 0 was handled above. |
621 | for (int i = 1; result != nullptr && i < class_names.size(); i++) { |
622 | String current_name = class_names[i]; |
623 | GDScriptParser::ClassNode *next = nullptr; |
624 | if (result->has_member(current_name)) { |
625 | GDScriptParser::ClassNode::Member member = result->get_member(current_name); |
626 | if (member.type == GDScriptParser::ClassNode::Member::CLASS) { |
627 | next = member.m_class; |
628 | } |
629 | } |
630 | result = next; |
631 | } |
632 | |
633 | return result; |
634 | } |
635 | |
636 | bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const { |
637 | if (head->fqcn.is_empty() && p_class->fqcn.get_slice("::" , 0).is_empty()) { |
638 | return p_class == head; |
639 | } else if (p_class->fqcn.begins_with(head->fqcn)) { |
640 | return find_class(p_class->fqcn.trim_prefix(head->fqcn)) == p_class; |
641 | } |
642 | |
643 | return false; |
644 | } |
645 | |
646 | GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) { |
647 | ClassNode *n_class = alloc_node<ClassNode>(); |
648 | |
649 | ClassNode *previous_class = current_class; |
650 | current_class = n_class; |
651 | n_class->outer = previous_class; |
652 | |
653 | if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)" )) { |
654 | n_class->identifier = parse_identifier(); |
655 | if (n_class->outer) { |
656 | String fqcn = n_class->outer->fqcn; |
657 | if (fqcn.is_empty()) { |
658 | fqcn = script_path; |
659 | } |
660 | n_class->fqcn = fqcn + "::" + n_class->identifier->name; |
661 | } else { |
662 | n_class->fqcn = n_class->identifier->name; |
663 | } |
664 | } |
665 | |
666 | if (match(GDScriptTokenizer::Token::EXTENDS)) { |
667 | parse_extends(); |
668 | } |
669 | |
670 | consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)" ); |
671 | |
672 | bool multiline = match(GDScriptTokenizer::Token::NEWLINE); |
673 | |
674 | if (multiline && !consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)" )) { |
675 | current_class = previous_class; |
676 | complete_extents(n_class); |
677 | return n_class; |
678 | } |
679 | |
680 | if (match(GDScriptTokenizer::Token::EXTENDS)) { |
681 | if (n_class->extends_used) { |
682 | push_error(R"(Cannot use "extends" more than once in the same class.)" ); |
683 | } |
684 | parse_extends(); |
685 | end_statement("superclass" ); |
686 | } |
687 | |
688 | parse_class_body(multiline); |
689 | complete_extents(n_class); |
690 | |
691 | if (multiline) { |
692 | consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)" ); |
693 | } |
694 | |
695 | current_class = previous_class; |
696 | return n_class; |
697 | } |
698 | |
699 | void GDScriptParser::parse_class_name() { |
700 | if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)" )) { |
701 | current_class->identifier = parse_identifier(); |
702 | current_class->fqcn = String(current_class->identifier->name); |
703 | } |
704 | |
705 | if (match(GDScriptTokenizer::Token::EXTENDS)) { |
706 | // Allow extends on the same line. |
707 | parse_extends(); |
708 | end_statement("superclass" ); |
709 | } else { |
710 | end_statement("class_name statement" ); |
711 | } |
712 | } |
713 | |
714 | void GDScriptParser::parse_extends() { |
715 | current_class->extends_used = true; |
716 | |
717 | int chain_index = 0; |
718 | |
719 | if (match(GDScriptTokenizer::Token::LITERAL)) { |
720 | if (previous.literal.get_type() != Variant::STRING) { |
721 | push_error(vformat(R"(Only strings or identifiers can be used after "extends", found "%s" instead.)" , Variant::get_type_name(previous.literal.get_type()))); |
722 | } |
723 | current_class->extends_path = previous.literal; |
724 | |
725 | if (!match(GDScriptTokenizer::Token::PERIOD)) { |
726 | return; |
727 | } |
728 | } |
729 | |
730 | make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++); |
731 | |
732 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)" )) { |
733 | return; |
734 | } |
735 | current_class->extends.push_back(parse_identifier()); |
736 | |
737 | while (match(GDScriptTokenizer::Token::PERIOD)) { |
738 | make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++); |
739 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)" )) { |
740 | return; |
741 | } |
742 | current_class->extends.push_back(parse_identifier()); |
743 | } |
744 | } |
745 | |
746 | template <class T> |
747 | void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) { |
748 | advance(); |
749 | |
750 | #ifdef TOOLS_ENABLED |
751 | int = previous.start_line - 1; |
752 | #endif // TOOLS_ENABLED |
753 | |
754 | // Consume annotations. |
755 | List<AnnotationNode *> annotations; |
756 | while (!annotation_stack.is_empty()) { |
757 | AnnotationNode *last_annotation = annotation_stack.back()->get(); |
758 | if (last_annotation->applies_to(p_target)) { |
759 | annotations.push_front(last_annotation); |
760 | annotation_stack.pop_back(); |
761 | } else { |
762 | push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)" , last_annotation->name, p_member_kind)); |
763 | clear_unused_annotations(); |
764 | } |
765 | #ifdef TOOLS_ENABLED |
766 | if (last_annotation->start_line == doc_comment_line) { |
767 | doc_comment_line--; |
768 | } |
769 | #endif // TOOLS_ENABLED |
770 | } |
771 | |
772 | T *member = (this->*p_parse_function)(p_is_static); |
773 | if (member == nullptr) { |
774 | return; |
775 | } |
776 | |
777 | for (AnnotationNode *&annotation : annotations) { |
778 | member->annotations.push_back(annotation); |
779 | } |
780 | |
781 | #ifdef TOOLS_ENABLED |
782 | // Consume doc comments. |
783 | class_doc_line = MIN(class_doc_line, doc_comment_line - 1); |
784 | |
785 | // Check whether current line has a doc comment |
786 | if (has_comment(previous.start_line, true)) { |
787 | if constexpr (std::is_same_v<T, ClassNode>) { |
788 | member->doc_data = parse_class_doc_comment(previous.start_line, true, true); |
789 | } else { |
790 | member->doc_data = parse_doc_comment(previous.start_line, true); |
791 | } |
792 | } else if (has_comment(doc_comment_line, true)) { |
793 | if constexpr (std::is_same_v<T, ClassNode>) { |
794 | member->doc_data = parse_class_doc_comment(doc_comment_line, true); |
795 | } else { |
796 | member->doc_data = parse_doc_comment(doc_comment_line); |
797 | } |
798 | } |
799 | #endif // TOOLS_ENABLED |
800 | |
801 | if (member->identifier != nullptr) { |
802 | if (!((String)member->identifier->name).is_empty()) { // Enums may be unnamed. |
803 | if (current_class->members_indices.has(member->identifier->name)) { |
804 | push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)" , p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); |
805 | } else { |
806 | current_class->add_member(member); |
807 | } |
808 | } else { |
809 | current_class->add_member(member); |
810 | } |
811 | } |
812 | } |
813 | |
814 | void GDScriptParser::parse_class_body(bool p_is_multiline) { |
815 | bool class_end = false; |
816 | bool next_is_static = false; |
817 | while (!class_end && !is_at_end()) { |
818 | GDScriptTokenizer::Token token = current; |
819 | switch (token.type) { |
820 | case GDScriptTokenizer::Token::VAR: |
821 | parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable" , next_is_static); |
822 | if (next_is_static) { |
823 | current_class->has_static_data = true; |
824 | } |
825 | break; |
826 | case GDScriptTokenizer::Token::CONST: |
827 | parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant" ); |
828 | break; |
829 | case GDScriptTokenizer::Token::SIGNAL: |
830 | parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal" ); |
831 | break; |
832 | case GDScriptTokenizer::Token::FUNC: |
833 | parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function" , next_is_static); |
834 | break; |
835 | case GDScriptTokenizer::Token::CLASS: |
836 | parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class" ); |
837 | break; |
838 | case GDScriptTokenizer::Token::ENUM: |
839 | parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum" ); |
840 | break; |
841 | case GDScriptTokenizer::Token::STATIC: { |
842 | advance(); |
843 | next_is_static = true; |
844 | if (!check(GDScriptTokenizer::Token::FUNC) && !check(GDScriptTokenizer::Token::VAR)) { |
845 | push_error(R"(Expected "func" or "var" after "static".)" ); |
846 | } |
847 | } break; |
848 | case GDScriptTokenizer::Token::ANNOTATION: { |
849 | advance(); |
850 | |
851 | // Check for standalone and class-level annotations. |
852 | AnnotationNode *annotation = parse_annotation(AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); |
853 | if (annotation != nullptr) { |
854 | if (annotation->applies_to(AnnotationInfo::STANDALONE)) { |
855 | if (previous.type != GDScriptTokenizer::Token::NEWLINE) { |
856 | push_error(R"(Expected newline after a standalone annotation.)" ); |
857 | } |
858 | if (annotation->name == SNAME("@export_category" ) || annotation->name == SNAME("@export_group" ) || annotation->name == SNAME("@export_subgroup" )) { |
859 | current_class->add_member_group(annotation); |
860 | } else { |
861 | // For potential non-group standalone annotations. |
862 | push_error(R"(Unexpected standalone annotation in class body.)" ); |
863 | } |
864 | } else { |
865 | annotation_stack.push_back(annotation); |
866 | } |
867 | } |
868 | break; |
869 | } |
870 | case GDScriptTokenizer::Token::PASS: |
871 | advance(); |
872 | end_statement(R"("pass")" ); |
873 | break; |
874 | case GDScriptTokenizer::Token::DEDENT: |
875 | class_end = true; |
876 | break; |
877 | case GDScriptTokenizer::Token::LITERAL: |
878 | if (current.literal.get_type() == Variant::STRING) { |
879 | // Allow strings in class body as multiline comments. |
880 | advance(); |
881 | if (!match(GDScriptTokenizer::Token::NEWLINE)) { |
882 | push_error("Expected newline after comment string." ); |
883 | } |
884 | break; |
885 | } |
886 | [[fallthrough]]; |
887 | default: |
888 | // Display a completion with identifiers. |
889 | make_completion_context(COMPLETION_IDENTIFIER, nullptr); |
890 | push_error(vformat(R"(Unexpected "%s" in class body.)" , current.get_name())); |
891 | advance(); |
892 | break; |
893 | } |
894 | if (token.type != GDScriptTokenizer::Token::STATIC) { |
895 | next_is_static = false; |
896 | } |
897 | if (panic_mode) { |
898 | synchronize(); |
899 | } |
900 | if (!p_is_multiline) { |
901 | class_end = true; |
902 | } |
903 | } |
904 | } |
905 | |
906 | GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) { |
907 | return parse_variable(p_is_static, true); |
908 | } |
909 | |
910 | GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) { |
911 | VariableNode *variable = alloc_node<VariableNode>(); |
912 | |
913 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)" )) { |
914 | complete_extents(variable); |
915 | return nullptr; |
916 | } |
917 | |
918 | variable->identifier = parse_identifier(); |
919 | variable->export_info.name = variable->identifier->name; |
920 | variable->is_static = p_is_static; |
921 | |
922 | if (match(GDScriptTokenizer::Token::COLON)) { |
923 | if (check(GDScriptTokenizer::Token::NEWLINE)) { |
924 | if (p_allow_property) { |
925 | advance(); |
926 | return parse_property(variable, true); |
927 | } else { |
928 | push_error(R"(Expected type after ":")" ); |
929 | complete_extents(variable); |
930 | return nullptr; |
931 | } |
932 | } else if (check((GDScriptTokenizer::Token::EQUAL))) { |
933 | // Infer type. |
934 | variable->infer_datatype = true; |
935 | } else { |
936 | if (p_allow_property) { |
937 | make_completion_context(COMPLETION_PROPERTY_DECLARATION_OR_TYPE, variable); |
938 | if (check(GDScriptTokenizer::Token::IDENTIFIER)) { |
939 | // Check if get or set. |
940 | if (current.get_identifier() == "get" || current.get_identifier() == "set" ) { |
941 | return parse_property(variable, false); |
942 | } |
943 | } |
944 | } |
945 | |
946 | // Parse type. |
947 | variable->datatype_specifier = parse_type(); |
948 | } |
949 | } |
950 | |
951 | if (match(GDScriptTokenizer::Token::EQUAL)) { |
952 | // Initializer. |
953 | variable->initializer = parse_expression(false); |
954 | if (variable->initializer == nullptr) { |
955 | push_error(R"(Expected expression for variable initial value after "=".)" ); |
956 | } |
957 | variable->assignments++; |
958 | } |
959 | |
960 | if (p_allow_property && match(GDScriptTokenizer::Token::COLON)) { |
961 | if (match(GDScriptTokenizer::Token::NEWLINE)) { |
962 | return parse_property(variable, true); |
963 | } else { |
964 | return parse_property(variable, false); |
965 | } |
966 | } |
967 | |
968 | complete_extents(variable); |
969 | end_statement("variable declaration" ); |
970 | |
971 | return variable; |
972 | } |
973 | |
974 | GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_variable, bool p_need_indent) { |
975 | if (p_need_indent) { |
976 | if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block for property after ":".)" )) { |
977 | complete_extents(p_variable); |
978 | return nullptr; |
979 | } |
980 | } |
981 | |
982 | VariableNode *property = p_variable; |
983 | |
984 | make_completion_context(COMPLETION_PROPERTY_DECLARATION, property); |
985 | |
986 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)" )) { |
987 | complete_extents(p_variable); |
988 | return nullptr; |
989 | } |
990 | |
991 | IdentifierNode *function = parse_identifier(); |
992 | |
993 | if (check(GDScriptTokenizer::Token::EQUAL)) { |
994 | p_variable->property = VariableNode::PROP_SETGET; |
995 | } else { |
996 | p_variable->property = VariableNode::PROP_INLINE; |
997 | if (!p_need_indent) { |
998 | push_error("Property with inline code must go to an indented block." ); |
999 | } |
1000 | } |
1001 | |
1002 | bool getter_used = false; |
1003 | bool setter_used = false; |
1004 | |
1005 | // Run with a loop because order doesn't matter. |
1006 | for (int i = 0; i < 2; i++) { |
1007 | if (function->name == SNAME("set" )) { |
1008 | if (setter_used) { |
1009 | push_error(R"(Properties can only have one setter.)" ); |
1010 | } else { |
1011 | parse_property_setter(property); |
1012 | setter_used = true; |
1013 | } |
1014 | } else if (function->name == SNAME("get" )) { |
1015 | if (getter_used) { |
1016 | push_error(R"(Properties can only have one getter.)" ); |
1017 | } else { |
1018 | parse_property_getter(property); |
1019 | getter_used = true; |
1020 | } |
1021 | } else { |
1022 | // TODO: Update message to only have the missing one if it's the case. |
1023 | push_error(R"(Expected "get" or "set" for property declaration.)" ); |
1024 | } |
1025 | |
1026 | if (i == 0 && p_variable->property == VariableNode::PROP_SETGET) { |
1027 | if (match(GDScriptTokenizer::Token::COMMA)) { |
1028 | // Consume potential newline. |
1029 | if (match(GDScriptTokenizer::Token::NEWLINE)) { |
1030 | if (!p_need_indent) { |
1031 | push_error(R"(Inline setter/getter setting cannot span across multiple lines (use "\\"" if needed).)" ); |
1032 | } |
1033 | } |
1034 | } else { |
1035 | break; |
1036 | } |
1037 | } |
1038 | |
1039 | if (!match(GDScriptTokenizer::Token::IDENTIFIER)) { |
1040 | break; |
1041 | } |
1042 | function = parse_identifier(); |
1043 | } |
1044 | complete_extents(p_variable); |
1045 | |
1046 | if (p_variable->property == VariableNode::PROP_SETGET) { |
1047 | end_statement("property declaration" ); |
1048 | } |
1049 | |
1050 | if (p_need_indent) { |
1051 | consume(GDScriptTokenizer::Token::DEDENT, R"(Expected end of indented block for property.)" ); |
1052 | } |
1053 | return property; |
1054 | } |
1055 | |
1056 | void GDScriptParser::parse_property_setter(VariableNode *p_variable) { |
1057 | switch (p_variable->property) { |
1058 | case VariableNode::PROP_INLINE: { |
1059 | FunctionNode *function = alloc_node<FunctionNode>(); |
1060 | IdentifierNode *identifier = alloc_node<IdentifierNode>(); |
1061 | complete_extents(identifier); |
1062 | identifier->name = "@" + p_variable->identifier->name + "_setter" ; |
1063 | function->identifier = identifier; |
1064 | function->is_static = p_variable->is_static; |
1065 | |
1066 | consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)" ); |
1067 | |
1068 | ParameterNode *parameter = alloc_node<ParameterNode>(); |
1069 | if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)" )) { |
1070 | reset_extents(parameter, previous); |
1071 | p_variable->setter_parameter = parse_identifier(); |
1072 | parameter->identifier = p_variable->setter_parameter; |
1073 | function->parameters_indices[parameter->identifier->name] = 0; |
1074 | function->parameters.push_back(parameter); |
1075 | } |
1076 | complete_extents(parameter); |
1077 | |
1078 | consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*" ); |
1079 | consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*" ); |
1080 | |
1081 | FunctionNode *previous_function = current_function; |
1082 | current_function = function; |
1083 | if (p_variable->setter_parameter != nullptr) { |
1084 | SuiteNode *body = alloc_node<SuiteNode>(); |
1085 | body->add_local(parameter, function); |
1086 | function->body = parse_suite("setter declaration" , body); |
1087 | p_variable->setter = function; |
1088 | } |
1089 | current_function = previous_function; |
1090 | complete_extents(function); |
1091 | break; |
1092 | } |
1093 | case VariableNode::PROP_SETGET: |
1094 | consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")" ); |
1095 | make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); |
1096 | if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)" )) { |
1097 | p_variable->setter_pointer = parse_identifier(); |
1098 | } |
1099 | break; |
1100 | case VariableNode::PROP_NONE: |
1101 | break; // Unreachable. |
1102 | } |
1103 | } |
1104 | |
1105 | void GDScriptParser::parse_property_getter(VariableNode *p_variable) { |
1106 | switch (p_variable->property) { |
1107 | case VariableNode::PROP_INLINE: { |
1108 | FunctionNode *function = alloc_node<FunctionNode>(); |
1109 | |
1110 | consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)" ); |
1111 | |
1112 | IdentifierNode *identifier = alloc_node<IdentifierNode>(); |
1113 | complete_extents(identifier); |
1114 | identifier->name = "@" + p_variable->identifier->name + "_getter" ; |
1115 | function->identifier = identifier; |
1116 | function->is_static = p_variable->is_static; |
1117 | |
1118 | FunctionNode *previous_function = current_function; |
1119 | current_function = function; |
1120 | |
1121 | SuiteNode *body = alloc_node<SuiteNode>(); |
1122 | function->body = parse_suite("getter declaration" , body); |
1123 | p_variable->getter = function; |
1124 | |
1125 | current_function = previous_function; |
1126 | complete_extents(function); |
1127 | break; |
1128 | } |
1129 | case VariableNode::PROP_SETGET: |
1130 | consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")" ); |
1131 | make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); |
1132 | if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)" )) { |
1133 | p_variable->getter_pointer = parse_identifier(); |
1134 | } |
1135 | break; |
1136 | case VariableNode::PROP_NONE: |
1137 | break; // Unreachable. |
1138 | } |
1139 | } |
1140 | |
1141 | GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) { |
1142 | ConstantNode *constant = alloc_node<ConstantNode>(); |
1143 | |
1144 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)" )) { |
1145 | complete_extents(constant); |
1146 | return nullptr; |
1147 | } |
1148 | |
1149 | constant->identifier = parse_identifier(); |
1150 | |
1151 | if (match(GDScriptTokenizer::Token::COLON)) { |
1152 | if (check((GDScriptTokenizer::Token::EQUAL))) { |
1153 | // Infer type. |
1154 | constant->infer_datatype = true; |
1155 | } else { |
1156 | // Parse type. |
1157 | constant->datatype_specifier = parse_type(); |
1158 | } |
1159 | } |
1160 | |
1161 | if (consume(GDScriptTokenizer::Token::EQUAL, R"(Expected initializer after constant name.)" )) { |
1162 | // Initializer. |
1163 | constant->initializer = parse_expression(false); |
1164 | |
1165 | if (constant->initializer == nullptr) { |
1166 | push_error(R"(Expected initializer expression for constant.)" ); |
1167 | complete_extents(constant); |
1168 | return nullptr; |
1169 | } |
1170 | } else { |
1171 | complete_extents(constant); |
1172 | return nullptr; |
1173 | } |
1174 | |
1175 | complete_extents(constant); |
1176 | end_statement("constant declaration" ); |
1177 | |
1178 | return constant; |
1179 | } |
1180 | |
1181 | GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() { |
1182 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name.)" )) { |
1183 | return nullptr; |
1184 | } |
1185 | |
1186 | ParameterNode *parameter = alloc_node<ParameterNode>(); |
1187 | parameter->identifier = parse_identifier(); |
1188 | |
1189 | if (match(GDScriptTokenizer::Token::COLON)) { |
1190 | if (check((GDScriptTokenizer::Token::EQUAL))) { |
1191 | // Infer type. |
1192 | parameter->infer_datatype = true; |
1193 | } else { |
1194 | // Parse type. |
1195 | make_completion_context(COMPLETION_TYPE_NAME, parameter); |
1196 | parameter->datatype_specifier = parse_type(); |
1197 | } |
1198 | } |
1199 | |
1200 | if (match(GDScriptTokenizer::Token::EQUAL)) { |
1201 | // Default value. |
1202 | parameter->initializer = parse_expression(false); |
1203 | } |
1204 | |
1205 | complete_extents(parameter); |
1206 | return parameter; |
1207 | } |
1208 | |
1209 | GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) { |
1210 | SignalNode *signal = alloc_node<SignalNode>(); |
1211 | |
1212 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)" )) { |
1213 | complete_extents(signal); |
1214 | return nullptr; |
1215 | } |
1216 | |
1217 | signal->identifier = parse_identifier(); |
1218 | |
1219 | if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { |
1220 | push_multiline(true); |
1221 | advance(); |
1222 | do { |
1223 | if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { |
1224 | // Allow for trailing comma. |
1225 | break; |
1226 | } |
1227 | |
1228 | ParameterNode *parameter = parse_parameter(); |
1229 | if (parameter == nullptr) { |
1230 | push_error("Expected signal parameter name." ); |
1231 | break; |
1232 | } |
1233 | if (parameter->initializer != nullptr) { |
1234 | push_error(R"(Signal parameters cannot have a default value.)" ); |
1235 | } |
1236 | if (signal->parameters_indices.has(parameter->identifier->name)) { |
1237 | push_error(vformat(R"(Parameter with name "%s" was already declared for this signal.)" , parameter->identifier->name)); |
1238 | } else { |
1239 | signal->parameters_indices[parameter->identifier->name] = signal->parameters.size(); |
1240 | signal->parameters.push_back(parameter); |
1241 | } |
1242 | } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); |
1243 | |
1244 | pop_multiline(); |
1245 | consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*" ); |
1246 | } |
1247 | |
1248 | complete_extents(signal); |
1249 | end_statement("signal declaration" ); |
1250 | |
1251 | return signal; |
1252 | } |
1253 | |
1254 | GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { |
1255 | EnumNode *enum_node = alloc_node<EnumNode>(); |
1256 | bool named = false; |
1257 | |
1258 | if (check(GDScriptTokenizer::Token::IDENTIFIER)) { |
1259 | advance(); |
1260 | enum_node->identifier = parse_identifier(); |
1261 | named = true; |
1262 | } |
1263 | |
1264 | push_multiline(true); |
1265 | consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)" , named ? "enum name" : R"("enum")" )); |
1266 | |
1267 | HashMap<StringName, int> elements; |
1268 | |
1269 | #ifdef DEBUG_ENABLED |
1270 | List<MethodInfo> gdscript_funcs; |
1271 | GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); |
1272 | #endif |
1273 | |
1274 | do { |
1275 | if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { |
1276 | break; // Allow trailing comma. |
1277 | } |
1278 | if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)" )) { |
1279 | EnumNode::Value item; |
1280 | GDScriptParser::IdentifierNode *identifier = parse_identifier(); |
1281 | #ifdef DEBUG_ENABLED |
1282 | if (!named) { // Named enum identifiers do not shadow anything since you can only access them with NamedEnum.ENUM_VALUE |
1283 | for (MethodInfo &info : gdscript_funcs) { |
1284 | if (info.name == identifier->name) { |
1285 | push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member" , identifier->name, "built-in function" ); |
1286 | } |
1287 | } |
1288 | if (Variant::has_utility_function(identifier->name)) { |
1289 | push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member" , identifier->name, "built-in function" ); |
1290 | } else if (ClassDB::class_exists(identifier->name)) { |
1291 | push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member" , identifier->name, "global class" ); |
1292 | } |
1293 | } |
1294 | #endif |
1295 | item.identifier = identifier; |
1296 | item.parent_enum = enum_node; |
1297 | item.line = previous.start_line; |
1298 | item.leftmost_column = previous.leftmost_column; |
1299 | item.rightmost_column = previous.rightmost_column; |
1300 | |
1301 | if (elements.has(item.identifier->name)) { |
1302 | push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)" , item.identifier->name, elements[item.identifier->name]), item.identifier); |
1303 | } else if (!named) { |
1304 | if (current_class->members_indices.has(item.identifier->name)) { |
1305 | push_error(vformat(R"(Name "%s" is already used as a class %s.)" , item.identifier->name, current_class->get_member(item.identifier->name).get_type_name())); |
1306 | } |
1307 | } |
1308 | |
1309 | elements[item.identifier->name] = item.line; |
1310 | |
1311 | if (match(GDScriptTokenizer::Token::EQUAL)) { |
1312 | ExpressionNode *value = parse_expression(false); |
1313 | if (value == nullptr) { |
1314 | push_error(R"(Expected expression value after "=".)" ); |
1315 | } |
1316 | item.custom_value = value; |
1317 | } |
1318 | |
1319 | item.index = enum_node->values.size(); |
1320 | enum_node->values.push_back(item); |
1321 | if (!named) { |
1322 | // Add as member of current class. |
1323 | current_class->add_member(item); |
1324 | } |
1325 | } |
1326 | } while (match(GDScriptTokenizer::Token::COMMA)); |
1327 | |
1328 | pop_multiline(); |
1329 | consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)" ); |
1330 | |
1331 | #ifdef TOOLS_ENABLED |
1332 | // Enum values documentation. |
1333 | for (int i = 0; i < enum_node->values.size(); i++) { |
1334 | int = enum_node->values[i].line; |
1335 | bool single_line = false; |
1336 | |
1337 | if (has_comment(doc_comment_line, true)) { |
1338 | single_line = true; |
1339 | } else if (has_comment(doc_comment_line - 1, true)) { |
1340 | doc_comment_line--; |
1341 | } else { |
1342 | continue; |
1343 | } |
1344 | |
1345 | if (i == enum_node->values.size() - 1) { |
1346 | // If close bracket is same line as last value. |
1347 | if (doc_comment_line == previous.start_line) { |
1348 | break; |
1349 | } |
1350 | } else { |
1351 | // If two values are same line. |
1352 | if (doc_comment_line == enum_node->values[i + 1].line) { |
1353 | continue; |
1354 | } |
1355 | } |
1356 | |
1357 | if (named) { |
1358 | enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line); |
1359 | } else { |
1360 | current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line)); |
1361 | } |
1362 | } |
1363 | #endif // TOOLS_ENABLED |
1364 | |
1365 | complete_extents(enum_node); |
1366 | end_statement("enum" ); |
1367 | |
1368 | return enum_node; |
1369 | } |
1370 | |
1371 | void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type) { |
1372 | if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { |
1373 | bool default_used = false; |
1374 | do { |
1375 | if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { |
1376 | // Allow for trailing comma. |
1377 | break; |
1378 | } |
1379 | ParameterNode *parameter = parse_parameter(); |
1380 | if (parameter == nullptr) { |
1381 | break; |
1382 | } |
1383 | if (parameter->initializer != nullptr) { |
1384 | default_used = true; |
1385 | } else { |
1386 | if (default_used) { |
1387 | push_error("Cannot have mandatory parameters after optional parameters." ); |
1388 | continue; |
1389 | } |
1390 | } |
1391 | if (p_function->parameters_indices.has(parameter->identifier->name)) { |
1392 | push_error(vformat(R"(Parameter with name "%s" was already declared for this %s.)" , parameter->identifier->name, p_type)); |
1393 | } else { |
1394 | p_function->parameters_indices[parameter->identifier->name] = p_function->parameters.size(); |
1395 | p_function->parameters.push_back(parameter); |
1396 | p_body->add_local(parameter, current_function); |
1397 | } |
1398 | } while (match(GDScriptTokenizer::Token::COMMA)); |
1399 | } |
1400 | |
1401 | pop_multiline(); |
1402 | consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, vformat(R"*(Expected closing ")" after %s parameters.)*" , p_type)); |
1403 | |
1404 | if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) { |
1405 | make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, p_function); |
1406 | p_function->return_type = parse_type(true); |
1407 | if (p_function->return_type == nullptr) { |
1408 | push_error(R"(Expected return type or "void" after "->".)" ); |
1409 | } |
1410 | } |
1411 | |
1412 | if (!p_function->source_lambda && p_function->identifier && p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init) { |
1413 | if (!p_function->is_static) { |
1414 | push_error(R"(Static constructor must be declared static.)" ); |
1415 | } |
1416 | if (p_function->parameters.size() != 0) { |
1417 | push_error(R"(Static constructor cannot have parameters.)" ); |
1418 | } |
1419 | current_class->has_static_data = true; |
1420 | } |
1421 | |
1422 | // TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens. |
1423 | consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)" , p_type)); |
1424 | } |
1425 | |
1426 | GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) { |
1427 | FunctionNode *function = alloc_node<FunctionNode>(); |
1428 | |
1429 | make_completion_context(COMPLETION_OVERRIDE_METHOD, function); |
1430 | |
1431 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)" )) { |
1432 | complete_extents(function); |
1433 | return nullptr; |
1434 | } |
1435 | |
1436 | FunctionNode *previous_function = current_function; |
1437 | current_function = function; |
1438 | |
1439 | function->identifier = parse_identifier(); |
1440 | function->is_static = p_is_static; |
1441 | |
1442 | SuiteNode *body = alloc_node<SuiteNode>(); |
1443 | SuiteNode *previous_suite = current_suite; |
1444 | current_suite = body; |
1445 | |
1446 | push_multiline(true); |
1447 | consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)" ); |
1448 | parse_function_signature(function, body, "function" ); |
1449 | |
1450 | current_suite = previous_suite; |
1451 | function->body = parse_suite("function declaration" , body); |
1452 | |
1453 | current_function = previous_function; |
1454 | complete_extents(function); |
1455 | return function; |
1456 | } |
1457 | |
1458 | GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_valid_targets) { |
1459 | AnnotationNode *annotation = alloc_node<AnnotationNode>(); |
1460 | |
1461 | annotation->name = previous.literal; |
1462 | |
1463 | make_completion_context(COMPLETION_ANNOTATION, annotation); |
1464 | |
1465 | bool valid = true; |
1466 | |
1467 | if (!valid_annotations.has(annotation->name)) { |
1468 | push_error(vformat(R"(Unrecognized annotation: "%s".)" , annotation->name)); |
1469 | valid = false; |
1470 | } |
1471 | |
1472 | annotation->info = &valid_annotations[annotation->name]; |
1473 | |
1474 | if (!annotation->applies_to(p_valid_targets)) { |
1475 | if (annotation->applies_to(AnnotationInfo::SCRIPT)) { |
1476 | push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)" , annotation->name)); |
1477 | } else { |
1478 | push_error(vformat(R"(Annotation "%s" is not allowed in this level.)" , annotation->name)); |
1479 | } |
1480 | valid = false; |
1481 | } |
1482 | |
1483 | if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { |
1484 | push_multiline(true); |
1485 | advance(); |
1486 | // Arguments. |
1487 | push_completion_call(annotation); |
1488 | make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); |
1489 | int argument_index = 0; |
1490 | do { |
1491 | if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { |
1492 | // Allow for trailing comma. |
1493 | break; |
1494 | } |
1495 | |
1496 | make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); |
1497 | set_last_completion_call_arg(argument_index++); |
1498 | ExpressionNode *argument = parse_expression(false); |
1499 | if (argument == nullptr) { |
1500 | push_error("Expected expression as the annotation argument." ); |
1501 | valid = false; |
1502 | continue; |
1503 | } |
1504 | annotation->arguments.push_back(argument); |
1505 | } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); |
1506 | |
1507 | pop_multiline(); |
1508 | consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*" ); |
1509 | pop_completion_call(); |
1510 | } |
1511 | complete_extents(annotation); |
1512 | |
1513 | match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional. |
1514 | |
1515 | if (valid) { |
1516 | valid = validate_annotation_arguments(annotation); |
1517 | } |
1518 | |
1519 | return valid ? annotation : nullptr; |
1520 | } |
1521 | |
1522 | void GDScriptParser::clear_unused_annotations() { |
1523 | for (const AnnotationNode *annotation : annotation_stack) { |
1524 | push_error(vformat(R"(Annotation "%s" does not precede a valid target, so it will have no effect.)" , annotation->name), annotation); |
1525 | } |
1526 | |
1527 | annotation_stack.clear(); |
1528 | } |
1529 | |
1530 | bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments, bool p_is_vararg) { |
1531 | ERR_FAIL_COND_V_MSG(valid_annotations.has(p_info.name), false, vformat(R"(Annotation "%s" already registered.)" , p_info.name)); |
1532 | |
1533 | AnnotationInfo new_annotation; |
1534 | new_annotation.info = p_info; |
1535 | new_annotation.info.default_arguments = p_default_arguments; |
1536 | if (p_is_vararg) { |
1537 | new_annotation.info.flags |= METHOD_FLAG_VARARG; |
1538 | } |
1539 | new_annotation.apply = p_apply; |
1540 | new_annotation.target_kind = p_target_kinds; |
1541 | |
1542 | valid_annotations[p_info.name] = new_annotation; |
1543 | return true; |
1544 | } |
1545 | |
1546 | GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite, bool p_for_lambda) { |
1547 | SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>(); |
1548 | suite->parent_block = current_suite; |
1549 | suite->parent_function = current_function; |
1550 | current_suite = suite; |
1551 | |
1552 | if (!p_for_lambda && suite->parent_block != nullptr && suite->parent_block->is_in_loop) { |
1553 | // Do not reset to false if true is set before calling parse_suite(). |
1554 | suite->is_in_loop = true; |
1555 | } |
1556 | |
1557 | bool multiline = false; |
1558 | |
1559 | if (match(GDScriptTokenizer::Token::NEWLINE)) { |
1560 | multiline = true; |
1561 | } |
1562 | |
1563 | if (multiline) { |
1564 | if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)" , p_context))) { |
1565 | current_suite = suite->parent_block; |
1566 | complete_extents(suite); |
1567 | return suite; |
1568 | } |
1569 | } |
1570 | reset_extents(suite, current); |
1571 | |
1572 | int error_count = 0; |
1573 | |
1574 | do { |
1575 | if (is_at_end() || (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE))) { |
1576 | break; |
1577 | } |
1578 | Node *statement = parse_statement(); |
1579 | if (statement == nullptr) { |
1580 | if (error_count++ > 100) { |
1581 | push_error("Too many statement errors." , suite); |
1582 | break; |
1583 | } |
1584 | continue; |
1585 | } |
1586 | suite->statements.push_back(statement); |
1587 | |
1588 | // Register locals. |
1589 | switch (statement->type) { |
1590 | case Node::VARIABLE: { |
1591 | VariableNode *variable = static_cast<VariableNode *>(statement); |
1592 | const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name); |
1593 | if (local.type != SuiteNode::Local::UNDEFINED) { |
1594 | push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)" , local.get_name(), variable->identifier->name), variable->identifier); |
1595 | } |
1596 | current_suite->add_local(variable, current_function); |
1597 | break; |
1598 | } |
1599 | case Node::CONSTANT: { |
1600 | ConstantNode *constant = static_cast<ConstantNode *>(statement); |
1601 | const SuiteNode::Local &local = current_suite->get_local(constant->identifier->name); |
1602 | if (local.type != SuiteNode::Local::UNDEFINED) { |
1603 | String name; |
1604 | if (local.type == SuiteNode::Local::CONSTANT) { |
1605 | name = "constant" ; |
1606 | } else { |
1607 | name = "variable" ; |
1608 | } |
1609 | push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)" , name, constant->identifier->name), constant->identifier); |
1610 | } |
1611 | current_suite->add_local(constant, current_function); |
1612 | break; |
1613 | } |
1614 | default: |
1615 | break; |
1616 | } |
1617 | |
1618 | } while ((multiline || previous.type == GDScriptTokenizer::Token::SEMICOLON) && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); |
1619 | |
1620 | complete_extents(suite); |
1621 | |
1622 | if (multiline) { |
1623 | if (!lambda_ended) { |
1624 | consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)" , p_context)); |
1625 | |
1626 | } else { |
1627 | match(GDScriptTokenizer::Token::DEDENT); |
1628 | } |
1629 | } else if (previous.type == GDScriptTokenizer::Token::SEMICOLON) { |
1630 | consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after ";" at the end of %s.)" , p_context)); |
1631 | } |
1632 | |
1633 | if (p_for_lambda) { |
1634 | lambda_ended = true; |
1635 | } |
1636 | current_suite = suite->parent_block; |
1637 | return suite; |
1638 | } |
1639 | |
1640 | GDScriptParser::Node *GDScriptParser::parse_statement() { |
1641 | Node *result = nullptr; |
1642 | #ifdef DEBUG_ENABLED |
1643 | bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code; |
1644 | #endif |
1645 | |
1646 | bool is_annotation = false; |
1647 | |
1648 | switch (current.type) { |
1649 | case GDScriptTokenizer::Token::PASS: |
1650 | advance(); |
1651 | result = alloc_node<PassNode>(); |
1652 | complete_extents(result); |
1653 | end_statement(R"("pass")" ); |
1654 | break; |
1655 | case GDScriptTokenizer::Token::VAR: |
1656 | advance(); |
1657 | result = parse_variable(false, false); |
1658 | break; |
1659 | case GDScriptTokenizer::Token::CONST: |
1660 | advance(); |
1661 | result = parse_constant(false); |
1662 | break; |
1663 | case GDScriptTokenizer::Token::IF: |
1664 | advance(); |
1665 | result = parse_if(); |
1666 | break; |
1667 | case GDScriptTokenizer::Token::FOR: |
1668 | advance(); |
1669 | result = parse_for(); |
1670 | break; |
1671 | case GDScriptTokenizer::Token::WHILE: |
1672 | advance(); |
1673 | result = parse_while(); |
1674 | break; |
1675 | case GDScriptTokenizer::Token::MATCH: |
1676 | advance(); |
1677 | result = parse_match(); |
1678 | break; |
1679 | case GDScriptTokenizer::Token::BREAK: |
1680 | advance(); |
1681 | result = parse_break(); |
1682 | break; |
1683 | case GDScriptTokenizer::Token::CONTINUE: |
1684 | advance(); |
1685 | result = parse_continue(); |
1686 | break; |
1687 | case GDScriptTokenizer::Token::RETURN: { |
1688 | advance(); |
1689 | ReturnNode *n_return = alloc_node<ReturnNode>(); |
1690 | if (!is_statement_end()) { |
1691 | if (current_function && (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init || current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init)) { |
1692 | push_error(R"(Constructor cannot return a value.)" ); |
1693 | } |
1694 | n_return->return_value = parse_expression(false); |
1695 | } else if (in_lambda && !is_statement_end_token()) { |
1696 | // Try to parse it anyway as this might not be the statement end in a lambda. |
1697 | // If this fails the expression will be nullptr, but that's the same as no return, so it's fine. |
1698 | n_return->return_value = parse_expression(false); |
1699 | } |
1700 | complete_extents(n_return); |
1701 | result = n_return; |
1702 | |
1703 | current_suite->has_return = true; |
1704 | |
1705 | end_statement("return statement" ); |
1706 | break; |
1707 | } |
1708 | case GDScriptTokenizer::Token::BREAKPOINT: |
1709 | advance(); |
1710 | result = alloc_node<BreakpointNode>(); |
1711 | complete_extents(result); |
1712 | end_statement(R"("breakpoint")" ); |
1713 | break; |
1714 | case GDScriptTokenizer::Token::ASSERT: |
1715 | advance(); |
1716 | result = parse_assert(); |
1717 | break; |
1718 | case GDScriptTokenizer::Token::ANNOTATION: { |
1719 | advance(); |
1720 | is_annotation = true; |
1721 | AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT); |
1722 | if (annotation != nullptr) { |
1723 | annotation_stack.push_back(annotation); |
1724 | } |
1725 | break; |
1726 | } |
1727 | default: { |
1728 | // Expression statement. |
1729 | ExpressionNode *expression = parse_expression(true); // Allow assignment here. |
1730 | bool has_ended_lambda = false; |
1731 | if (expression == nullptr) { |
1732 | if (in_lambda) { |
1733 | // If it's not a valid expression beginning, it might be the continuation of the outer expression where this lambda is. |
1734 | lambda_ended = true; |
1735 | has_ended_lambda = true; |
1736 | } else { |
1737 | advance(); |
1738 | push_error(vformat(R"(Expected statement, found "%s" instead.)" , previous.get_name())); |
1739 | } |
1740 | } else { |
1741 | end_statement("expression" ); |
1742 | } |
1743 | lambda_ended = lambda_ended || has_ended_lambda; |
1744 | result = expression; |
1745 | |
1746 | #ifdef DEBUG_ENABLED |
1747 | if (expression != nullptr) { |
1748 | switch (expression->type) { |
1749 | case Node::CALL: |
1750 | case Node::ASSIGNMENT: |
1751 | case Node::AWAIT: |
1752 | case Node::TERNARY_OPERATOR: |
1753 | // Fine. |
1754 | break; |
1755 | case Node::LAMBDA: |
1756 | // Standalone lambdas can't be used, so make this an error. |
1757 | push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable." , expression); |
1758 | break; |
1759 | case Node::LITERAL: |
1760 | if (static_cast<GDScriptParser::LiteralNode *>(expression)->value.get_type() == Variant::STRING) { |
1761 | // Allow strings as multiline comments. |
1762 | break; |
1763 | } |
1764 | [[fallthrough]]; |
1765 | default: |
1766 | push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION); |
1767 | } |
1768 | } |
1769 | #endif |
1770 | break; |
1771 | } |
1772 | } |
1773 | |
1774 | while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) { |
1775 | AnnotationNode *last_annotation = annotation_stack.back()->get(); |
1776 | if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) { |
1777 | result->annotations.push_front(last_annotation); |
1778 | annotation_stack.pop_back(); |
1779 | } else { |
1780 | push_error(vformat(R"(Annotation "%s" cannot be applied to a statement.)" , last_annotation->name)); |
1781 | clear_unused_annotations(); |
1782 | } |
1783 | } |
1784 | |
1785 | #ifdef DEBUG_ENABLED |
1786 | if (unreachable && result != nullptr) { |
1787 | current_suite->has_unreachable_code = true; |
1788 | if (current_function) { |
1789 | push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier ? current_function->identifier->name : "<anonymous lambda>" ); |
1790 | } else { |
1791 | // TODO: Properties setters and getters with unreachable code are not being warned |
1792 | } |
1793 | } |
1794 | #endif |
1795 | |
1796 | if (panic_mode) { |
1797 | synchronize(); |
1798 | } |
1799 | |
1800 | return result; |
1801 | } |
1802 | |
1803 | GDScriptParser::AssertNode *GDScriptParser::parse_assert() { |
1804 | // TODO: Add assert message. |
1805 | AssertNode *assert = alloc_node<AssertNode>(); |
1806 | |
1807 | push_multiline(true); |
1808 | consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "assert".)" ); |
1809 | |
1810 | assert->condition = parse_expression(false); |
1811 | if (assert->condition == nullptr) { |
1812 | push_error("Expected expression to assert." ); |
1813 | pop_multiline(); |
1814 | complete_extents(assert); |
1815 | return nullptr; |
1816 | } |
1817 | |
1818 | if (match(GDScriptTokenizer::Token::COMMA) && !check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { |
1819 | assert->message = parse_expression(false); |
1820 | if (assert->message == nullptr) { |
1821 | push_error(R"(Expected error message for assert after ",".)" ); |
1822 | pop_multiline(); |
1823 | complete_extents(assert); |
1824 | return nullptr; |
1825 | } |
1826 | match(GDScriptTokenizer::Token::COMMA); |
1827 | } |
1828 | |
1829 | pop_multiline(); |
1830 | consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after assert expression.)*" ); |
1831 | |
1832 | complete_extents(assert); |
1833 | end_statement(R"("assert")" ); |
1834 | |
1835 | return assert; |
1836 | } |
1837 | |
1838 | GDScriptParser::BreakNode *GDScriptParser::parse_break() { |
1839 | if (!can_break) { |
1840 | push_error(R"(Cannot use "break" outside of a loop.)" ); |
1841 | } |
1842 | BreakNode *break_node = alloc_node<BreakNode>(); |
1843 | complete_extents(break_node); |
1844 | end_statement(R"("break")" ); |
1845 | return break_node; |
1846 | } |
1847 | |
1848 | GDScriptParser::ContinueNode *GDScriptParser::parse_continue() { |
1849 | if (!can_continue) { |
1850 | push_error(R"(Cannot use "continue" outside of a loop.)" ); |
1851 | } |
1852 | current_suite->has_continue = true; |
1853 | ContinueNode *cont = alloc_node<ContinueNode>(); |
1854 | complete_extents(cont); |
1855 | end_statement(R"("continue")" ); |
1856 | return cont; |
1857 | } |
1858 | |
1859 | GDScriptParser::ForNode *GDScriptParser::parse_for() { |
1860 | ForNode *n_for = alloc_node<ForNode>(); |
1861 | |
1862 | if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected loop variable name after "for".)" )) { |
1863 | n_for->variable = parse_identifier(); |
1864 | } |
1865 | |
1866 | if (match(GDScriptTokenizer::Token::COLON)) { |
1867 | n_for->datatype_specifier = parse_type(); |
1868 | if (n_for->datatype_specifier == nullptr) { |
1869 | push_error(R"(Expected type specifier after ":".)" ); |
1870 | } |
1871 | } |
1872 | |
1873 | if (n_for->datatype_specifier == nullptr) { |
1874 | consume(GDScriptTokenizer::Token::IN, R"(Expected "in" or ":" after "for" variable name.)" ); |
1875 | } else { |
1876 | consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable type specifier.)" ); |
1877 | } |
1878 | |
1879 | n_for->list = parse_expression(false); |
1880 | |
1881 | if (!n_for->list) { |
1882 | push_error(R"(Expected iterable after "in".)" ); |
1883 | } |
1884 | |
1885 | consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)" ); |
1886 | |
1887 | // Save break/continue state. |
1888 | bool could_break = can_break; |
1889 | bool could_continue = can_continue; |
1890 | |
1891 | // Allow break/continue. |
1892 | can_break = true; |
1893 | can_continue = true; |
1894 | |
1895 | SuiteNode *suite = alloc_node<SuiteNode>(); |
1896 | if (n_for->variable) { |
1897 | const SuiteNode::Local &local = current_suite->get_local(n_for->variable->name); |
1898 | if (local.type != SuiteNode::Local::UNDEFINED) { |
1899 | push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)" , local.get_name(), n_for->variable->name), n_for->variable); |
1900 | } |
1901 | suite->add_local(SuiteNode::Local(n_for->variable, current_function)); |
1902 | } |
1903 | suite->is_in_loop = true; |
1904 | n_for->loop = parse_suite(R"("for" block)" , suite); |
1905 | complete_extents(n_for); |
1906 | |
1907 | // Reset break/continue state. |
1908 | can_break = could_break; |
1909 | can_continue = could_continue; |
1910 | |
1911 | return n_for; |
1912 | } |
1913 | |
1914 | GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) { |
1915 | IfNode *n_if = alloc_node<IfNode>(); |
1916 | |
1917 | n_if->condition = parse_expression(false); |
1918 | if (n_if->condition == nullptr) { |
1919 | push_error(vformat(R"(Expected conditional expression after "%s".)" , p_token)); |
1920 | } |
1921 | |
1922 | consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)" , p_token)); |
1923 | |
1924 | n_if->true_block = parse_suite(vformat(R"("%s" block)" , p_token)); |
1925 | n_if->true_block->parent_if = n_if; |
1926 | |
1927 | if (n_if->true_block->has_continue) { |
1928 | current_suite->has_continue = true; |
1929 | } |
1930 | |
1931 | if (match(GDScriptTokenizer::Token::ELIF)) { |
1932 | SuiteNode *else_block = alloc_node<SuiteNode>(); |
1933 | else_block->parent_function = current_function; |
1934 | else_block->parent_block = current_suite; |
1935 | |
1936 | SuiteNode *previous_suite = current_suite; |
1937 | current_suite = else_block; |
1938 | |
1939 | IfNode *elif = parse_if("elif" ); |
1940 | else_block->statements.push_back(elif); |
1941 | complete_extents(else_block); |
1942 | n_if->false_block = else_block; |
1943 | |
1944 | current_suite = previous_suite; |
1945 | } else if (match(GDScriptTokenizer::Token::ELSE)) { |
1946 | consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "else".)" ); |
1947 | n_if->false_block = parse_suite(R"("else" block)" ); |
1948 | } |
1949 | complete_extents(n_if); |
1950 | |
1951 | if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) { |
1952 | current_suite->has_return = true; |
1953 | } |
1954 | if (n_if->false_block != nullptr && n_if->false_block->has_continue) { |
1955 | current_suite->has_continue = true; |
1956 | } |
1957 | |
1958 | return n_if; |
1959 | } |
1960 | |
1961 | GDScriptParser::MatchNode *GDScriptParser::parse_match() { |
1962 | MatchNode *match = alloc_node<MatchNode>(); |
1963 | |
1964 | match->test = parse_expression(false); |
1965 | if (match->test == nullptr) { |
1966 | push_error(R"(Expected expression to test after "match".)" ); |
1967 | } |
1968 | |
1969 | consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" expression.)" ); |
1970 | consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected a newline after "match" statement.)" ); |
1971 | |
1972 | if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected an indented block after "match" statement.)" )) { |
1973 | complete_extents(match); |
1974 | return match; |
1975 | } |
1976 | |
1977 | bool all_have_return = true; |
1978 | bool have_wildcard = false; |
1979 | |
1980 | while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { |
1981 | MatchBranchNode *branch = parse_match_branch(); |
1982 | if (branch == nullptr) { |
1983 | advance(); |
1984 | continue; |
1985 | } |
1986 | |
1987 | #ifdef DEBUG_ENABLED |
1988 | if (have_wildcard && !branch->patterns.is_empty()) { |
1989 | push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN); |
1990 | } |
1991 | #endif |
1992 | |
1993 | have_wildcard = have_wildcard || branch->has_wildcard; |
1994 | all_have_return = all_have_return && branch->block->has_return; |
1995 | match->branches.push_back(branch); |
1996 | } |
1997 | complete_extents(match); |
1998 | |
1999 | consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)" ); |
2000 | |
2001 | if (all_have_return && have_wildcard) { |
2002 | current_suite->has_return = true; |
2003 | } |
2004 | |
2005 | return match; |
2006 | } |
2007 | |
2008 | GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { |
2009 | MatchBranchNode *branch = alloc_node<MatchBranchNode>(); |
2010 | reset_extents(branch, current); |
2011 | |
2012 | bool has_bind = false; |
2013 | |
2014 | do { |
2015 | PatternNode *pattern = parse_match_pattern(); |
2016 | if (pattern == nullptr) { |
2017 | continue; |
2018 | } |
2019 | if (pattern->binds.size() > 0) { |
2020 | has_bind = true; |
2021 | } |
2022 | if (branch->patterns.size() > 0 && has_bind) { |
2023 | push_error(R"(Cannot use a variable bind with multiple patterns.)" ); |
2024 | } |
2025 | if (pattern->pattern_type == PatternNode::PT_REST) { |
2026 | push_error(R"(Rest pattern can only be used inside array and dictionary patterns.)" ); |
2027 | } else if (pattern->pattern_type == PatternNode::PT_BIND || pattern->pattern_type == PatternNode::PT_WILDCARD) { |
2028 | branch->has_wildcard = true; |
2029 | } |
2030 | branch->patterns.push_back(pattern); |
2031 | } while (match(GDScriptTokenizer::Token::COMMA)); |
2032 | |
2033 | if (branch->patterns.is_empty()) { |
2034 | push_error(R"(No pattern found for "match" branch.)" ); |
2035 | } |
2036 | |
2037 | if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)" )) { |
2038 | complete_extents(branch); |
2039 | return nullptr; |
2040 | } |
2041 | |
2042 | SuiteNode *suite = alloc_node<SuiteNode>(); |
2043 | if (branch->patterns.size() > 0) { |
2044 | for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) { |
2045 | SuiteNode::Local local(E.value, current_function); |
2046 | local.type = SuiteNode::Local::PATTERN_BIND; |
2047 | suite->add_local(local); |
2048 | } |
2049 | } |
2050 | |
2051 | branch->block = parse_suite("match pattern block" , suite); |
2052 | complete_extents(branch); |
2053 | |
2054 | return branch; |
2055 | } |
2056 | |
2057 | GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_root_pattern) { |
2058 | PatternNode *pattern = alloc_node<PatternNode>(); |
2059 | reset_extents(pattern, current); |
2060 | |
2061 | switch (current.type) { |
2062 | case GDScriptTokenizer::Token::VAR: { |
2063 | // Bind. |
2064 | advance(); |
2065 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected bind name after "var".)" )) { |
2066 | complete_extents(pattern); |
2067 | return nullptr; |
2068 | } |
2069 | pattern->pattern_type = PatternNode::PT_BIND; |
2070 | pattern->bind = parse_identifier(); |
2071 | |
2072 | PatternNode *root_pattern = p_root_pattern == nullptr ? pattern : p_root_pattern; |
2073 | |
2074 | if (p_root_pattern != nullptr) { |
2075 | if (p_root_pattern->has_bind(pattern->bind->name)) { |
2076 | push_error(vformat(R"(Bind variable name "%s" was already used in this pattern.)" , pattern->bind->name)); |
2077 | complete_extents(pattern); |
2078 | return nullptr; |
2079 | } |
2080 | } |
2081 | |
2082 | if (current_suite->has_local(pattern->bind->name)) { |
2083 | push_error(vformat(R"(There's already a %s named "%s" in this scope.)" , current_suite->get_local(pattern->bind->name).get_name(), pattern->bind->name)); |
2084 | complete_extents(pattern); |
2085 | return nullptr; |
2086 | } |
2087 | |
2088 | root_pattern->binds[pattern->bind->name] = pattern->bind; |
2089 | |
2090 | } break; |
2091 | case GDScriptTokenizer::Token::UNDERSCORE: |
2092 | // Wildcard. |
2093 | advance(); |
2094 | pattern->pattern_type = PatternNode::PT_WILDCARD; |
2095 | break; |
2096 | case GDScriptTokenizer::Token::PERIOD_PERIOD: |
2097 | // Rest. |
2098 | advance(); |
2099 | pattern->pattern_type = PatternNode::PT_REST; |
2100 | break; |
2101 | case GDScriptTokenizer::Token::BRACKET_OPEN: { |
2102 | // Array. |
2103 | advance(); |
2104 | pattern->pattern_type = PatternNode::PT_ARRAY; |
2105 | |
2106 | if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) { |
2107 | do { |
2108 | PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); |
2109 | if (sub_pattern == nullptr) { |
2110 | continue; |
2111 | } |
2112 | if (pattern->rest_used) { |
2113 | push_error(R"(The ".." pattern must be the last element in the pattern array.)" ); |
2114 | } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { |
2115 | pattern->rest_used = true; |
2116 | } |
2117 | pattern->array.push_back(sub_pattern); |
2118 | } while (match(GDScriptTokenizer::Token::COMMA)); |
2119 | } |
2120 | consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" to close the array pattern.)" ); |
2121 | break; |
2122 | } |
2123 | case GDScriptTokenizer::Token::BRACE_OPEN: { |
2124 | // Dictionary. |
2125 | advance(); |
2126 | pattern->pattern_type = PatternNode::PT_DICTIONARY; |
2127 | do { |
2128 | if (check(GDScriptTokenizer::Token::BRACE_CLOSE) || is_at_end()) { |
2129 | break; |
2130 | } |
2131 | if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { |
2132 | // Rest. |
2133 | if (pattern->rest_used) { |
2134 | push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)" ); |
2135 | } else { |
2136 | PatternNode *sub_pattern = alloc_node<PatternNode>(); |
2137 | complete_extents(sub_pattern); |
2138 | sub_pattern->pattern_type = PatternNode::PT_REST; |
2139 | pattern->dictionary.push_back({ nullptr, sub_pattern }); |
2140 | pattern->rest_used = true; |
2141 | } |
2142 | } else { |
2143 | ExpressionNode *key = parse_expression(false); |
2144 | if (key == nullptr) { |
2145 | push_error(R"(Expected expression as key for dictionary pattern.)" ); |
2146 | } |
2147 | if (match(GDScriptTokenizer::Token::COLON)) { |
2148 | // Value pattern. |
2149 | PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); |
2150 | if (sub_pattern == nullptr) { |
2151 | continue; |
2152 | } |
2153 | if (pattern->rest_used) { |
2154 | push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)" ); |
2155 | } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { |
2156 | push_error(R"(The ".." pattern cannot be used as a value.)" ); |
2157 | } else { |
2158 | pattern->dictionary.push_back({ key, sub_pattern }); |
2159 | } |
2160 | } else { |
2161 | // Key match only. |
2162 | pattern->dictionary.push_back({ key, nullptr }); |
2163 | } |
2164 | } |
2165 | } while (match(GDScriptTokenizer::Token::COMMA)); |
2166 | consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)" ); |
2167 | break; |
2168 | } |
2169 | default: { |
2170 | // Expression. |
2171 | ExpressionNode *expression = parse_expression(false); |
2172 | if (expression == nullptr) { |
2173 | push_error(R"(Expected expression for match pattern.)" ); |
2174 | complete_extents(pattern); |
2175 | return nullptr; |
2176 | } else { |
2177 | if (expression->type == GDScriptParser::Node::LITERAL) { |
2178 | pattern->pattern_type = PatternNode::PT_LITERAL; |
2179 | } else { |
2180 | pattern->pattern_type = PatternNode::PT_EXPRESSION; |
2181 | } |
2182 | pattern->expression = expression; |
2183 | } |
2184 | break; |
2185 | } |
2186 | } |
2187 | complete_extents(pattern); |
2188 | |
2189 | return pattern; |
2190 | } |
2191 | |
2192 | bool GDScriptParser::PatternNode::has_bind(const StringName &p_name) { |
2193 | return binds.has(p_name); |
2194 | } |
2195 | |
2196 | GDScriptParser::IdentifierNode *GDScriptParser::PatternNode::get_bind(const StringName &p_name) { |
2197 | return binds[p_name]; |
2198 | } |
2199 | |
2200 | GDScriptParser::WhileNode *GDScriptParser::parse_while() { |
2201 | WhileNode *n_while = alloc_node<WhileNode>(); |
2202 | |
2203 | n_while->condition = parse_expression(false); |
2204 | if (n_while->condition == nullptr) { |
2205 | push_error(R"(Expected conditional expression after "while".)" ); |
2206 | } |
2207 | |
2208 | consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "while" condition.)" ); |
2209 | |
2210 | // Save break/continue state. |
2211 | bool could_break = can_break; |
2212 | bool could_continue = can_continue; |
2213 | |
2214 | // Allow break/continue. |
2215 | can_break = true; |
2216 | can_continue = true; |
2217 | |
2218 | SuiteNode *suite = alloc_node<SuiteNode>(); |
2219 | suite->is_in_loop = true; |
2220 | n_while->loop = parse_suite(R"("while" block)" , suite); |
2221 | complete_extents(n_while); |
2222 | |
2223 | // Reset break/continue state. |
2224 | can_break = could_break; |
2225 | can_continue = could_continue; |
2226 | |
2227 | return n_while; |
2228 | } |
2229 | |
2230 | GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_precedence, bool p_can_assign, bool p_stop_on_assign) { |
2231 | // Switch multiline mode on for grouping tokens. |
2232 | // Do this early to avoid the tokenizer generating whitespace tokens. |
2233 | switch (current.type) { |
2234 | case GDScriptTokenizer::Token::PARENTHESIS_OPEN: |
2235 | case GDScriptTokenizer::Token::BRACE_OPEN: |
2236 | case GDScriptTokenizer::Token::BRACKET_OPEN: |
2237 | push_multiline(true); |
2238 | break; |
2239 | default: |
2240 | break; // Nothing to do. |
2241 | } |
2242 | |
2243 | // Completion can appear whenever an expression is expected. |
2244 | make_completion_context(COMPLETION_IDENTIFIER, nullptr); |
2245 | |
2246 | GDScriptTokenizer::Token token = current; |
2247 | GDScriptTokenizer::Token::Type token_type = token.type; |
2248 | if (token.is_identifier()) { |
2249 | // Allow keywords that can be treated as identifiers. |
2250 | token_type = GDScriptTokenizer::Token::IDENTIFIER; |
2251 | } |
2252 | ParseFunction prefix_rule = get_rule(token_type)->prefix; |
2253 | |
2254 | if (prefix_rule == nullptr) { |
2255 | // Expected expression. Let the caller give the proper error message. |
2256 | return nullptr; |
2257 | } |
2258 | |
2259 | advance(); // Only consume the token if there's a valid rule. |
2260 | |
2261 | ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); |
2262 | |
2263 | while (p_precedence <= get_rule(current.type)->precedence) { |
2264 | if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) || lambda_ended) { |
2265 | return previous_operand; |
2266 | } |
2267 | // Also switch multiline mode on here for infix operators. |
2268 | switch (current.type) { |
2269 | // case GDScriptTokenizer::Token::BRACE_OPEN: // Not an infix operator. |
2270 | case GDScriptTokenizer::Token::PARENTHESIS_OPEN: |
2271 | case GDScriptTokenizer::Token::BRACKET_OPEN: |
2272 | push_multiline(true); |
2273 | break; |
2274 | default: |
2275 | break; // Nothing to do. |
2276 | } |
2277 | token = advance(); |
2278 | ParseFunction infix_rule = get_rule(token.type)->infix; |
2279 | previous_operand = (this->*infix_rule)(previous_operand, p_can_assign); |
2280 | } |
2281 | |
2282 | return previous_operand; |
2283 | } |
2284 | |
2285 | GDScriptParser::ExpressionNode *GDScriptParser::parse_expression(bool p_can_assign, bool p_stop_on_assign) { |
2286 | return parse_precedence(PREC_ASSIGNMENT, p_can_assign, p_stop_on_assign); |
2287 | } |
2288 | |
2289 | GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() { |
2290 | IdentifierNode *identifier = static_cast<IdentifierNode *>(parse_identifier(nullptr, false)); |
2291 | #ifdef DEBUG_ENABLED |
2292 | // Check for spoofing here (if available in TextServer) since this isn't called inside expressions. This is only relevant for declarations. |
2293 | if (identifier && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier->name.operator String())) { |
2294 | push_warning(identifier, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier->name.operator String()); |
2295 | } |
2296 | #endif |
2297 | return identifier; |
2298 | } |
2299 | |
2300 | GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2301 | if (!previous.is_identifier()) { |
2302 | ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing identifier node without identifier token." ); |
2303 | } |
2304 | IdentifierNode *identifier = alloc_node<IdentifierNode>(); |
2305 | complete_extents(identifier); |
2306 | identifier->name = previous.get_identifier(); |
2307 | identifier->suite = current_suite; |
2308 | |
2309 | if (current_suite != nullptr && current_suite->has_local(identifier->name)) { |
2310 | const SuiteNode::Local &declaration = current_suite->get_local(identifier->name); |
2311 | |
2312 | identifier->source_function = declaration.source_function; |
2313 | switch (declaration.type) { |
2314 | case SuiteNode::Local::CONSTANT: |
2315 | identifier->source = IdentifierNode::LOCAL_CONSTANT; |
2316 | identifier->constant_source = declaration.constant; |
2317 | declaration.constant->usages++; |
2318 | break; |
2319 | case SuiteNode::Local::VARIABLE: |
2320 | identifier->source = IdentifierNode::LOCAL_VARIABLE; |
2321 | identifier->variable_source = declaration.variable; |
2322 | declaration.variable->usages++; |
2323 | break; |
2324 | case SuiteNode::Local::PARAMETER: |
2325 | identifier->source = IdentifierNode::FUNCTION_PARAMETER; |
2326 | identifier->parameter_source = declaration.parameter; |
2327 | declaration.parameter->usages++; |
2328 | break; |
2329 | case SuiteNode::Local::FOR_VARIABLE: |
2330 | identifier->source = IdentifierNode::LOCAL_ITERATOR; |
2331 | identifier->bind_source = declaration.bind; |
2332 | declaration.bind->usages++; |
2333 | break; |
2334 | case SuiteNode::Local::PATTERN_BIND: |
2335 | identifier->source = IdentifierNode::LOCAL_BIND; |
2336 | identifier->bind_source = declaration.bind; |
2337 | declaration.bind->usages++; |
2338 | break; |
2339 | case SuiteNode::Local::UNDEFINED: |
2340 | ERR_FAIL_V_MSG(nullptr, "Undefined local found." ); |
2341 | } |
2342 | } |
2343 | |
2344 | return identifier; |
2345 | } |
2346 | |
2347 | GDScriptParser::LiteralNode *GDScriptParser::parse_literal() { |
2348 | return static_cast<LiteralNode *>(parse_literal(nullptr, false)); |
2349 | } |
2350 | |
2351 | GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2352 | if (previous.type != GDScriptTokenizer::Token::LITERAL) { |
2353 | push_error("Parser bug: parsing literal node without literal token." ); |
2354 | ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token." ); |
2355 | } |
2356 | |
2357 | LiteralNode *literal = alloc_node<LiteralNode>(); |
2358 | complete_extents(literal); |
2359 | literal->value = previous.literal; |
2360 | return literal; |
2361 | } |
2362 | |
2363 | GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2364 | if (current_function && current_function->is_static) { |
2365 | push_error(R"(Cannot use "self" inside a static function.)" ); |
2366 | } |
2367 | SelfNode *self = alloc_node<SelfNode>(); |
2368 | complete_extents(self); |
2369 | self->current_class = current_class; |
2370 | return self; |
2371 | } |
2372 | |
2373 | GDScriptParser::ExpressionNode *GDScriptParser::parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2374 | GDScriptTokenizer::Token::Type op_type = previous.type; |
2375 | LiteralNode *constant = alloc_node<LiteralNode>(); |
2376 | complete_extents(constant); |
2377 | |
2378 | switch (op_type) { |
2379 | case GDScriptTokenizer::Token::CONST_PI: |
2380 | constant->value = Math_PI; |
2381 | break; |
2382 | case GDScriptTokenizer::Token::CONST_TAU: |
2383 | constant->value = Math_TAU; |
2384 | break; |
2385 | case GDScriptTokenizer::Token::CONST_INF: |
2386 | constant->value = INFINITY; |
2387 | break; |
2388 | case GDScriptTokenizer::Token::CONST_NAN: |
2389 | constant->value = NAN; |
2390 | break; |
2391 | default: |
2392 | return nullptr; // Unreachable. |
2393 | } |
2394 | |
2395 | return constant; |
2396 | } |
2397 | |
2398 | GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2399 | GDScriptTokenizer::Token::Type op_type = previous.type; |
2400 | UnaryOpNode *operation = alloc_node<UnaryOpNode>(); |
2401 | |
2402 | switch (op_type) { |
2403 | case GDScriptTokenizer::Token::MINUS: |
2404 | operation->operation = UnaryOpNode::OP_NEGATIVE; |
2405 | operation->variant_op = Variant::OP_NEGATE; |
2406 | operation->operand = parse_precedence(PREC_SIGN, false); |
2407 | if (operation->operand == nullptr) { |
2408 | push_error(R"(Expected expression after "-" operator.)" ); |
2409 | } |
2410 | break; |
2411 | case GDScriptTokenizer::Token::PLUS: |
2412 | operation->operation = UnaryOpNode::OP_POSITIVE; |
2413 | operation->variant_op = Variant::OP_POSITIVE; |
2414 | operation->operand = parse_precedence(PREC_SIGN, false); |
2415 | if (operation->operand == nullptr) { |
2416 | push_error(R"(Expected expression after "+" operator.)" ); |
2417 | } |
2418 | break; |
2419 | case GDScriptTokenizer::Token::TILDE: |
2420 | operation->operation = UnaryOpNode::OP_COMPLEMENT; |
2421 | operation->variant_op = Variant::OP_BIT_NEGATE; |
2422 | operation->operand = parse_precedence(PREC_BIT_NOT, false); |
2423 | if (operation->operand == nullptr) { |
2424 | push_error(R"(Expected expression after "~" operator.)" ); |
2425 | } |
2426 | break; |
2427 | case GDScriptTokenizer::Token::NOT: |
2428 | case GDScriptTokenizer::Token::BANG: |
2429 | operation->operation = UnaryOpNode::OP_LOGIC_NOT; |
2430 | operation->variant_op = Variant::OP_NOT; |
2431 | operation->operand = parse_precedence(PREC_LOGIC_NOT, false); |
2432 | if (operation->operand == nullptr) { |
2433 | push_error(vformat(R"(Expected expression after "%s" operator.)" , op_type == GDScriptTokenizer::Token::NOT ? "not" : "!" )); |
2434 | } |
2435 | break; |
2436 | default: |
2437 | complete_extents(operation); |
2438 | return nullptr; // Unreachable. |
2439 | } |
2440 | complete_extents(operation); |
2441 | |
2442 | return operation; |
2443 | } |
2444 | |
2445 | GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_not_in_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2446 | // check that NOT is followed by IN by consuming it before calling parse_binary_operator which will only receive a plain IN |
2447 | UnaryOpNode *operation = alloc_node<UnaryOpNode>(); |
2448 | reset_extents(operation, p_previous_operand); |
2449 | update_extents(operation); |
2450 | consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "not" in content-test operator.)" ); |
2451 | ExpressionNode *in_operation = parse_binary_operator(p_previous_operand, p_can_assign); |
2452 | operation->operation = UnaryOpNode::OP_LOGIC_NOT; |
2453 | operation->variant_op = Variant::OP_NOT; |
2454 | operation->operand = in_operation; |
2455 | complete_extents(operation); |
2456 | return operation; |
2457 | } |
2458 | |
2459 | GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2460 | GDScriptTokenizer::Token op = previous; |
2461 | BinaryOpNode *operation = alloc_node<BinaryOpNode>(); |
2462 | reset_extents(operation, p_previous_operand); |
2463 | update_extents(operation); |
2464 | |
2465 | Precedence precedence = (Precedence)(get_rule(op.type)->precedence + 1); |
2466 | operation->left_operand = p_previous_operand; |
2467 | operation->right_operand = parse_precedence(precedence, false); |
2468 | complete_extents(operation); |
2469 | |
2470 | if (operation->right_operand == nullptr) { |
2471 | push_error(vformat(R"(Expected expression after "%s" operator.)" , op.get_name())); |
2472 | } |
2473 | |
2474 | // TODO: Also for unary, ternary, and assignment. |
2475 | switch (op.type) { |
2476 | case GDScriptTokenizer::Token::PLUS: |
2477 | operation->operation = BinaryOpNode::OP_ADDITION; |
2478 | operation->variant_op = Variant::OP_ADD; |
2479 | break; |
2480 | case GDScriptTokenizer::Token::MINUS: |
2481 | operation->operation = BinaryOpNode::OP_SUBTRACTION; |
2482 | operation->variant_op = Variant::OP_SUBTRACT; |
2483 | break; |
2484 | case GDScriptTokenizer::Token::STAR: |
2485 | operation->operation = BinaryOpNode::OP_MULTIPLICATION; |
2486 | operation->variant_op = Variant::OP_MULTIPLY; |
2487 | break; |
2488 | case GDScriptTokenizer::Token::SLASH: |
2489 | operation->operation = BinaryOpNode::OP_DIVISION; |
2490 | operation->variant_op = Variant::OP_DIVIDE; |
2491 | break; |
2492 | case GDScriptTokenizer::Token::PERCENT: |
2493 | operation->operation = BinaryOpNode::OP_MODULO; |
2494 | operation->variant_op = Variant::OP_MODULE; |
2495 | break; |
2496 | case GDScriptTokenizer::Token::STAR_STAR: |
2497 | operation->operation = BinaryOpNode::OP_POWER; |
2498 | operation->variant_op = Variant::OP_POWER; |
2499 | break; |
2500 | case GDScriptTokenizer::Token::LESS_LESS: |
2501 | operation->operation = BinaryOpNode::OP_BIT_LEFT_SHIFT; |
2502 | operation->variant_op = Variant::OP_SHIFT_LEFT; |
2503 | break; |
2504 | case GDScriptTokenizer::Token::GREATER_GREATER: |
2505 | operation->operation = BinaryOpNode::OP_BIT_RIGHT_SHIFT; |
2506 | operation->variant_op = Variant::OP_SHIFT_RIGHT; |
2507 | break; |
2508 | case GDScriptTokenizer::Token::AMPERSAND: |
2509 | operation->operation = BinaryOpNode::OP_BIT_AND; |
2510 | operation->variant_op = Variant::OP_BIT_AND; |
2511 | break; |
2512 | case GDScriptTokenizer::Token::PIPE: |
2513 | operation->operation = BinaryOpNode::OP_BIT_OR; |
2514 | operation->variant_op = Variant::OP_BIT_OR; |
2515 | break; |
2516 | case GDScriptTokenizer::Token::CARET: |
2517 | operation->operation = BinaryOpNode::OP_BIT_XOR; |
2518 | operation->variant_op = Variant::OP_BIT_XOR; |
2519 | break; |
2520 | case GDScriptTokenizer::Token::AND: |
2521 | case GDScriptTokenizer::Token::AMPERSAND_AMPERSAND: |
2522 | operation->operation = BinaryOpNode::OP_LOGIC_AND; |
2523 | operation->variant_op = Variant::OP_AND; |
2524 | break; |
2525 | case GDScriptTokenizer::Token::OR: |
2526 | case GDScriptTokenizer::Token::PIPE_PIPE: |
2527 | operation->operation = BinaryOpNode::OP_LOGIC_OR; |
2528 | operation->variant_op = Variant::OP_OR; |
2529 | break; |
2530 | case GDScriptTokenizer::Token::IN: |
2531 | operation->operation = BinaryOpNode::OP_CONTENT_TEST; |
2532 | operation->variant_op = Variant::OP_IN; |
2533 | break; |
2534 | case GDScriptTokenizer::Token::EQUAL_EQUAL: |
2535 | operation->operation = BinaryOpNode::OP_COMP_EQUAL; |
2536 | operation->variant_op = Variant::OP_EQUAL; |
2537 | break; |
2538 | case GDScriptTokenizer::Token::BANG_EQUAL: |
2539 | operation->operation = BinaryOpNode::OP_COMP_NOT_EQUAL; |
2540 | operation->variant_op = Variant::OP_NOT_EQUAL; |
2541 | break; |
2542 | case GDScriptTokenizer::Token::LESS: |
2543 | operation->operation = BinaryOpNode::OP_COMP_LESS; |
2544 | operation->variant_op = Variant::OP_LESS; |
2545 | break; |
2546 | case GDScriptTokenizer::Token::LESS_EQUAL: |
2547 | operation->operation = BinaryOpNode::OP_COMP_LESS_EQUAL; |
2548 | operation->variant_op = Variant::OP_LESS_EQUAL; |
2549 | break; |
2550 | case GDScriptTokenizer::Token::GREATER: |
2551 | operation->operation = BinaryOpNode::OP_COMP_GREATER; |
2552 | operation->variant_op = Variant::OP_GREATER; |
2553 | break; |
2554 | case GDScriptTokenizer::Token::GREATER_EQUAL: |
2555 | operation->operation = BinaryOpNode::OP_COMP_GREATER_EQUAL; |
2556 | operation->variant_op = Variant::OP_GREATER_EQUAL; |
2557 | break; |
2558 | default: |
2559 | return nullptr; // Unreachable. |
2560 | } |
2561 | |
2562 | return operation; |
2563 | } |
2564 | |
2565 | GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2566 | // Only one ternary operation exists, so no abstraction here. |
2567 | TernaryOpNode *operation = alloc_node<TernaryOpNode>(); |
2568 | reset_extents(operation, p_previous_operand); |
2569 | update_extents(operation); |
2570 | |
2571 | operation->true_expr = p_previous_operand; |
2572 | operation->condition = parse_precedence(PREC_TERNARY, false); |
2573 | |
2574 | if (operation->condition == nullptr) { |
2575 | push_error(R"(Expected expression as ternary condition after "if".)" ); |
2576 | } |
2577 | |
2578 | consume(GDScriptTokenizer::Token::ELSE, R"(Expected "else" after ternary operator condition.)" ); |
2579 | |
2580 | operation->false_expr = parse_precedence(PREC_TERNARY, false); |
2581 | |
2582 | if (operation->false_expr == nullptr) { |
2583 | push_error(R"(Expected expression after "else".)" ); |
2584 | } |
2585 | |
2586 | complete_extents(operation); |
2587 | return operation; |
2588 | } |
2589 | |
2590 | GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2591 | if (!p_can_assign) { |
2592 | push_error("Assignment is not allowed inside an expression." ); |
2593 | return parse_expression(false); // Return the following expression. |
2594 | } |
2595 | if (p_previous_operand == nullptr) { |
2596 | return parse_expression(false); // Return the following expression. |
2597 | } |
2598 | |
2599 | #ifdef DEBUG_ENABLED |
2600 | VariableNode *source_variable = nullptr; |
2601 | #endif |
2602 | |
2603 | switch (p_previous_operand->type) { |
2604 | case Node::IDENTIFIER: { |
2605 | #ifdef DEBUG_ENABLED |
2606 | // Get source to store assignment count. |
2607 | // Also remove one usage since assignment isn't usage. |
2608 | IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand); |
2609 | switch (id->source) { |
2610 | case IdentifierNode::LOCAL_VARIABLE: |
2611 | |
2612 | source_variable = id->variable_source; |
2613 | id->variable_source->usages--; |
2614 | break; |
2615 | case IdentifierNode::LOCAL_CONSTANT: |
2616 | id->constant_source->usages--; |
2617 | break; |
2618 | case IdentifierNode::FUNCTION_PARAMETER: |
2619 | id->parameter_source->usages--; |
2620 | break; |
2621 | case IdentifierNode::LOCAL_ITERATOR: |
2622 | case IdentifierNode::LOCAL_BIND: |
2623 | id->bind_source->usages--; |
2624 | break; |
2625 | default: |
2626 | break; |
2627 | } |
2628 | #endif |
2629 | } break; |
2630 | case Node::SUBSCRIPT: |
2631 | // Okay. |
2632 | break; |
2633 | default: |
2634 | push_error(R"(Only identifier, attribute access, and subscription access can be used as assignment target.)" ); |
2635 | return parse_expression(false); // Return the following expression. |
2636 | } |
2637 | |
2638 | AssignmentNode *assignment = alloc_node<AssignmentNode>(); |
2639 | reset_extents(assignment, p_previous_operand); |
2640 | update_extents(assignment); |
2641 | |
2642 | make_completion_context(COMPLETION_ASSIGN, assignment); |
2643 | #ifdef DEBUG_ENABLED |
2644 | bool has_operator = true; |
2645 | #endif |
2646 | switch (previous.type) { |
2647 | case GDScriptTokenizer::Token::EQUAL: |
2648 | assignment->operation = AssignmentNode::OP_NONE; |
2649 | assignment->variant_op = Variant::OP_MAX; |
2650 | #ifdef DEBUG_ENABLED |
2651 | has_operator = false; |
2652 | #endif |
2653 | break; |
2654 | case GDScriptTokenizer::Token::PLUS_EQUAL: |
2655 | assignment->operation = AssignmentNode::OP_ADDITION; |
2656 | assignment->variant_op = Variant::OP_ADD; |
2657 | break; |
2658 | case GDScriptTokenizer::Token::MINUS_EQUAL: |
2659 | assignment->operation = AssignmentNode::OP_SUBTRACTION; |
2660 | assignment->variant_op = Variant::OP_SUBTRACT; |
2661 | break; |
2662 | case GDScriptTokenizer::Token::STAR_EQUAL: |
2663 | assignment->operation = AssignmentNode::OP_MULTIPLICATION; |
2664 | assignment->variant_op = Variant::OP_MULTIPLY; |
2665 | break; |
2666 | case GDScriptTokenizer::Token::STAR_STAR_EQUAL: |
2667 | assignment->operation = AssignmentNode::OP_POWER; |
2668 | assignment->variant_op = Variant::OP_POWER; |
2669 | break; |
2670 | case GDScriptTokenizer::Token::SLASH_EQUAL: |
2671 | assignment->operation = AssignmentNode::OP_DIVISION; |
2672 | assignment->variant_op = Variant::OP_DIVIDE; |
2673 | break; |
2674 | case GDScriptTokenizer::Token::PERCENT_EQUAL: |
2675 | assignment->operation = AssignmentNode::OP_MODULO; |
2676 | assignment->variant_op = Variant::OP_MODULE; |
2677 | break; |
2678 | case GDScriptTokenizer::Token::LESS_LESS_EQUAL: |
2679 | assignment->operation = AssignmentNode::OP_BIT_SHIFT_LEFT; |
2680 | assignment->variant_op = Variant::OP_SHIFT_LEFT; |
2681 | break; |
2682 | case GDScriptTokenizer::Token::GREATER_GREATER_EQUAL: |
2683 | assignment->operation = AssignmentNode::OP_BIT_SHIFT_RIGHT; |
2684 | assignment->variant_op = Variant::OP_SHIFT_RIGHT; |
2685 | break; |
2686 | case GDScriptTokenizer::Token::AMPERSAND_EQUAL: |
2687 | assignment->operation = AssignmentNode::OP_BIT_AND; |
2688 | assignment->variant_op = Variant::OP_BIT_AND; |
2689 | break; |
2690 | case GDScriptTokenizer::Token::PIPE_EQUAL: |
2691 | assignment->operation = AssignmentNode::OP_BIT_OR; |
2692 | assignment->variant_op = Variant::OP_BIT_OR; |
2693 | break; |
2694 | case GDScriptTokenizer::Token::CARET_EQUAL: |
2695 | assignment->operation = AssignmentNode::OP_BIT_XOR; |
2696 | assignment->variant_op = Variant::OP_BIT_XOR; |
2697 | break; |
2698 | default: |
2699 | break; // Unreachable. |
2700 | } |
2701 | assignment->assignee = p_previous_operand; |
2702 | assignment->assigned_value = parse_expression(false); |
2703 | if (assignment->assigned_value == nullptr) { |
2704 | push_error(R"(Expected an expression after "=".)" ); |
2705 | } |
2706 | complete_extents(assignment); |
2707 | |
2708 | #ifdef DEBUG_ENABLED |
2709 | if (source_variable != nullptr) { |
2710 | if (has_operator && source_variable->assignments == 0) { |
2711 | push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); |
2712 | } |
2713 | |
2714 | source_variable->assignments += 1; |
2715 | } |
2716 | #endif |
2717 | |
2718 | return assignment; |
2719 | } |
2720 | |
2721 | GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2722 | AwaitNode *await = alloc_node<AwaitNode>(); |
2723 | ExpressionNode *element = parse_precedence(PREC_AWAIT, false); |
2724 | if (element == nullptr) { |
2725 | push_error(R"(Expected signal or coroutine after "await".)" ); |
2726 | } |
2727 | await->to_await = element; |
2728 | complete_extents(await); |
2729 | |
2730 | if (current_function) { // Might be null in a getter or setter. |
2731 | current_function->is_coroutine = true; |
2732 | } |
2733 | |
2734 | return await; |
2735 | } |
2736 | |
2737 | GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2738 | ArrayNode *array = alloc_node<ArrayNode>(); |
2739 | |
2740 | if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) { |
2741 | do { |
2742 | if (check(GDScriptTokenizer::Token::BRACKET_CLOSE)) { |
2743 | // Allow for trailing comma. |
2744 | break; |
2745 | } |
2746 | |
2747 | ExpressionNode *element = parse_expression(false); |
2748 | if (element == nullptr) { |
2749 | push_error(R"(Expected expression as array element.)" ); |
2750 | } else { |
2751 | array->elements.push_back(element); |
2752 | } |
2753 | } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); |
2754 | } |
2755 | pop_multiline(); |
2756 | consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after array elements.)" ); |
2757 | complete_extents(array); |
2758 | |
2759 | return array; |
2760 | } |
2761 | |
2762 | GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2763 | DictionaryNode *dictionary = alloc_node<DictionaryNode>(); |
2764 | |
2765 | bool decided_style = false; |
2766 | if (!check(GDScriptTokenizer::Token::BRACE_CLOSE)) { |
2767 | do { |
2768 | if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { |
2769 | // Allow for trailing comma. |
2770 | break; |
2771 | } |
2772 | |
2773 | // Key. |
2774 | ExpressionNode *key = parse_expression(false, true); // Stop on "=" so we can check for Lua table style. |
2775 | |
2776 | if (key == nullptr) { |
2777 | push_error(R"(Expected expression as dictionary key.)" ); |
2778 | } |
2779 | |
2780 | if (!decided_style) { |
2781 | switch (current.type) { |
2782 | case GDScriptTokenizer::Token::COLON: |
2783 | dictionary->style = DictionaryNode::PYTHON_DICT; |
2784 | break; |
2785 | case GDScriptTokenizer::Token::EQUAL: |
2786 | dictionary->style = DictionaryNode::LUA_TABLE; |
2787 | break; |
2788 | default: |
2789 | push_error(R"(Expected ":" or "=" after dictionary key.)" ); |
2790 | break; |
2791 | } |
2792 | decided_style = true; |
2793 | } |
2794 | |
2795 | switch (dictionary->style) { |
2796 | case DictionaryNode::LUA_TABLE: |
2797 | if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) { |
2798 | push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)" ); |
2799 | advance(); |
2800 | break; |
2801 | } |
2802 | if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) { |
2803 | push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)" ); |
2804 | advance(); |
2805 | break; |
2806 | } |
2807 | if (!match(GDScriptTokenizer::Token::EQUAL)) { |
2808 | if (match(GDScriptTokenizer::Token::COLON)) { |
2809 | push_error(R"(Expected "=" after dictionary key. Mixing dictionary styles is not allowed.)" ); |
2810 | advance(); // Consume wrong separator anyway. |
2811 | } else { |
2812 | push_error(R"(Expected "=" after dictionary key.)" ); |
2813 | } |
2814 | } |
2815 | if (key != nullptr) { |
2816 | key->is_constant = true; |
2817 | if (key->type == Node::IDENTIFIER) { |
2818 | key->reduced_value = static_cast<IdentifierNode *>(key)->name; |
2819 | } else if (key->type == Node::LITERAL) { |
2820 | key->reduced_value = StringName(static_cast<LiteralNode *>(key)->value.operator String()); |
2821 | } |
2822 | } |
2823 | break; |
2824 | case DictionaryNode::PYTHON_DICT: |
2825 | if (!match(GDScriptTokenizer::Token::COLON)) { |
2826 | if (match(GDScriptTokenizer::Token::EQUAL)) { |
2827 | push_error(R"(Expected ":" after dictionary key. Mixing dictionary styles is not allowed.)" ); |
2828 | advance(); // Consume wrong separator anyway. |
2829 | } else { |
2830 | push_error(R"(Expected ":" after dictionary key.)" ); |
2831 | } |
2832 | } |
2833 | break; |
2834 | } |
2835 | |
2836 | // Value. |
2837 | ExpressionNode *value = parse_expression(false); |
2838 | if (value == nullptr) { |
2839 | push_error(R"(Expected expression as dictionary value.)" ); |
2840 | } |
2841 | |
2842 | if (key != nullptr && value != nullptr) { |
2843 | dictionary->elements.push_back({ key, value }); |
2844 | } |
2845 | } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); |
2846 | } |
2847 | pop_multiline(); |
2848 | consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" after dictionary elements.)" ); |
2849 | complete_extents(dictionary); |
2850 | |
2851 | return dictionary; |
2852 | } |
2853 | |
2854 | GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2855 | ExpressionNode *grouped = parse_expression(false); |
2856 | pop_multiline(); |
2857 | if (grouped == nullptr) { |
2858 | push_error(R"(Expected grouping expression.)" ); |
2859 | } else { |
2860 | consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*" ); |
2861 | } |
2862 | return grouped; |
2863 | } |
2864 | |
2865 | GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2866 | SubscriptNode *attribute = alloc_node<SubscriptNode>(); |
2867 | reset_extents(attribute, p_previous_operand); |
2868 | update_extents(attribute); |
2869 | |
2870 | if (for_completion) { |
2871 | bool is_builtin = false; |
2872 | if (p_previous_operand && p_previous_operand->type == Node::IDENTIFIER) { |
2873 | const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand); |
2874 | Variant::Type builtin_type = get_builtin_type(id->name); |
2875 | if (builtin_type < Variant::VARIANT_MAX) { |
2876 | make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type, true); |
2877 | is_builtin = true; |
2878 | } |
2879 | } |
2880 | if (!is_builtin) { |
2881 | make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true); |
2882 | } |
2883 | } |
2884 | |
2885 | attribute->base = p_previous_operand; |
2886 | |
2887 | if (current.is_node_name()) { |
2888 | current.type = GDScriptTokenizer::Token::IDENTIFIER; |
2889 | } |
2890 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)" )) { |
2891 | complete_extents(attribute); |
2892 | return attribute; |
2893 | } |
2894 | |
2895 | attribute->is_attribute = true; |
2896 | attribute->attribute = parse_identifier(); |
2897 | |
2898 | complete_extents(attribute); |
2899 | return attribute; |
2900 | } |
2901 | |
2902 | GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2903 | SubscriptNode *subscript = alloc_node<SubscriptNode>(); |
2904 | reset_extents(subscript, p_previous_operand); |
2905 | update_extents(subscript); |
2906 | |
2907 | make_completion_context(COMPLETION_SUBSCRIPT, subscript); |
2908 | |
2909 | subscript->base = p_previous_operand; |
2910 | subscript->index = parse_expression(false); |
2911 | |
2912 | if (subscript->index == nullptr) { |
2913 | push_error(R"(Expected expression after "[".)" ); |
2914 | } |
2915 | |
2916 | pop_multiline(); |
2917 | consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)" ); |
2918 | complete_extents(subscript); |
2919 | |
2920 | return subscript; |
2921 | } |
2922 | |
2923 | GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2924 | CastNode *cast = alloc_node<CastNode>(); |
2925 | reset_extents(cast, p_previous_operand); |
2926 | update_extents(cast); |
2927 | |
2928 | cast->operand = p_previous_operand; |
2929 | cast->cast_type = parse_type(); |
2930 | complete_extents(cast); |
2931 | |
2932 | if (cast->cast_type == nullptr) { |
2933 | push_error(R"(Expected type specifier after "as".)" ); |
2934 | return p_previous_operand; |
2935 | } |
2936 | |
2937 | return cast; |
2938 | } |
2939 | |
2940 | GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_previous_operand, bool p_can_assign) { |
2941 | CallNode *call = alloc_node<CallNode>(); |
2942 | reset_extents(call, p_previous_operand); |
2943 | |
2944 | if (previous.type == GDScriptTokenizer::Token::SUPER) { |
2945 | // Super call. |
2946 | call->is_super = true; |
2947 | push_multiline(true); |
2948 | if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { |
2949 | // Implicit call to the parent method of the same name. |
2950 | if (current_function == nullptr) { |
2951 | push_error(R"(Cannot use implicit "super" call outside of a function.)" ); |
2952 | pop_multiline(); |
2953 | complete_extents(call); |
2954 | return nullptr; |
2955 | } |
2956 | if (current_function->identifier) { |
2957 | call->function_name = current_function->identifier->name; |
2958 | } else { |
2959 | call->function_name = SNAME("<anonymous>" ); |
2960 | } |
2961 | } else { |
2962 | consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)" ); |
2963 | make_completion_context(COMPLETION_SUPER_METHOD, call, true); |
2964 | if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)" )) { |
2965 | pop_multiline(); |
2966 | complete_extents(call); |
2967 | return nullptr; |
2968 | } |
2969 | IdentifierNode *identifier = parse_identifier(); |
2970 | call->callee = identifier; |
2971 | call->function_name = identifier->name; |
2972 | consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after function name.)" ); |
2973 | } |
2974 | } else { |
2975 | call->callee = p_previous_operand; |
2976 | |
2977 | if (call->callee == nullptr) { |
2978 | push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*" ); |
2979 | } else if (call->callee->type == Node::IDENTIFIER) { |
2980 | call->function_name = static_cast<IdentifierNode *>(call->callee)->name; |
2981 | make_completion_context(COMPLETION_METHOD, call->callee); |
2982 | } else if (call->callee->type == Node::SUBSCRIPT) { |
2983 | SubscriptNode *attribute = static_cast<SubscriptNode *>(call->callee); |
2984 | if (attribute->is_attribute) { |
2985 | if (attribute->attribute) { |
2986 | call->function_name = attribute->attribute->name; |
2987 | } |
2988 | make_completion_context(COMPLETION_ATTRIBUTE_METHOD, call->callee); |
2989 | } else { |
2990 | // TODO: The analyzer can see if this is actually a Callable and give better error message. |
2991 | push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*" ); |
2992 | } |
2993 | } else { |
2994 | push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*" ); |
2995 | } |
2996 | } |
2997 | |
2998 | // Arguments. |
2999 | CompletionType ct = COMPLETION_CALL_ARGUMENTS; |
3000 | if (call->function_name == SNAME("load" )) { |
3001 | ct = COMPLETION_RESOURCE_PATH; |
3002 | } |
3003 | push_completion_call(call); |
3004 | int argument_index = 0; |
3005 | do { |
3006 | make_completion_context(ct, call, argument_index++, true); |
3007 | if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { |
3008 | // Allow for trailing comma. |
3009 | break; |
3010 | } |
3011 | bool use_identifier_completion = current.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE; |
3012 | ExpressionNode *argument = parse_expression(false); |
3013 | if (argument == nullptr) { |
3014 | push_error(R"(Expected expression as the function argument.)" ); |
3015 | } else { |
3016 | call->arguments.push_back(argument); |
3017 | |
3018 | if (argument->type == Node::IDENTIFIER && use_identifier_completion) { |
3019 | completion_context.type = COMPLETION_IDENTIFIER; |
3020 | } |
3021 | } |
3022 | ct = COMPLETION_CALL_ARGUMENTS; |
3023 | } while (match(GDScriptTokenizer::Token::COMMA)); |
3024 | pop_completion_call(); |
3025 | |
3026 | pop_multiline(); |
3027 | consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*" ); |
3028 | complete_extents(call); |
3029 | |
3030 | return call; |
3031 | } |
3032 | |
3033 | GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) { |
3034 | if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { |
3035 | push_error(vformat(R"(Expected node path as string or identifier after "%s".)" , previous.get_name())); |
3036 | return nullptr; |
3037 | } |
3038 | |
3039 | if (check(GDScriptTokenizer::Token::LITERAL)) { |
3040 | if (current.literal.get_type() != Variant::STRING) { |
3041 | push_error(vformat(R"(Expected node path as string or identifier after "%s".)" , previous.get_name())); |
3042 | return nullptr; |
3043 | } |
3044 | } |
3045 | |
3046 | GetNodeNode *get_node = alloc_node<GetNodeNode>(); |
3047 | |
3048 | // Store the last item in the path so the parser knows what to expect. |
3049 | // Allow allows more specific error messages. |
3050 | enum PathState { |
3051 | PATH_STATE_START, |
3052 | PATH_STATE_SLASH, |
3053 | PATH_STATE_PERCENT, |
3054 | PATH_STATE_NODE_NAME, |
3055 | } path_state = PATH_STATE_START; |
3056 | |
3057 | if (previous.type == GDScriptTokenizer::Token::DOLLAR) { |
3058 | // Detect initial slash, which will be handled in the loop if it matches. |
3059 | match(GDScriptTokenizer::Token::SLASH); |
3060 | } else { |
3061 | get_node->use_dollar = false; |
3062 | } |
3063 | |
3064 | int context_argument = 0; |
3065 | |
3066 | do { |
3067 | if (previous.type == GDScriptTokenizer::Token::PERCENT) { |
3068 | if (path_state != PATH_STATE_START && path_state != PATH_STATE_SLASH) { |
3069 | push_error(R"("%" is only valid in the beginning of a node name (either after "$" or after "/"))" ); |
3070 | complete_extents(get_node); |
3071 | return nullptr; |
3072 | } |
3073 | get_node->full_path += "%" ; |
3074 | |
3075 | path_state = PATH_STATE_PERCENT; |
3076 | } else if (previous.type == GDScriptTokenizer::Token::SLASH) { |
3077 | if (path_state != PATH_STATE_START && path_state != PATH_STATE_NODE_NAME) { |
3078 | push_error(R"("/" is only valid at the beginning of the path or after a node name.)" ); |
3079 | complete_extents(get_node); |
3080 | return nullptr; |
3081 | } |
3082 | |
3083 | get_node->full_path += "/" ; |
3084 | |
3085 | path_state = PATH_STATE_SLASH; |
3086 | } |
3087 | |
3088 | make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++); |
3089 | |
3090 | if (match(GDScriptTokenizer::Token::LITERAL)) { |
3091 | if (previous.literal.get_type() != Variant::STRING) { |
3092 | String previous_token; |
3093 | switch (path_state) { |
3094 | case PATH_STATE_START: |
3095 | previous_token = "$" ; |
3096 | break; |
3097 | case PATH_STATE_PERCENT: |
3098 | previous_token = "%" ; |
3099 | break; |
3100 | case PATH_STATE_SLASH: |
3101 | previous_token = "/" ; |
3102 | break; |
3103 | default: |
3104 | break; |
3105 | } |
3106 | push_error(vformat(R"(Expected node path as string or identifier after "%s".)" , previous_token)); |
3107 | complete_extents(get_node); |
3108 | return nullptr; |
3109 | } |
3110 | |
3111 | get_node->full_path += previous.literal.operator String(); |
3112 | |
3113 | path_state = PATH_STATE_NODE_NAME; |
3114 | } else if (current.is_node_name()) { |
3115 | advance(); |
3116 | String identifier = previous.get_identifier(); |
3117 | #ifdef DEBUG_ENABLED |
3118 | // Check spoofing. |
3119 | if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier)) { |
3120 | push_warning(get_node, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier); |
3121 | } |
3122 | #endif |
3123 | get_node->full_path += identifier; |
3124 | |
3125 | path_state = PATH_STATE_NODE_NAME; |
3126 | } else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { |
3127 | push_error(vformat(R"(Unexpected "%s" in node path.)" , current.get_name())); |
3128 | complete_extents(get_node); |
3129 | return nullptr; |
3130 | } |
3131 | } while (match(GDScriptTokenizer::Token::SLASH) || match(GDScriptTokenizer::Token::PERCENT)); |
3132 | |
3133 | complete_extents(get_node); |
3134 | return get_node; |
3135 | } |
3136 | |
3137 | GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign) { |
3138 | PreloadNode *preload = alloc_node<PreloadNode>(); |
3139 | preload->resolved_path = "<missing path>" ; |
3140 | |
3141 | push_multiline(true); |
3142 | consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)" ); |
3143 | |
3144 | make_completion_context(COMPLETION_RESOURCE_PATH, preload); |
3145 | push_completion_call(preload); |
3146 | |
3147 | preload->path = parse_expression(false); |
3148 | |
3149 | if (preload->path == nullptr) { |
3150 | push_error(R"(Expected resource path after "(".)" ); |
3151 | } |
3152 | |
3153 | pop_completion_call(); |
3154 | |
3155 | pop_multiline(); |
3156 | consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*" ); |
3157 | complete_extents(preload); |
3158 | |
3159 | return preload; |
3160 | } |
3161 | |
3162 | GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) { |
3163 | LambdaNode *lambda = alloc_node<LambdaNode>(); |
3164 | lambda->parent_function = current_function; |
3165 | lambda->parent_lambda = current_lambda; |
3166 | |
3167 | FunctionNode *function = alloc_node<FunctionNode>(); |
3168 | function->source_lambda = lambda; |
3169 | |
3170 | function->is_static = current_function != nullptr ? current_function->is_static : false; |
3171 | |
3172 | if (match(GDScriptTokenizer::Token::IDENTIFIER)) { |
3173 | function->identifier = parse_identifier(); |
3174 | } |
3175 | |
3176 | bool multiline_context = multiline_stack.back()->get(); |
3177 | |
3178 | // Reset the multiline stack since we don't want the multiline mode one in the lambda body. |
3179 | push_multiline(false); |
3180 | if (multiline_context) { |
3181 | tokenizer.push_expression_indented_block(); |
3182 | } |
3183 | |
3184 | push_multiline(true); // For the parameters. |
3185 | if (function->identifier) { |
3186 | consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after lambda name.)" ); |
3187 | } else { |
3188 | consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after "func".)" ); |
3189 | } |
3190 | |
3191 | FunctionNode *previous_function = current_function; |
3192 | current_function = function; |
3193 | |
3194 | LambdaNode *previous_lambda = current_lambda; |
3195 | current_lambda = lambda; |
3196 | |
3197 | SuiteNode *body = alloc_node<SuiteNode>(); |
3198 | body->parent_function = current_function; |
3199 | body->parent_block = current_suite; |
3200 | |
3201 | SuiteNode *previous_suite = current_suite; |
3202 | current_suite = body; |
3203 | |
3204 | parse_function_signature(function, body, "lambda" ); |
3205 | |
3206 | current_suite = previous_suite; |
3207 | |
3208 | bool previous_in_lambda = in_lambda; |
3209 | in_lambda = true; |
3210 | |
3211 | // Save break/continue state. |
3212 | bool could_break = can_break; |
3213 | bool could_continue = can_continue; |
3214 | |
3215 | // Disallow break/continue. |
3216 | can_break = false; |
3217 | can_continue = false; |
3218 | |
3219 | function->body = parse_suite("lambda declaration" , body, true); |
3220 | complete_extents(function); |
3221 | complete_extents(lambda); |
3222 | |
3223 | pop_multiline(); |
3224 | |
3225 | if (multiline_context) { |
3226 | // If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens. |
3227 | while (check(GDScriptTokenizer::Token::DEDENT) || check(GDScriptTokenizer::Token::INDENT) || check(GDScriptTokenizer::Token::NEWLINE)) { |
3228 | current = tokenizer.scan(); // Not advance() since we don't want to change the previous token. |
3229 | } |
3230 | tokenizer.pop_expression_indented_block(); |
3231 | } |
3232 | |
3233 | current_function = previous_function; |
3234 | current_lambda = previous_lambda; |
3235 | in_lambda = previous_in_lambda; |
3236 | lambda->function = function; |
3237 | |
3238 | // Reset break/continue state. |
3239 | can_break = could_break; |
3240 | can_continue = could_continue; |
3241 | |
3242 | return lambda; |
3243 | } |
3244 | |
3245 | GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) { |
3246 | TypeTestNode *type_test = alloc_node<TypeTestNode>(); |
3247 | reset_extents(type_test, p_previous_operand); |
3248 | update_extents(type_test); |
3249 | |
3250 | type_test->operand = p_previous_operand; |
3251 | type_test->test_type = parse_type(); |
3252 | complete_extents(type_test); |
3253 | |
3254 | if (type_test->test_type == nullptr) { |
3255 | push_error(R"(Expected type specifier after "is".)" ); |
3256 | } |
3257 | |
3258 | return type_test; |
3259 | } |
3260 | |
3261 | GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) { |
3262 | push_error(R"("yield" was removed in Godot 4. Use "await" instead.)" ); |
3263 | return nullptr; |
3264 | } |
3265 | |
3266 | GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) { |
3267 | // Just for better error messages. |
3268 | GDScriptTokenizer::Token::Type invalid = previous.type; |
3269 | |
3270 | switch (invalid) { |
3271 | case GDScriptTokenizer::Token::QUESTION_MARK: |
3272 | push_error(R"(Unexpected "?" in source. If you want a ternary operator, use "truthy_value if true_condition else falsy_value".)" ); |
3273 | break; |
3274 | default: |
3275 | return nullptr; // Unreachable. |
3276 | } |
3277 | |
3278 | // Return the previous expression. |
3279 | return p_previous_operand; |
3280 | } |
3281 | |
3282 | GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { |
3283 | TypeNode *type = alloc_node<TypeNode>(); |
3284 | make_completion_context(p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME, type); |
3285 | if (!match(GDScriptTokenizer::Token::IDENTIFIER)) { |
3286 | if (match(GDScriptTokenizer::Token::VOID)) { |
3287 | if (p_allow_void) { |
3288 | complete_extents(type); |
3289 | TypeNode *void_type = type; |
3290 | return void_type; |
3291 | } else { |
3292 | push_error(R"("void" is only allowed for a function return type.)" ); |
3293 | } |
3294 | } |
3295 | // Leave error message to the caller who knows the context. |
3296 | complete_extents(type); |
3297 | return nullptr; |
3298 | } |
3299 | |
3300 | IdentifierNode *type_element = parse_identifier(); |
3301 | |
3302 | type->type_chain.push_back(type_element); |
3303 | |
3304 | if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) { |
3305 | // Typed collection (like Array[int]). |
3306 | type->container_type = parse_type(false); // Don't allow void for array element type. |
3307 | if (type->container_type == nullptr) { |
3308 | push_error(R"(Expected type for collection after "[".)" ); |
3309 | complete_extents(type); |
3310 | type = nullptr; |
3311 | } else if (type->container_type->container_type != nullptr) { |
3312 | push_error("Nested typed collections are not supported." ); |
3313 | } |
3314 | consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)" ); |
3315 | if (type != nullptr) { |
3316 | complete_extents(type); |
3317 | } |
3318 | return type; |
3319 | } |
3320 | |
3321 | int chain_index = 1; |
3322 | while (match(GDScriptTokenizer::Token::PERIOD)) { |
3323 | make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++); |
3324 | if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)" )) { |
3325 | type_element = parse_identifier(); |
3326 | type->type_chain.push_back(type_element); |
3327 | } |
3328 | } |
3329 | |
3330 | complete_extents(type); |
3331 | return type; |
3332 | } |
3333 | |
3334 | #ifdef TOOLS_ENABLED |
3335 | enum DocLineState { |
3336 | DOC_LINE_NORMAL, |
3337 | DOC_LINE_IN_CODE, |
3338 | DOC_LINE_IN_CODEBLOCK, |
3339 | }; |
3340 | |
3341 | static String _process_doc_line(const String &p_line, const String &p_text, const String &p_space_prefix, DocLineState &r_state) { |
3342 | String line = p_line; |
3343 | if (r_state == DOC_LINE_NORMAL) { |
3344 | line = line.strip_edges(true, false); |
3345 | } else { |
3346 | line = line.trim_prefix(p_space_prefix); |
3347 | } |
3348 | |
3349 | String line_join; |
3350 | if (!p_text.is_empty()) { |
3351 | if (r_state == DOC_LINE_NORMAL) { |
3352 | if (p_text.ends_with("[/codeblock]" )) { |
3353 | line_join = "\n" ; |
3354 | } else if (!p_text.ends_with("[br]" )) { |
3355 | line_join = " " ; |
3356 | } |
3357 | } else { |
3358 | line_join = "\n" ; |
3359 | } |
3360 | } |
3361 | |
3362 | String result; |
3363 | int from = 0; |
3364 | int buffer_start = 0; |
3365 | const int len = line.length(); |
3366 | bool process = true; |
3367 | while (process) { |
3368 | switch (r_state) { |
3369 | case DOC_LINE_NORMAL: { |
3370 | int lb_pos = line.find_char('[', from); |
3371 | if (lb_pos < 0) { |
3372 | process = false; |
3373 | break; |
3374 | } |
3375 | int rb_pos = line.find_char(']', lb_pos + 1); |
3376 | if (rb_pos < 0) { |
3377 | process = false; |
3378 | break; |
3379 | } |
3380 | |
3381 | from = rb_pos + 1; |
3382 | |
3383 | String tag = line.substr(lb_pos + 1, rb_pos - lb_pos - 1); |
3384 | if (tag == "code" ) { |
3385 | r_state = DOC_LINE_IN_CODE; |
3386 | } else if (tag == "codeblock" ) { |
3387 | if (lb_pos == 0) { |
3388 | line_join = "\n" ; |
3389 | } else { |
3390 | result += line.substr(buffer_start, lb_pos - buffer_start) + '\n'; |
3391 | } |
3392 | result += "[codeblock]" ; |
3393 | if (from < len) { |
3394 | result += '\n'; |
3395 | } |
3396 | |
3397 | r_state = DOC_LINE_IN_CODEBLOCK; |
3398 | buffer_start = from; |
3399 | } |
3400 | } break; |
3401 | case DOC_LINE_IN_CODE: { |
3402 | int pos = line.find("[/code]" , from); |
3403 | if (pos < 0) { |
3404 | process = false; |
3405 | break; |
3406 | } |
3407 | |
3408 | from = pos + 7; |
3409 | |
3410 | r_state = DOC_LINE_NORMAL; |
3411 | } break; |
3412 | case DOC_LINE_IN_CODEBLOCK: { |
3413 | int pos = line.find("[/codeblock]" , from); |
3414 | if (pos < 0) { |
3415 | process = false; |
3416 | break; |
3417 | } |
3418 | |
3419 | from = pos + 12; |
3420 | |
3421 | if (pos == 0) { |
3422 | line_join = "\n" ; |
3423 | } else { |
3424 | result += line.substr(buffer_start, pos - buffer_start) + '\n'; |
3425 | } |
3426 | result += "[/codeblock]" ; |
3427 | if (from < len) { |
3428 | result += '\n'; |
3429 | } |
3430 | |
3431 | r_state = DOC_LINE_NORMAL; |
3432 | buffer_start = from; |
3433 | } break; |
3434 | } |
3435 | } |
3436 | |
3437 | result += line.substr(buffer_start); |
3438 | if (r_state == DOC_LINE_NORMAL) { |
3439 | result = result.strip_edges(false, true); |
3440 | } |
3441 | |
3442 | return line_join + result; |
3443 | } |
3444 | |
3445 | bool GDScriptParser::(int p_line, bool p_must_be_doc) { |
3446 | bool = tokenizer.get_comments().has(p_line); |
3447 | // If there are no comments or if we don't care whether the comment |
3448 | // is a docstring, we have our result. |
3449 | if (!p_must_be_doc || !has_comment) { |
3450 | return has_comment; |
3451 | } |
3452 | |
3453 | return tokenizer.get_comments()[p_line].comment.begins_with("##" ); |
3454 | } |
3455 | |
3456 | GDScriptParser::MemberDocData GDScriptParser::(int p_line, bool p_single_line) { |
3457 | MemberDocData result; |
3458 | |
3459 | const HashMap<int, GDScriptTokenizer::CommentData> & = tokenizer.get_comments(); |
3460 | ERR_FAIL_COND_V(!comments.has(p_line), result); |
3461 | |
3462 | if (p_single_line) { |
3463 | if (comments[p_line].comment.begins_with("##" )) { |
3464 | result.description = comments[p_line].comment.trim_prefix("##" ).strip_edges(); |
3465 | return result; |
3466 | } |
3467 | return result; |
3468 | } |
3469 | |
3470 | int line = p_line; |
3471 | DocLineState state = DOC_LINE_NORMAL; |
3472 | |
3473 | while (comments.has(line - 1)) { |
3474 | if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##" )) { |
3475 | break; |
3476 | } |
3477 | line--; |
3478 | } |
3479 | |
3480 | String space_prefix; |
3481 | if (comments.has(line) && comments[line].comment.begins_with("##" )) { |
3482 | int i = 2; |
3483 | for (; i < comments[line].comment.length(); i++) { |
3484 | if (comments[line].comment[i] != ' ') { |
3485 | break; |
3486 | } |
3487 | } |
3488 | space_prefix = String(" " ).repeat(i - 2); |
3489 | } |
3490 | |
3491 | while (comments.has(line)) { |
3492 | if (!comments[line].new_line || !comments[line].comment.begins_with("##" )) { |
3493 | break; |
3494 | } |
3495 | |
3496 | String doc_line = comments[line].comment.trim_prefix("##" ); |
3497 | line++; |
3498 | |
3499 | if (state == DOC_LINE_NORMAL) { |
3500 | String stripped_line = doc_line.strip_edges(); |
3501 | if (stripped_line.begins_with("@deprecated" )) { |
3502 | result.is_deprecated = true; |
3503 | continue; |
3504 | } else if (stripped_line.begins_with("@experimental" )) { |
3505 | result.is_experimental = true; |
3506 | continue; |
3507 | } |
3508 | } |
3509 | |
3510 | result.description += _process_doc_line(doc_line, result.description, space_prefix, state); |
3511 | } |
3512 | |
3513 | return result; |
3514 | } |
3515 | |
3516 | GDScriptParser::ClassDocData GDScriptParser::(int p_line, bool p_inner_class, bool p_single_line) { |
3517 | ClassDocData result; |
3518 | |
3519 | const HashMap<int, GDScriptTokenizer::CommentData> & = tokenizer.get_comments(); |
3520 | ERR_FAIL_COND_V(!comments.has(p_line), result); |
3521 | |
3522 | if (p_single_line) { |
3523 | if (comments[p_line].comment.begins_with("##" )) { |
3524 | result.brief = comments[p_line].comment.trim_prefix("##" ).strip_edges(); |
3525 | return result; |
3526 | } |
3527 | return result; |
3528 | } |
3529 | |
3530 | int line = p_line; |
3531 | DocLineState state = DOC_LINE_NORMAL; |
3532 | bool is_in_brief = true; |
3533 | |
3534 | if (p_inner_class) { |
3535 | while (comments.has(line - 1)) { |
3536 | if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##" )) { |
3537 | break; |
3538 | } |
3539 | line--; |
3540 | } |
3541 | } |
3542 | |
3543 | String space_prefix; |
3544 | if (comments.has(line) && comments[line].comment.begins_with("##" )) { |
3545 | int i = 2; |
3546 | for (; i < comments[line].comment.length(); i++) { |
3547 | if (comments[line].comment[i] != ' ') { |
3548 | break; |
3549 | } |
3550 | } |
3551 | space_prefix = String(" " ).repeat(i - 2); |
3552 | } |
3553 | |
3554 | while (comments.has(line)) { |
3555 | if (!comments[line].new_line || !comments[line].comment.begins_with("##" )) { |
3556 | break; |
3557 | } |
3558 | |
3559 | String doc_line = comments[line].comment.trim_prefix("##" ); |
3560 | line++; |
3561 | |
3562 | if (state == DOC_LINE_NORMAL) { |
3563 | String stripped_line = doc_line.strip_edges(); |
3564 | |
3565 | // A blank line separates the description from the brief. |
3566 | if (is_in_brief && !result.brief.is_empty() && stripped_line.is_empty()) { |
3567 | is_in_brief = false; |
3568 | continue; |
3569 | } |
3570 | |
3571 | if (stripped_line.begins_with("@tutorial" )) { |
3572 | String title, link; |
3573 | |
3574 | int begin_scan = String("@tutorial" ).length(); |
3575 | if (begin_scan >= stripped_line.length()) { |
3576 | continue; // Invalid syntax. |
3577 | } |
3578 | |
3579 | if (stripped_line[begin_scan] == ':') { // No title. |
3580 | // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional. |
3581 | title = "" ; |
3582 | link = stripped_line.trim_prefix("@tutorial:" ).strip_edges(); |
3583 | } else { |
3584 | /* Syntax: |
3585 | * @tutorial ( The Title Here ) : https://the.url/ |
3586 | * ^ open ^ close ^ colon ^ url |
3587 | */ |
3588 | int open_bracket_pos = begin_scan, close_bracket_pos = 0; |
3589 | while (open_bracket_pos < stripped_line.length() && (stripped_line[open_bracket_pos] == ' ' || stripped_line[open_bracket_pos] == '\t')) { |
3590 | open_bracket_pos++; |
3591 | } |
3592 | if (open_bracket_pos == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') { |
3593 | continue; // Invalid syntax. |
3594 | } |
3595 | close_bracket_pos = open_bracket_pos; |
3596 | while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') { |
3597 | close_bracket_pos++; |
3598 | } |
3599 | if (close_bracket_pos == stripped_line.length()) { |
3600 | continue; // Invalid syntax. |
3601 | } |
3602 | |
3603 | int colon_pos = close_bracket_pos + 1; |
3604 | while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) { |
3605 | colon_pos++; |
3606 | } |
3607 | if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') { |
3608 | continue; // Invalid syntax. |
3609 | } |
3610 | |
3611 | title = stripped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); |
3612 | link = stripped_line.substr(colon_pos).strip_edges(); |
3613 | } |
3614 | |
3615 | result.tutorials.append(Pair<String, String>(title, link)); |
3616 | continue; |
3617 | } else if (stripped_line.begins_with("@deprecated" )) { |
3618 | result.is_deprecated = true; |
3619 | continue; |
3620 | } else if (stripped_line.begins_with("@experimental" )) { |
3621 | result.is_experimental = true; |
3622 | continue; |
3623 | } |
3624 | } |
3625 | |
3626 | if (is_in_brief) { |
3627 | result.brief += _process_doc_line(doc_line, result.brief, space_prefix, state); |
3628 | } else { |
3629 | result.description += _process_doc_line(doc_line, result.description, space_prefix, state); |
3630 | } |
3631 | } |
3632 | |
3633 | if (current_class->members.size() > 0) { |
3634 | const ClassNode::Member &m = current_class->members[0]; |
3635 | int first_member_line = m.get_line(); |
3636 | if (first_member_line == line) { |
3637 | result = ClassDocData(); // Clear result. |
3638 | } |
3639 | } |
3640 | |
3641 | return result; |
3642 | } |
3643 | #endif // TOOLS_ENABLED |
3644 | |
3645 | GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Type p_token_type) { |
3646 | // Function table for expression parsing. |
3647 | // clang-format destroys the alignment here, so turn off for the table. |
3648 | /* clang-format off */ |
3649 | static ParseRule rules[] = { |
3650 | // PREFIX INFIX PRECEDENCE (for infix) |
3651 | { nullptr, nullptr, PREC_NONE }, // EMPTY, |
3652 | // Basic |
3653 | { nullptr, nullptr, PREC_NONE }, // ANNOTATION, |
3654 | { &GDScriptParser::parse_identifier, nullptr, PREC_NONE }, // IDENTIFIER, |
3655 | { &GDScriptParser::parse_literal, nullptr, PREC_NONE }, // LITERAL, |
3656 | // Comparison |
3657 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // LESS, |
3658 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // LESS_EQUAL, |
3659 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // GREATER, |
3660 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // GREATER_EQUAL, |
3661 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // EQUAL_EQUAL, |
3662 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // BANG_EQUAL, |
3663 | // Logical |
3664 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AND, |
3665 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // OR, |
3666 | { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_not_in_operator, PREC_CONTENT_TEST }, // NOT, |
3667 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AMPERSAND_AMPERSAND, |
3668 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // PIPE_PIPE, |
3669 | { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // BANG, |
3670 | // Bitwise |
3671 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_AND }, // AMPERSAND, |
3672 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_OR }, // PIPE, |
3673 | { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // TILDE, |
3674 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_XOR }, // CARET, |
3675 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // LESS_LESS, |
3676 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // GREATER_GREATER, |
3677 | // Math |
3678 | { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_ADDITION_SUBTRACTION }, // PLUS, |
3679 | { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_ADDITION_SUBTRACTION }, // MINUS, |
3680 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // STAR, |
3681 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_POWER }, // STAR_STAR, |
3682 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // SLASH, |
3683 | { &GDScriptParser::parse_get_node, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // PERCENT, |
3684 | // Assignment |
3685 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // EQUAL, |
3686 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PLUS_EQUAL, |
3687 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // MINUS_EQUAL, |
3688 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // STAR_EQUAL, |
3689 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // STAR_STAR_EQUAL, |
3690 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // SLASH_EQUAL, |
3691 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PERCENT_EQUAL, |
3692 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // LESS_LESS_EQUAL, |
3693 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // GREATER_GREATER_EQUAL, |
3694 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // AMPERSAND_EQUAL, |
3695 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PIPE_EQUAL, |
3696 | { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // CARET_EQUAL, |
3697 | // Control flow |
3698 | { nullptr, &GDScriptParser::parse_ternary_operator, PREC_TERNARY }, // IF, |
3699 | { nullptr, nullptr, PREC_NONE }, // ELIF, |
3700 | { nullptr, nullptr, PREC_NONE }, // ELSE, |
3701 | { nullptr, nullptr, PREC_NONE }, // FOR, |
3702 | { nullptr, nullptr, PREC_NONE }, // WHILE, |
3703 | { nullptr, nullptr, PREC_NONE }, // BREAK, |
3704 | { nullptr, nullptr, PREC_NONE }, // CONTINUE, |
3705 | { nullptr, nullptr, PREC_NONE }, // PASS, |
3706 | { nullptr, nullptr, PREC_NONE }, // RETURN, |
3707 | { nullptr, nullptr, PREC_NONE }, // MATCH, |
3708 | // Keywords |
3709 | { nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS, |
3710 | { nullptr, nullptr, PREC_NONE }, // ASSERT, |
3711 | { &GDScriptParser::parse_await, nullptr, PREC_NONE }, // AWAIT, |
3712 | { nullptr, nullptr, PREC_NONE }, // BREAKPOINT, |
3713 | { nullptr, nullptr, PREC_NONE }, // CLASS, |
3714 | { nullptr, nullptr, PREC_NONE }, // CLASS_NAME, |
3715 | { nullptr, nullptr, PREC_NONE }, // CONST, |
3716 | { nullptr, nullptr, PREC_NONE }, // ENUM, |
3717 | { nullptr, nullptr, PREC_NONE }, // EXTENDS, |
3718 | { &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC, |
3719 | { nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN, |
3720 | { nullptr, &GDScriptParser::parse_type_test, PREC_TYPE_TEST }, // IS, |
3721 | { nullptr, nullptr, PREC_NONE }, // NAMESPACE, |
3722 | { &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD, |
3723 | { &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF, |
3724 | { nullptr, nullptr, PREC_NONE }, // SIGNAL, |
3725 | { nullptr, nullptr, PREC_NONE }, // STATIC, |
3726 | { &GDScriptParser::parse_call, nullptr, PREC_NONE }, // SUPER, |
3727 | { nullptr, nullptr, PREC_NONE }, // TRAIT, |
3728 | { nullptr, nullptr, PREC_NONE }, // VAR, |
3729 | { nullptr, nullptr, PREC_NONE }, // VOID, |
3730 | { &GDScriptParser::parse_yield, nullptr, PREC_NONE }, // YIELD, |
3731 | // Punctuation |
3732 | { &GDScriptParser::parse_array, &GDScriptParser::parse_subscript, PREC_SUBSCRIPT }, // BRACKET_OPEN, |
3733 | { nullptr, nullptr, PREC_NONE }, // BRACKET_CLOSE, |
3734 | { &GDScriptParser::parse_dictionary, nullptr, PREC_NONE }, // BRACE_OPEN, |
3735 | { nullptr, nullptr, PREC_NONE }, // BRACE_CLOSE, |
3736 | { &GDScriptParser::parse_grouping, &GDScriptParser::parse_call, PREC_CALL }, // PARENTHESIS_OPEN, |
3737 | { nullptr, nullptr, PREC_NONE }, // PARENTHESIS_CLOSE, |
3738 | { nullptr, nullptr, PREC_NONE }, // COMMA, |
3739 | { nullptr, nullptr, PREC_NONE }, // SEMICOLON, |
3740 | { nullptr, &GDScriptParser::parse_attribute, PREC_ATTRIBUTE }, // PERIOD, |
3741 | { nullptr, nullptr, PREC_NONE }, // PERIOD_PERIOD, |
3742 | { nullptr, nullptr, PREC_NONE }, // COLON, |
3743 | { &GDScriptParser::parse_get_node, nullptr, PREC_NONE }, // DOLLAR, |
3744 | { nullptr, nullptr, PREC_NONE }, // FORWARD_ARROW, |
3745 | { nullptr, nullptr, PREC_NONE }, // UNDERSCORE, |
3746 | // Whitespace |
3747 | { nullptr, nullptr, PREC_NONE }, // NEWLINE, |
3748 | { nullptr, nullptr, PREC_NONE }, // INDENT, |
3749 | { nullptr, nullptr, PREC_NONE }, // DEDENT, |
3750 | // Constants |
3751 | { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_PI, |
3752 | { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_TAU, |
3753 | { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_INF, |
3754 | { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_NAN, |
3755 | // Error message improvement |
3756 | { nullptr, nullptr, PREC_NONE }, // VCS_CONFLICT_MARKER, |
3757 | { nullptr, nullptr, PREC_NONE }, // BACKTICK, |
3758 | { nullptr, &GDScriptParser::parse_invalid_token, PREC_CAST }, // QUESTION_MARK, |
3759 | // Special |
3760 | { nullptr, nullptr, PREC_NONE }, // ERROR, |
3761 | { nullptr, nullptr, PREC_NONE }, // TK_EOF, |
3762 | }; |
3763 | /* clang-format on */ |
3764 | // Avoid desync. |
3765 | static_assert(sizeof(rules) / sizeof(rules[0]) == GDScriptTokenizer::Token::TK_MAX, "Amount of parse rules don't match the amount of token types." ); |
3766 | |
3767 | // Let's assume this is never invalid, since nothing generates a TK_MAX. |
3768 | return &rules[p_token_type]; |
3769 | } |
3770 | |
3771 | bool GDScriptParser::SuiteNode::has_local(const StringName &p_name) const { |
3772 | if (locals_indices.has(p_name)) { |
3773 | return true; |
3774 | } |
3775 | if (parent_block != nullptr) { |
3776 | return parent_block->has_local(p_name); |
3777 | } |
3778 | return false; |
3779 | } |
3780 | |
3781 | const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(const StringName &p_name) const { |
3782 | if (locals_indices.has(p_name)) { |
3783 | return locals[locals_indices[p_name]]; |
3784 | } |
3785 | if (parent_block != nullptr) { |
3786 | return parent_block->get_local(p_name); |
3787 | } |
3788 | return empty; |
3789 | } |
3790 | |
3791 | bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) { |
3792 | if (is_applied) { |
3793 | return true; |
3794 | } |
3795 | is_applied = true; |
3796 | return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target); |
3797 | } |
3798 | |
3799 | bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { |
3800 | return (info->target_kind & p_target_kinds) > 0; |
3801 | } |
3802 | |
3803 | bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) { |
3804 | ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)" , p_annotation->name)); |
3805 | |
3806 | const MethodInfo &info = valid_annotations[p_annotation->name].info; |
3807 | |
3808 | if (((info.flags & METHOD_FLAG_VARARG) == 0) && p_annotation->arguments.size() > info.arguments.size()) { |
3809 | push_error(vformat(R"(Annotation "%s" requires at most %d arguments, but %d were given.)" , p_annotation->name, info.arguments.size(), p_annotation->arguments.size())); |
3810 | return false; |
3811 | } |
3812 | |
3813 | if (p_annotation->arguments.size() < info.arguments.size() - info.default_arguments.size()) { |
3814 | push_error(vformat(R"(Annotation "%s" requires at least %d arguments, but %d were given.)" , p_annotation->name, info.arguments.size() - info.default_arguments.size(), p_annotation->arguments.size())); |
3815 | return false; |
3816 | } |
3817 | |
3818 | // `@icon`'s argument needs to be resolved in the parser. See GH-72444. |
3819 | if (p_annotation->name == SNAME("@icon" )) { |
3820 | ExpressionNode *argument = p_annotation->arguments[0]; |
3821 | |
3822 | if (argument->type != Node::LITERAL) { |
3823 | push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)" , argument); |
3824 | return false; |
3825 | } |
3826 | |
3827 | Variant value = static_cast<LiteralNode *>(argument)->value; |
3828 | |
3829 | if (value.get_type() != Variant::STRING) { |
3830 | push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)" , argument); |
3831 | return false; |
3832 | } |
3833 | |
3834 | p_annotation->resolved_arguments.push_back(value); |
3835 | } |
3836 | |
3837 | // For other annotations, see `GDScriptAnalyzer::resolve_annotation()`. |
3838 | |
3839 | return true; |
3840 | } |
3841 | |
3842 | bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) { |
3843 | #ifdef DEBUG_ENABLED |
3844 | if (this->_is_tool) { |
3845 | push_error(R"("@tool" annotation can only be used once.)" , p_annotation); |
3846 | return false; |
3847 | } |
3848 | #endif // DEBUG_ENABLED |
3849 | this->_is_tool = true; |
3850 | return true; |
3851 | } |
3852 | |
3853 | bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) { |
3854 | ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)" ); |
3855 | ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); |
3856 | |
3857 | ClassNode *p_class = static_cast<ClassNode *>(p_node); |
3858 | String path = p_annotation->resolved_arguments[0]; |
3859 | |
3860 | #ifdef DEBUG_ENABLED |
3861 | if (!p_class->icon_path.is_empty()) { |
3862 | push_error(R"("@icon" annotation can only be used once.)" , p_annotation); |
3863 | return false; |
3864 | } |
3865 | if (path.is_empty()) { |
3866 | push_error(R"("@icon" annotation argument must contain the path to the icon.)" , p_annotation->arguments[0]); |
3867 | return false; |
3868 | } |
3869 | #endif // DEBUG_ENABLED |
3870 | |
3871 | p_class->icon_path = path; |
3872 | |
3873 | if (path.is_empty() || path.is_absolute_path()) { |
3874 | p_class->simplified_icon_path = path.simplify_path(); |
3875 | } else if (path.is_relative_path()) { |
3876 | p_class->simplified_icon_path = script_path.get_base_dir().path_join(path).simplify_path(); |
3877 | } else { |
3878 | p_class->simplified_icon_path = path; |
3879 | } |
3880 | |
3881 | return true; |
3882 | } |
3883 | |
3884 | bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) { |
3885 | ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)" ); |
3886 | |
3887 | if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node" ))) { |
3888 | push_error(R"("@onready" can only be used in classes that inherit "Node".)" , p_annotation); |
3889 | } |
3890 | |
3891 | VariableNode *variable = static_cast<VariableNode *>(p_node); |
3892 | if (variable->is_static) { |
3893 | push_error(R"("@onready" annotation cannot be applied to a static variable.)" , p_annotation); |
3894 | return false; |
3895 | } |
3896 | if (variable->onready) { |
3897 | push_error(R"("@onready" annotation can only be used once per variable.)" , p_annotation); |
3898 | return false; |
3899 | } |
3900 | variable->onready = true; |
3901 | current_class->onready_used = true; |
3902 | return true; |
3903 | } |
3904 | |
3905 | template <PropertyHint t_hint, Variant::Type t_type> |
3906 | bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) { |
3907 | ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)" , p_annotation->name)); |
3908 | |
3909 | VariableNode *variable = static_cast<VariableNode *>(p_node); |
3910 | if (variable->is_static) { |
3911 | push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)" , p_annotation->name), p_annotation); |
3912 | return false; |
3913 | } |
3914 | if (variable->exported) { |
3915 | push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)" , p_annotation->name), p_annotation); |
3916 | return false; |
3917 | } |
3918 | |
3919 | variable->exported = true; |
3920 | |
3921 | variable->export_info.type = t_type; |
3922 | variable->export_info.hint = t_hint; |
3923 | |
3924 | String hint_string; |
3925 | for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { |
3926 | String arg_string = String(p_annotation->resolved_arguments[i]); |
3927 | |
3928 | if (p_annotation->name != SNAME("@export_placeholder" )) { |
3929 | if (arg_string.is_empty()) { |
3930 | push_error(vformat(R"(Argument %d of annotation "%s" is empty.)" , i + 1, p_annotation->name), p_annotation->arguments[i]); |
3931 | return false; |
3932 | } |
3933 | if (arg_string.contains("," )) { |
3934 | push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)" , i + 1, p_annotation->name), p_annotation->arguments[i]); |
3935 | return false; |
3936 | } |
3937 | } |
3938 | |
3939 | // WARNING: Do not merge with the previous `if` because there `!=`, not `==`! |
3940 | if (p_annotation->name == SNAME("@export_flags" )) { |
3941 | const int64_t max_flags = 32; |
3942 | Vector<String> t = arg_string.split(":" , true, 1); |
3943 | if (t[0].is_empty()) { |
3944 | push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag name.)" , i + 1), p_annotation->arguments[i]); |
3945 | return false; |
3946 | } |
3947 | if (t.size() == 2) { |
3948 | if (t[1].is_empty()) { |
3949 | push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag value.)" , i + 1), p_annotation->arguments[i]); |
3950 | return false; |
3951 | } |
3952 | if (!t[1].is_valid_int()) { |
3953 | push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be a valid integer.)" , i + 1), p_annotation->arguments[i]); |
3954 | return false; |
3955 | } |
3956 | int64_t value = t[1].to_int(); |
3957 | if (value < 1 || value >= (1LL << max_flags)) { |
3958 | push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be at least 1 and at most 2 ** %d - 1.)" , i + 1, max_flags), p_annotation->arguments[i]); |
3959 | return false; |
3960 | } |
3961 | } else if (i >= max_flags) { |
3962 | push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Starting from argument %d, the flag value must be specified explicitly.)" , i + 1, max_flags + 1), p_annotation->arguments[i]); |
3963 | return false; |
3964 | } |
3965 | } else if (p_annotation->name == SNAME("@export_node_path" )) { |
3966 | String native_class = arg_string; |
3967 | if (ScriptServer::is_global_class(arg_string)) { |
3968 | native_class = ScriptServer::get_global_class_native_base(arg_string); |
3969 | } |
3970 | if (!ClassDB::class_exists(native_class)) { |
3971 | push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" was not found in the global scope.)" , i + 1, arg_string), p_annotation->arguments[i]); |
3972 | return false; |
3973 | } else if (!ClassDB::is_parent_class(native_class, SNAME("Node" ))) { |
3974 | push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" does not inherit "Node".)" , i + 1, arg_string), p_annotation->arguments[i]); |
3975 | return false; |
3976 | } |
3977 | } |
3978 | |
3979 | if (i > 0) { |
3980 | hint_string += "," ; |
3981 | } |
3982 | hint_string += arg_string; |
3983 | } |
3984 | |
3985 | variable->export_info.hint_string = hint_string; |
3986 | |
3987 | // This is called after the analyzer is done finding the type, so this should be set here. |
3988 | DataType export_type = variable->get_datatype(); |
3989 | |
3990 | if (p_annotation->name == SNAME("@export_range" )) { |
3991 | if (export_type.builtin_type == Variant::INT) { |
3992 | variable->export_info.type = Variant::INT; |
3993 | } |
3994 | } else if (p_annotation->name == SNAME("@export_multiline" )) { |
3995 | if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) { |
3996 | DataType inner_type = export_type.get_container_element_type(); |
3997 | if (inner_type.builtin_type != Variant::STRING) { |
3998 | push_error(vformat(R"("%s" annotation on arrays requires a string type but type "%s" was given instead.)" , p_annotation->name.operator String(), inner_type.to_string()), variable); |
3999 | return false; |
4000 | } |
4001 | |
4002 | String hint_prefix = itos(inner_type.builtin_type) + "/" + itos(variable->export_info.hint); |
4003 | variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; |
4004 | variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; |
4005 | variable->export_info.type = Variant::ARRAY; |
4006 | |
4007 | return true; |
4008 | } else if (export_type.builtin_type == Variant::DICTIONARY) { |
4009 | variable->export_info.type = Variant::DICTIONARY; |
4010 | |
4011 | return true; |
4012 | } else if (export_type.builtin_type == Variant::PACKED_STRING_ARRAY) { |
4013 | String hint_prefix = itos(Variant::STRING) + "/" + itos(variable->export_info.hint); |
4014 | variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; |
4015 | variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; |
4016 | variable->export_info.type = Variant::PACKED_STRING_ARRAY; |
4017 | |
4018 | return true; |
4019 | } |
4020 | } |
4021 | |
4022 | // WARNING: Do not merge with the previous `else if`! Otherwise `else` (default variable type check) |
4023 | // will not work for the above annotations. `@export` and `@export_enum` validate the type separately. |
4024 | if (p_annotation->name == SNAME("@export" )) { |
4025 | if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { |
4026 | push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)" , p_annotation); |
4027 | return false; |
4028 | } |
4029 | |
4030 | bool is_array = false; |
4031 | |
4032 | if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) { |
4033 | export_type = export_type.get_container_element_type(); // Use inner type for. |
4034 | is_array = true; |
4035 | } |
4036 | |
4037 | if (export_type.is_variant() || export_type.has_no_type()) { |
4038 | push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)" , p_annotation); |
4039 | return false; |
4040 | } |
4041 | |
4042 | switch (export_type.kind) { |
4043 | case GDScriptParser::DataType::BUILTIN: |
4044 | variable->export_info.type = export_type.builtin_type; |
4045 | variable->export_info.hint = PROPERTY_HINT_NONE; |
4046 | variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); |
4047 | break; |
4048 | case GDScriptParser::DataType::NATIVE: |
4049 | if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource" ))) { |
4050 | variable->export_info.type = Variant::OBJECT; |
4051 | variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; |
4052 | variable->export_info.hint_string = export_type.native_type; |
4053 | } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node" ))) { |
4054 | variable->export_info.type = Variant::OBJECT; |
4055 | variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; |
4056 | variable->export_info.hint_string = export_type.native_type; |
4057 | } else { |
4058 | push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)" , variable); |
4059 | return false; |
4060 | } |
4061 | break; |
4062 | case GDScriptParser::DataType::CLASS: |
4063 | if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource" ))) { |
4064 | variable->export_info.type = Variant::OBJECT; |
4065 | variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; |
4066 | variable->export_info.hint_string = export_type.to_string(); |
4067 | } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node" ))) { |
4068 | variable->export_info.type = Variant::OBJECT; |
4069 | variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; |
4070 | variable->export_info.hint_string = export_type.to_string(); |
4071 | } else { |
4072 | push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)" , variable); |
4073 | return false; |
4074 | } |
4075 | |
4076 | break; |
4077 | case GDScriptParser::DataType::SCRIPT: { |
4078 | StringName class_name; |
4079 | StringName native_base; |
4080 | if (export_type.script_type.is_valid()) { |
4081 | class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path()); |
4082 | native_base = export_type.script_type->get_instance_base_type(); |
4083 | } |
4084 | if (class_name == StringName()) { |
4085 | Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script" )); |
4086 | if (script.is_valid()) { |
4087 | class_name = script->get_language()->get_global_class_name(export_type.script_path); |
4088 | native_base = script->get_instance_base_type(); |
4089 | } |
4090 | } |
4091 | if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource" ))) { |
4092 | variable->export_info.type = Variant::OBJECT; |
4093 | variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; |
4094 | variable->export_info.hint_string = class_name; |
4095 | } |
4096 | } break; |
4097 | case GDScriptParser::DataType::ENUM: { |
4098 | variable->export_info.type = Variant::INT; |
4099 | variable->export_info.hint = PROPERTY_HINT_ENUM; |
4100 | |
4101 | String enum_hint_string; |
4102 | bool first = true; |
4103 | for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { |
4104 | if (!first) { |
4105 | enum_hint_string += "," ; |
4106 | } else { |
4107 | first = false; |
4108 | } |
4109 | enum_hint_string += E.key.operator String().capitalize().xml_escape(); |
4110 | enum_hint_string += ":" ; |
4111 | enum_hint_string += String::num_int64(E.value).xml_escape(); |
4112 | } |
4113 | |
4114 | variable->export_info.hint_string = enum_hint_string; |
4115 | variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; |
4116 | variable->export_info.class_name = String(export_type.native_type).replace("::" , "." ); |
4117 | } break; |
4118 | default: |
4119 | push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)" , variable); |
4120 | break; |
4121 | } |
4122 | |
4123 | if (is_array) { |
4124 | String hint_prefix = itos(variable->export_info.type); |
4125 | if (variable->export_info.hint) { |
4126 | hint_prefix += "/" + itos(variable->export_info.hint); |
4127 | } |
4128 | variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; |
4129 | variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; |
4130 | variable->export_info.type = Variant::ARRAY; |
4131 | } |
4132 | } else if (p_annotation->name == SNAME("@export_enum" )) { |
4133 | Variant::Type enum_type = Variant::INT; |
4134 | |
4135 | if (export_type.kind == DataType::BUILTIN && export_type.builtin_type == Variant::STRING) { |
4136 | enum_type = Variant::STRING; |
4137 | } else if (export_type.is_variant() && variable->initializer != nullptr) { |
4138 | DataType initializer_type = variable->initializer->get_datatype(); |
4139 | if (initializer_type.kind == DataType::BUILTIN && initializer_type.builtin_type == Variant::STRING) { |
4140 | enum_type = Variant::STRING; |
4141 | } |
4142 | } |
4143 | |
4144 | variable->export_info.type = enum_type; |
4145 | |
4146 | if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != enum_type)) { |
4147 | push_error(vformat(R"("@export_enum" annotation requires a variable of type "int" or "String" but type "%s" was given instead.)" , export_type.to_string()), variable); |
4148 | return false; |
4149 | } |
4150 | } else { |
4151 | // Validate variable type with export. |
4152 | if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) { |
4153 | // Allow float/int conversion. |
4154 | if ((t_type != Variant::FLOAT || export_type.builtin_type != Variant::INT) && (t_type != Variant::INT || export_type.builtin_type != Variant::FLOAT)) { |
4155 | push_error(vformat(R"("%s" annotation requires a variable of type "%s" but type "%s" was given instead.)" , p_annotation->name.operator String(), Variant::get_type_name(t_type), export_type.to_string()), variable); |
4156 | return false; |
4157 | } |
4158 | } |
4159 | } |
4160 | |
4161 | return true; |
4162 | } |
4163 | |
4164 | template <PropertyUsageFlags t_usage> |
4165 | bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) { |
4166 | AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); |
4167 | |
4168 | if (annotation->resolved_arguments.is_empty()) { |
4169 | return false; |
4170 | } |
4171 | |
4172 | annotation->export_info.name = annotation->resolved_arguments[0]; |
4173 | |
4174 | switch (t_usage) { |
4175 | case PROPERTY_USAGE_CATEGORY: { |
4176 | annotation->export_info.usage = t_usage; |
4177 | } break; |
4178 | |
4179 | case PROPERTY_USAGE_GROUP: { |
4180 | annotation->export_info.usage = t_usage; |
4181 | if (annotation->resolved_arguments.size() == 2) { |
4182 | annotation->export_info.hint_string = annotation->resolved_arguments[1]; |
4183 | } |
4184 | } break; |
4185 | |
4186 | case PROPERTY_USAGE_SUBGROUP: { |
4187 | annotation->export_info.usage = t_usage; |
4188 | if (annotation->resolved_arguments.size() == 2) { |
4189 | annotation->export_info.hint_string = annotation->resolved_arguments[1]; |
4190 | } |
4191 | } break; |
4192 | } |
4193 | |
4194 | return true; |
4195 | } |
4196 | |
4197 | bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) { |
4198 | #ifdef DEBUG_ENABLED |
4199 | bool has_error = false; |
4200 | for (const Variant &warning_name : p_annotation->resolved_arguments) { |
4201 | GDScriptWarning::Code warning = GDScriptWarning::get_code_from_name(String(warning_name).to_upper()); |
4202 | if (warning == GDScriptWarning::WARNING_MAX) { |
4203 | push_error(vformat(R"(Invalid warning name: "%s".)" , warning_name), p_annotation); |
4204 | has_error = true; |
4205 | } else { |
4206 | p_node->ignored_warnings.push_back(warning); |
4207 | } |
4208 | } |
4209 | |
4210 | return !has_error; |
4211 | |
4212 | #else // ! DEBUG_ENABLED |
4213 | // Only available in debug builds. |
4214 | return true; |
4215 | #endif // DEBUG_ENABLED |
4216 | } |
4217 | |
4218 | bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_node) { |
4219 | ERR_FAIL_COND_V_MSG(p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)" , p_annotation->name)); |
4220 | |
4221 | FunctionNode *function = static_cast<FunctionNode *>(p_node); |
4222 | if (function->rpc_config.get_type() != Variant::NIL) { |
4223 | push_error(R"(RPC annotations can only be used once per function.)" , p_annotation); |
4224 | return false; |
4225 | } |
4226 | |
4227 | Dictionary rpc_config; |
4228 | rpc_config["rpc_mode" ] = MultiplayerAPI::RPC_MODE_AUTHORITY; |
4229 | if (!p_annotation->resolved_arguments.is_empty()) { |
4230 | unsigned char locality_args = 0; |
4231 | unsigned char permission_args = 0; |
4232 | unsigned char transfer_mode_args = 0; |
4233 | |
4234 | for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { |
4235 | if (i == 3) { |
4236 | rpc_config["channel" ] = p_annotation->resolved_arguments[i].operator int(); |
4237 | continue; |
4238 | } |
4239 | |
4240 | String arg = p_annotation->resolved_arguments[i].operator String(); |
4241 | if (arg == "call_local" ) { |
4242 | locality_args++; |
4243 | rpc_config["call_local" ] = true; |
4244 | } else if (arg == "call_remote" ) { |
4245 | locality_args++; |
4246 | rpc_config["call_local" ] = false; |
4247 | } else if (arg == "any_peer" ) { |
4248 | permission_args++; |
4249 | rpc_config["rpc_mode" ] = MultiplayerAPI::RPC_MODE_ANY_PEER; |
4250 | } else if (arg == "authority" ) { |
4251 | permission_args++; |
4252 | rpc_config["rpc_mode" ] = MultiplayerAPI::RPC_MODE_AUTHORITY; |
4253 | } else if (arg == "reliable" ) { |
4254 | transfer_mode_args++; |
4255 | rpc_config["transfer_mode" ] = MultiplayerPeer::TRANSFER_MODE_RELIABLE; |
4256 | } else if (arg == "unreliable" ) { |
4257 | transfer_mode_args++; |
4258 | rpc_config["transfer_mode" ] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE; |
4259 | } else if (arg == "unreliable_ordered" ) { |
4260 | transfer_mode_args++; |
4261 | rpc_config["transfer_mode" ] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED; |
4262 | } else { |
4263 | push_error(R"(Invalid RPC argument. Must be one of: "call_local"/"call_remote" (local calls), "any_peer"/"authority" (permission), "reliable"/"unreliable"/"unreliable_ordered" (transfer mode).)" , p_annotation); |
4264 | } |
4265 | } |
4266 | |
4267 | if (locality_args > 1) { |
4268 | push_error(R"(Invalid RPC config. The locality ("call_local"/"call_remote") must be specified no more than once.)" , p_annotation); |
4269 | } else if (permission_args > 1) { |
4270 | push_error(R"(Invalid RPC config. The permission ("any_peer"/"authority") must be specified no more than once.)" , p_annotation); |
4271 | } else if (transfer_mode_args > 1) { |
4272 | push_error(R"(Invalid RPC config. The transfer mode ("reliable"/"unreliable"/"unreliable_ordered") must be specified no more than once.)" , p_annotation); |
4273 | } |
4274 | } |
4275 | function->rpc_config = rpc_config; |
4276 | return true; |
4277 | } |
4278 | |
4279 | bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) { |
4280 | ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)" , p_annotation->name)); |
4281 | ClassNode *p_class = static_cast<ClassNode *>(p_target); |
4282 | if (p_class->annotated_static_unload) { |
4283 | push_error(vformat(R"("%s" annotation can only be used once per script.)" , p_annotation->name), p_annotation); |
4284 | return false; |
4285 | } |
4286 | p_class->annotated_static_unload = true; |
4287 | return true; |
4288 | } |
4289 | |
4290 | GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const { |
4291 | switch (type) { |
4292 | case CONSTANT: |
4293 | return constant->get_datatype(); |
4294 | case VARIABLE: |
4295 | return variable->get_datatype(); |
4296 | case PARAMETER: |
4297 | return parameter->get_datatype(); |
4298 | case FOR_VARIABLE: |
4299 | case PATTERN_BIND: |
4300 | return bind->get_datatype(); |
4301 | case UNDEFINED: |
4302 | return DataType(); |
4303 | } |
4304 | return DataType(); |
4305 | } |
4306 | |
4307 | String GDScriptParser::SuiteNode::Local::get_name() const { |
4308 | switch (type) { |
4309 | case SuiteNode::Local::PARAMETER: |
4310 | return "parameter" ; |
4311 | case SuiteNode::Local::CONSTANT: |
4312 | return "constant" ; |
4313 | case SuiteNode::Local::VARIABLE: |
4314 | return "variable" ; |
4315 | case SuiteNode::Local::FOR_VARIABLE: |
4316 | return "for loop iterator" ; |
4317 | case SuiteNode::Local::PATTERN_BIND: |
4318 | return "pattern bind" ; |
4319 | case SuiteNode::Local::UNDEFINED: |
4320 | return "<undefined>" ; |
4321 | default: |
4322 | return String(); |
4323 | } |
4324 | } |
4325 | |
4326 | String GDScriptParser::DataType::to_string() const { |
4327 | switch (kind) { |
4328 | case VARIANT: |
4329 | return "Variant" ; |
4330 | case BUILTIN: |
4331 | if (builtin_type == Variant::NIL) { |
4332 | return "null" ; |
4333 | } |
4334 | if (builtin_type == Variant::ARRAY && has_container_element_type()) { |
4335 | return vformat("Array[%s]" , container_element_type->to_string()); |
4336 | } |
4337 | return Variant::get_type_name(builtin_type); |
4338 | case NATIVE: |
4339 | if (is_meta_type) { |
4340 | return GDScriptNativeClass::get_class_static(); |
4341 | } |
4342 | return native_type.operator String(); |
4343 | case CLASS: |
4344 | if (class_type->identifier != nullptr) { |
4345 | return class_type->identifier->name.operator String(); |
4346 | } |
4347 | return class_type->fqcn; |
4348 | case SCRIPT: { |
4349 | if (is_meta_type) { |
4350 | return script_type != nullptr ? script_type->get_class_name().operator String() : "" ; |
4351 | } |
4352 | String name = script_type != nullptr ? script_type->get_name() : "" ; |
4353 | if (!name.is_empty()) { |
4354 | return name; |
4355 | } |
4356 | name = script_path; |
4357 | if (!name.is_empty()) { |
4358 | return name; |
4359 | } |
4360 | return native_type.operator String(); |
4361 | } |
4362 | case ENUM: { |
4363 | // native_type contains either the native class defining the enum |
4364 | // or the fully qualified class name of the script defining the enum |
4365 | return String(native_type).get_file(); // Remove path, keep filename |
4366 | } |
4367 | case RESOLVING: |
4368 | case UNRESOLVED: |
4369 | return "<unresolved type>" ; |
4370 | } |
4371 | |
4372 | ERR_FAIL_V_MSG("<unresolved type>" , "Kind set outside the enum range." ); |
4373 | } |
4374 | |
4375 | PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) const { |
4376 | PropertyInfo result; |
4377 | result.name = p_name; |
4378 | result.usage = PROPERTY_USAGE_NONE; |
4379 | |
4380 | if (!is_hard_type()) { |
4381 | result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; |
4382 | return result; |
4383 | } |
4384 | |
4385 | switch (kind) { |
4386 | case BUILTIN: |
4387 | result.type = builtin_type; |
4388 | if (builtin_type == Variant::ARRAY && has_container_element_type()) { |
4389 | const DataType *elem_type = container_element_type; |
4390 | switch (elem_type->kind) { |
4391 | case BUILTIN: |
4392 | result.hint = PROPERTY_HINT_ARRAY_TYPE; |
4393 | result.hint_string = Variant::get_type_name(elem_type->builtin_type); |
4394 | break; |
4395 | case NATIVE: |
4396 | result.hint = PROPERTY_HINT_ARRAY_TYPE; |
4397 | result.hint_string = elem_type->native_type; |
4398 | break; |
4399 | case SCRIPT: |
4400 | result.hint = PROPERTY_HINT_ARRAY_TYPE; |
4401 | if (elem_type->script_type.is_valid() && elem_type->script_type->get_global_name() != StringName()) { |
4402 | result.hint_string = elem_type->script_type->get_global_name(); |
4403 | } else { |
4404 | result.hint_string = elem_type->native_type; |
4405 | } |
4406 | break; |
4407 | case CLASS: |
4408 | result.hint = PROPERTY_HINT_ARRAY_TYPE; |
4409 | if (elem_type->class_type != nullptr && elem_type->class_type->get_global_name() != StringName()) { |
4410 | result.hint_string = elem_type->class_type->get_global_name(); |
4411 | } else { |
4412 | result.hint_string = elem_type->native_type; |
4413 | } |
4414 | break; |
4415 | case ENUM: |
4416 | result.hint = PROPERTY_HINT_ARRAY_TYPE; |
4417 | result.hint_string = String(elem_type->native_type).replace("::" , "." ); |
4418 | break; |
4419 | case VARIANT: |
4420 | case RESOLVING: |
4421 | case UNRESOLVED: |
4422 | break; |
4423 | } |
4424 | } |
4425 | break; |
4426 | case NATIVE: |
4427 | result.type = Variant::OBJECT; |
4428 | if (is_meta_type) { |
4429 | result.class_name = GDScriptNativeClass::get_class_static(); |
4430 | } else { |
4431 | result.class_name = native_type; |
4432 | } |
4433 | break; |
4434 | case SCRIPT: |
4435 | result.type = Variant::OBJECT; |
4436 | if (is_meta_type) { |
4437 | result.class_name = script_type.is_valid() ? script_type->get_class() : Script::get_class_static(); |
4438 | } else if (script_type.is_valid() && script_type->get_global_name() != StringName()) { |
4439 | result.class_name = script_type->get_global_name(); |
4440 | } else { |
4441 | result.class_name = native_type; |
4442 | } |
4443 | break; |
4444 | case CLASS: |
4445 | result.type = Variant::OBJECT; |
4446 | if (is_meta_type) { |
4447 | result.class_name = GDScript::get_class_static(); |
4448 | } else if (class_type != nullptr && class_type->get_global_name() != StringName()) { |
4449 | result.class_name = class_type->get_global_name(); |
4450 | } else { |
4451 | result.class_name = native_type; |
4452 | } |
4453 | break; |
4454 | case ENUM: |
4455 | if (is_meta_type) { |
4456 | result.type = Variant::DICTIONARY; |
4457 | } else { |
4458 | result.type = Variant::INT; |
4459 | result.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; |
4460 | result.class_name = String(native_type).replace("::" , "." ); |
4461 | } |
4462 | break; |
4463 | case VARIANT: |
4464 | case RESOLVING: |
4465 | case UNRESOLVED: |
4466 | result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; |
4467 | break; |
4468 | } |
4469 | |
4470 | return result; |
4471 | } |
4472 | |
4473 | static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) { |
4474 | switch (p_type) { |
4475 | case Variant::PACKED_BYTE_ARRAY: |
4476 | case Variant::PACKED_INT32_ARRAY: |
4477 | case Variant::PACKED_INT64_ARRAY: |
4478 | return Variant::INT; |
4479 | case Variant::PACKED_FLOAT32_ARRAY: |
4480 | case Variant::PACKED_FLOAT64_ARRAY: |
4481 | return Variant::FLOAT; |
4482 | case Variant::PACKED_STRING_ARRAY: |
4483 | return Variant::STRING; |
4484 | case Variant::PACKED_VECTOR2_ARRAY: |
4485 | return Variant::VECTOR2; |
4486 | case Variant::PACKED_VECTOR3_ARRAY: |
4487 | return Variant::VECTOR3; |
4488 | case Variant::PACKED_COLOR_ARRAY: |
4489 | return Variant::COLOR; |
4490 | default: |
4491 | return Variant::NIL; |
4492 | } |
4493 | } |
4494 | |
4495 | bool GDScriptParser::DataType::is_typed_container_type() const { |
4496 | return kind == GDScriptParser::DataType::BUILTIN && _variant_type_to_typed_array_element_type(builtin_type) != Variant::NIL; |
4497 | } |
4498 | |
4499 | GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() const { |
4500 | GDScriptParser::DataType type; |
4501 | type.kind = GDScriptParser::DataType::BUILTIN; |
4502 | type.builtin_type = _variant_type_to_typed_array_element_type(builtin_type); |
4503 | return type; |
4504 | } |
4505 | |
4506 | void GDScriptParser::complete_extents(Node *p_node) { |
4507 | while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) { |
4508 | ERR_PRINT("Parser bug: Mismatch in extents tracking stack." ); |
4509 | nodes_in_progress.pop_back(); |
4510 | } |
4511 | if (nodes_in_progress.is_empty()) { |
4512 | ERR_PRINT("Parser bug: Extents tracking stack is empty." ); |
4513 | } else { |
4514 | nodes_in_progress.pop_back(); |
4515 | } |
4516 | } |
4517 | |
4518 | void GDScriptParser::update_extents(Node *p_node) { |
4519 | p_node->end_line = previous.end_line; |
4520 | p_node->end_column = previous.end_column; |
4521 | p_node->leftmost_column = MIN(p_node->leftmost_column, previous.leftmost_column); |
4522 | p_node->rightmost_column = MAX(p_node->rightmost_column, previous.rightmost_column); |
4523 | } |
4524 | |
4525 | void GDScriptParser::reset_extents(Node *p_node, GDScriptTokenizer::Token p_token) { |
4526 | p_node->start_line = p_token.start_line; |
4527 | p_node->end_line = p_token.end_line; |
4528 | p_node->start_column = p_token.start_column; |
4529 | p_node->end_column = p_token.end_column; |
4530 | p_node->leftmost_column = p_token.leftmost_column; |
4531 | p_node->rightmost_column = p_token.rightmost_column; |
4532 | } |
4533 | |
4534 | void GDScriptParser::reset_extents(Node *p_node, Node *p_from) { |
4535 | if (p_from == nullptr) { |
4536 | return; |
4537 | } |
4538 | p_node->start_line = p_from->start_line; |
4539 | p_node->end_line = p_from->end_line; |
4540 | p_node->start_column = p_from->start_column; |
4541 | p_node->end_column = p_from->end_column; |
4542 | p_node->leftmost_column = p_from->leftmost_column; |
4543 | p_node->rightmost_column = p_from->rightmost_column; |
4544 | } |
4545 | |
4546 | /*---------- PRETTY PRINT FOR DEBUG ----------*/ |
4547 | |
4548 | #ifdef DEBUG_ENABLED |
4549 | |
4550 | void GDScriptParser::TreePrinter::increase_indent() { |
4551 | indent_level++; |
4552 | indent = "" ; |
4553 | for (int i = 0; i < indent_level * 4; i++) { |
4554 | if (i % 4 == 0) { |
4555 | indent += "|" ; |
4556 | } else { |
4557 | indent += " " ; |
4558 | } |
4559 | } |
4560 | } |
4561 | |
4562 | void GDScriptParser::TreePrinter::decrease_indent() { |
4563 | indent_level--; |
4564 | indent = "" ; |
4565 | for (int i = 0; i < indent_level * 4; i++) { |
4566 | if (i % 4 == 0) { |
4567 | indent += "|" ; |
4568 | } else { |
4569 | indent += " " ; |
4570 | } |
4571 | } |
4572 | } |
4573 | |
4574 | void GDScriptParser::TreePrinter::push_line(const String &p_line) { |
4575 | if (!p_line.is_empty()) { |
4576 | push_text(p_line); |
4577 | } |
4578 | printed += "\n" ; |
4579 | pending_indent = true; |
4580 | } |
4581 | |
4582 | void GDScriptParser::TreePrinter::push_text(const String &p_text) { |
4583 | if (pending_indent) { |
4584 | printed += indent; |
4585 | pending_indent = false; |
4586 | } |
4587 | printed += p_text; |
4588 | } |
4589 | |
4590 | void GDScriptParser::TreePrinter::print_annotation(const AnnotationNode *p_annotation) { |
4591 | push_text(p_annotation->name); |
4592 | push_text(" (" ); |
4593 | for (int i = 0; i < p_annotation->arguments.size(); i++) { |
4594 | if (i > 0) { |
4595 | push_text(" , " ); |
4596 | } |
4597 | print_expression(p_annotation->arguments[i]); |
4598 | } |
4599 | push_line(")" ); |
4600 | } |
4601 | |
4602 | void GDScriptParser::TreePrinter::print_array(ArrayNode *p_array) { |
4603 | push_text("[ " ); |
4604 | for (int i = 0; i < p_array->elements.size(); i++) { |
4605 | if (i > 0) { |
4606 | push_text(" , " ); |
4607 | } |
4608 | print_expression(p_array->elements[i]); |
4609 | } |
4610 | push_text(" ]" ); |
4611 | } |
4612 | |
4613 | void GDScriptParser::TreePrinter::print_assert(AssertNode *p_assert) { |
4614 | push_text("Assert ( " ); |
4615 | print_expression(p_assert->condition); |
4616 | push_line(" )" ); |
4617 | } |
4618 | |
4619 | void GDScriptParser::TreePrinter::print_assignment(AssignmentNode *p_assignment) { |
4620 | switch (p_assignment->assignee->type) { |
4621 | case Node::IDENTIFIER: |
4622 | print_identifier(static_cast<IdentifierNode *>(p_assignment->assignee)); |
4623 | break; |
4624 | case Node::SUBSCRIPT: |
4625 | print_subscript(static_cast<SubscriptNode *>(p_assignment->assignee)); |
4626 | break; |
4627 | default: |
4628 | break; // Unreachable. |
4629 | } |
4630 | |
4631 | push_text(" " ); |
4632 | switch (p_assignment->operation) { |
4633 | case AssignmentNode::OP_ADDITION: |
4634 | push_text("+" ); |
4635 | break; |
4636 | case AssignmentNode::OP_SUBTRACTION: |
4637 | push_text("-" ); |
4638 | break; |
4639 | case AssignmentNode::OP_MULTIPLICATION: |
4640 | push_text("*" ); |
4641 | break; |
4642 | case AssignmentNode::OP_DIVISION: |
4643 | push_text("/" ); |
4644 | break; |
4645 | case AssignmentNode::OP_MODULO: |
4646 | push_text("%" ); |
4647 | break; |
4648 | case AssignmentNode::OP_POWER: |
4649 | push_text("**" ); |
4650 | break; |
4651 | case AssignmentNode::OP_BIT_SHIFT_LEFT: |
4652 | push_text("<<" ); |
4653 | break; |
4654 | case AssignmentNode::OP_BIT_SHIFT_RIGHT: |
4655 | push_text(">>" ); |
4656 | break; |
4657 | case AssignmentNode::OP_BIT_AND: |
4658 | push_text("&" ); |
4659 | break; |
4660 | case AssignmentNode::OP_BIT_OR: |
4661 | push_text("|" ); |
4662 | break; |
4663 | case AssignmentNode::OP_BIT_XOR: |
4664 | push_text("^" ); |
4665 | break; |
4666 | case AssignmentNode::OP_NONE: |
4667 | break; |
4668 | } |
4669 | push_text("= " ); |
4670 | print_expression(p_assignment->assigned_value); |
4671 | push_line(); |
4672 | } |
4673 | |
4674 | void GDScriptParser::TreePrinter::print_await(AwaitNode *p_await) { |
4675 | push_text("Await " ); |
4676 | print_expression(p_await->to_await); |
4677 | } |
4678 | |
4679 | void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) { |
4680 | // Surround in parenthesis for disambiguation. |
4681 | push_text("(" ); |
4682 | print_expression(p_binary_op->left_operand); |
4683 | switch (p_binary_op->operation) { |
4684 | case BinaryOpNode::OP_ADDITION: |
4685 | push_text(" + " ); |
4686 | break; |
4687 | case BinaryOpNode::OP_SUBTRACTION: |
4688 | push_text(" - " ); |
4689 | break; |
4690 | case BinaryOpNode::OP_MULTIPLICATION: |
4691 | push_text(" * " ); |
4692 | break; |
4693 | case BinaryOpNode::OP_DIVISION: |
4694 | push_text(" / " ); |
4695 | break; |
4696 | case BinaryOpNode::OP_MODULO: |
4697 | push_text(" % " ); |
4698 | break; |
4699 | case BinaryOpNode::OP_POWER: |
4700 | push_text(" ** " ); |
4701 | break; |
4702 | case BinaryOpNode::OP_BIT_LEFT_SHIFT: |
4703 | push_text(" << " ); |
4704 | break; |
4705 | case BinaryOpNode::OP_BIT_RIGHT_SHIFT: |
4706 | push_text(" >> " ); |
4707 | break; |
4708 | case BinaryOpNode::OP_BIT_AND: |
4709 | push_text(" & " ); |
4710 | break; |
4711 | case BinaryOpNode::OP_BIT_OR: |
4712 | push_text(" | " ); |
4713 | break; |
4714 | case BinaryOpNode::OP_BIT_XOR: |
4715 | push_text(" ^ " ); |
4716 | break; |
4717 | case BinaryOpNode::OP_LOGIC_AND: |
4718 | push_text(" AND " ); |
4719 | break; |
4720 | case BinaryOpNode::OP_LOGIC_OR: |
4721 | push_text(" OR " ); |
4722 | break; |
4723 | case BinaryOpNode::OP_CONTENT_TEST: |
4724 | push_text(" IN " ); |
4725 | break; |
4726 | case BinaryOpNode::OP_COMP_EQUAL: |
4727 | push_text(" == " ); |
4728 | break; |
4729 | case BinaryOpNode::OP_COMP_NOT_EQUAL: |
4730 | push_text(" != " ); |
4731 | break; |
4732 | case BinaryOpNode::OP_COMP_LESS: |
4733 | push_text(" < " ); |
4734 | break; |
4735 | case BinaryOpNode::OP_COMP_LESS_EQUAL: |
4736 | push_text(" <= " ); |
4737 | break; |
4738 | case BinaryOpNode::OP_COMP_GREATER: |
4739 | push_text(" > " ); |
4740 | break; |
4741 | case BinaryOpNode::OP_COMP_GREATER_EQUAL: |
4742 | push_text(" >= " ); |
4743 | break; |
4744 | } |
4745 | print_expression(p_binary_op->right_operand); |
4746 | // Surround in parenthesis for disambiguation. |
4747 | push_text(")" ); |
4748 | } |
4749 | |
4750 | void GDScriptParser::TreePrinter::print_call(CallNode *p_call) { |
4751 | if (p_call->is_super) { |
4752 | push_text("super" ); |
4753 | if (p_call->callee != nullptr) { |
4754 | push_text("." ); |
4755 | print_expression(p_call->callee); |
4756 | } |
4757 | } else { |
4758 | print_expression(p_call->callee); |
4759 | } |
4760 | push_text("( " ); |
4761 | for (int i = 0; i < p_call->arguments.size(); i++) { |
4762 | if (i > 0) { |
4763 | push_text(" , " ); |
4764 | } |
4765 | print_expression(p_call->arguments[i]); |
4766 | } |
4767 | push_text(" )" ); |
4768 | } |
4769 | |
4770 | void GDScriptParser::TreePrinter::print_cast(CastNode *p_cast) { |
4771 | print_expression(p_cast->operand); |
4772 | push_text(" AS " ); |
4773 | print_type(p_cast->cast_type); |
4774 | } |
4775 | |
4776 | void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) { |
4777 | push_text("Class " ); |
4778 | if (p_class->identifier == nullptr) { |
4779 | push_text("<unnamed>" ); |
4780 | } else { |
4781 | print_identifier(p_class->identifier); |
4782 | } |
4783 | |
4784 | if (p_class->extends_used) { |
4785 | bool first = true; |
4786 | push_text(" Extends " ); |
4787 | if (!p_class->extends_path.is_empty()) { |
4788 | push_text(vformat(R"("%s")" , p_class->extends_path)); |
4789 | first = false; |
4790 | } |
4791 | for (int i = 0; i < p_class->extends.size(); i++) { |
4792 | if (!first) { |
4793 | push_text("." ); |
4794 | } else { |
4795 | first = false; |
4796 | } |
4797 | push_text(p_class->extends[i]->name); |
4798 | } |
4799 | } |
4800 | |
4801 | push_line(" :" ); |
4802 | |
4803 | increase_indent(); |
4804 | |
4805 | for (int i = 0; i < p_class->members.size(); i++) { |
4806 | const ClassNode::Member &m = p_class->members[i]; |
4807 | |
4808 | switch (m.type) { |
4809 | case ClassNode::Member::CLASS: |
4810 | print_class(m.m_class); |
4811 | break; |
4812 | case ClassNode::Member::VARIABLE: |
4813 | print_variable(m.variable); |
4814 | break; |
4815 | case ClassNode::Member::CONSTANT: |
4816 | print_constant(m.constant); |
4817 | break; |
4818 | case ClassNode::Member::SIGNAL: |
4819 | print_signal(m.signal); |
4820 | break; |
4821 | case ClassNode::Member::FUNCTION: |
4822 | print_function(m.function); |
4823 | break; |
4824 | case ClassNode::Member::ENUM: |
4825 | print_enum(m.m_enum); |
4826 | break; |
4827 | case ClassNode::Member::ENUM_VALUE: |
4828 | break; // Nothing. Will be printed by enum. |
4829 | case ClassNode::Member::GROUP: |
4830 | break; // Nothing. Groups are only used by inspector. |
4831 | case ClassNode::Member::UNDEFINED: |
4832 | push_line("<unknown member>" ); |
4833 | break; |
4834 | } |
4835 | } |
4836 | |
4837 | decrease_indent(); |
4838 | } |
4839 | |
4840 | void GDScriptParser::TreePrinter::print_constant(ConstantNode *p_constant) { |
4841 | push_text("Constant " ); |
4842 | print_identifier(p_constant->identifier); |
4843 | |
4844 | increase_indent(); |
4845 | |
4846 | push_line(); |
4847 | push_text("= " ); |
4848 | if (p_constant->initializer == nullptr) { |
4849 | push_text("<missing value>" ); |
4850 | } else { |
4851 | print_expression(p_constant->initializer); |
4852 | } |
4853 | decrease_indent(); |
4854 | push_line(); |
4855 | } |
4856 | |
4857 | void GDScriptParser::TreePrinter::print_dictionary(DictionaryNode *p_dictionary) { |
4858 | push_line("{" ); |
4859 | increase_indent(); |
4860 | for (int i = 0; i < p_dictionary->elements.size(); i++) { |
4861 | print_expression(p_dictionary->elements[i].key); |
4862 | if (p_dictionary->style == DictionaryNode::PYTHON_DICT) { |
4863 | push_text(" : " ); |
4864 | } else { |
4865 | push_text(" = " ); |
4866 | } |
4867 | print_expression(p_dictionary->elements[i].value); |
4868 | push_line(" ," ); |
4869 | } |
4870 | decrease_indent(); |
4871 | push_text("}" ); |
4872 | } |
4873 | |
4874 | void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) { |
4875 | if (p_expression == nullptr) { |
4876 | push_text("<invalid expression>" ); |
4877 | return; |
4878 | } |
4879 | switch (p_expression->type) { |
4880 | case Node::ARRAY: |
4881 | print_array(static_cast<ArrayNode *>(p_expression)); |
4882 | break; |
4883 | case Node::ASSIGNMENT: |
4884 | print_assignment(static_cast<AssignmentNode *>(p_expression)); |
4885 | break; |
4886 | case Node::AWAIT: |
4887 | print_await(static_cast<AwaitNode *>(p_expression)); |
4888 | break; |
4889 | case Node::BINARY_OPERATOR: |
4890 | print_binary_op(static_cast<BinaryOpNode *>(p_expression)); |
4891 | break; |
4892 | case Node::CALL: |
4893 | print_call(static_cast<CallNode *>(p_expression)); |
4894 | break; |
4895 | case Node::CAST: |
4896 | print_cast(static_cast<CastNode *>(p_expression)); |
4897 | break; |
4898 | case Node::DICTIONARY: |
4899 | print_dictionary(static_cast<DictionaryNode *>(p_expression)); |
4900 | break; |
4901 | case Node::GET_NODE: |
4902 | print_get_node(static_cast<GetNodeNode *>(p_expression)); |
4903 | break; |
4904 | case Node::IDENTIFIER: |
4905 | print_identifier(static_cast<IdentifierNode *>(p_expression)); |
4906 | break; |
4907 | case Node::LAMBDA: |
4908 | print_lambda(static_cast<LambdaNode *>(p_expression)); |
4909 | break; |
4910 | case Node::LITERAL: |
4911 | print_literal(static_cast<LiteralNode *>(p_expression)); |
4912 | break; |
4913 | case Node::PRELOAD: |
4914 | print_preload(static_cast<PreloadNode *>(p_expression)); |
4915 | break; |
4916 | case Node::SELF: |
4917 | print_self(static_cast<SelfNode *>(p_expression)); |
4918 | break; |
4919 | case Node::SUBSCRIPT: |
4920 | print_subscript(static_cast<SubscriptNode *>(p_expression)); |
4921 | break; |
4922 | case Node::TERNARY_OPERATOR: |
4923 | print_ternary_op(static_cast<TernaryOpNode *>(p_expression)); |
4924 | break; |
4925 | case Node::TYPE_TEST: |
4926 | print_type_test(static_cast<TypeTestNode *>(p_expression)); |
4927 | break; |
4928 | case Node::UNARY_OPERATOR: |
4929 | print_unary_op(static_cast<UnaryOpNode *>(p_expression)); |
4930 | break; |
4931 | default: |
4932 | push_text(vformat("<unknown expression %d>" , p_expression->type)); |
4933 | break; |
4934 | } |
4935 | } |
4936 | |
4937 | void GDScriptParser::TreePrinter::print_enum(EnumNode *p_enum) { |
4938 | push_text("Enum " ); |
4939 | if (p_enum->identifier != nullptr) { |
4940 | print_identifier(p_enum->identifier); |
4941 | } else { |
4942 | push_text("<unnamed>" ); |
4943 | } |
4944 | |
4945 | push_line(" {" ); |
4946 | increase_indent(); |
4947 | for (int i = 0; i < p_enum->values.size(); i++) { |
4948 | const EnumNode::Value &item = p_enum->values[i]; |
4949 | print_identifier(item.identifier); |
4950 | push_text(" = " ); |
4951 | push_text(itos(item.value)); |
4952 | push_line(" ," ); |
4953 | } |
4954 | decrease_indent(); |
4955 | push_line("}" ); |
4956 | } |
4957 | |
4958 | void GDScriptParser::TreePrinter::print_for(ForNode *p_for) { |
4959 | push_text("For " ); |
4960 | print_identifier(p_for->variable); |
4961 | push_text(" IN " ); |
4962 | print_expression(p_for->list); |
4963 | push_line(" :" ); |
4964 | |
4965 | increase_indent(); |
4966 | |
4967 | print_suite(p_for->loop); |
4968 | |
4969 | decrease_indent(); |
4970 | } |
4971 | |
4972 | void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const String &p_context) { |
4973 | for (const AnnotationNode *E : p_function->annotations) { |
4974 | print_annotation(E); |
4975 | } |
4976 | push_text(p_context); |
4977 | push_text(" " ); |
4978 | if (p_function->identifier) { |
4979 | print_identifier(p_function->identifier); |
4980 | } else { |
4981 | push_text("<anonymous>" ); |
4982 | } |
4983 | push_text("( " ); |
4984 | for (int i = 0; i < p_function->parameters.size(); i++) { |
4985 | if (i > 0) { |
4986 | push_text(" , " ); |
4987 | } |
4988 | print_parameter(p_function->parameters[i]); |
4989 | } |
4990 | push_line(" ) :" ); |
4991 | increase_indent(); |
4992 | print_suite(p_function->body); |
4993 | decrease_indent(); |
4994 | } |
4995 | |
4996 | void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) { |
4997 | if (p_get_node->use_dollar) { |
4998 | push_text("$" ); |
4999 | } |
5000 | push_text(p_get_node->full_path); |
5001 | } |
5002 | |
5003 | void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) { |
5004 | if (p_identifier != nullptr) { |
5005 | push_text(p_identifier->name); |
5006 | } else { |
5007 | push_text("<invalid identifier>" ); |
5008 | } |
5009 | } |
5010 | |
5011 | void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) { |
5012 | if (p_is_elif) { |
5013 | push_text("Elif " ); |
5014 | } else { |
5015 | push_text("If " ); |
5016 | } |
5017 | print_expression(p_if->condition); |
5018 | push_line(" :" ); |
5019 | |
5020 | increase_indent(); |
5021 | print_suite(p_if->true_block); |
5022 | decrease_indent(); |
5023 | |
5024 | // FIXME: Properly detect "elif" blocks. |
5025 | if (p_if->false_block != nullptr) { |
5026 | push_line("Else :" ); |
5027 | increase_indent(); |
5028 | print_suite(p_if->false_block); |
5029 | decrease_indent(); |
5030 | } |
5031 | } |
5032 | |
5033 | void GDScriptParser::TreePrinter::print_lambda(LambdaNode *p_lambda) { |
5034 | print_function(p_lambda->function, "Lambda" ); |
5035 | push_text("| captures [ " ); |
5036 | for (int i = 0; i < p_lambda->captures.size(); i++) { |
5037 | if (i > 0) { |
5038 | push_text(" , " ); |
5039 | } |
5040 | push_text(p_lambda->captures[i]->name.operator String()); |
5041 | } |
5042 | push_line(" ]" ); |
5043 | } |
5044 | |
5045 | void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) { |
5046 | // Prefix for string types. |
5047 | switch (p_literal->value.get_type()) { |
5048 | case Variant::NODE_PATH: |
5049 | push_text("^\"" ); |
5050 | break; |
5051 | case Variant::STRING: |
5052 | push_text("\"" ); |
5053 | break; |
5054 | case Variant::STRING_NAME: |
5055 | push_text("&\"" ); |
5056 | break; |
5057 | default: |
5058 | break; |
5059 | } |
5060 | push_text(p_literal->value); |
5061 | // Suffix for string types. |
5062 | switch (p_literal->value.get_type()) { |
5063 | case Variant::NODE_PATH: |
5064 | case Variant::STRING: |
5065 | case Variant::STRING_NAME: |
5066 | push_text("\"" ); |
5067 | break; |
5068 | default: |
5069 | break; |
5070 | } |
5071 | } |
5072 | |
5073 | void GDScriptParser::TreePrinter::print_match(MatchNode *p_match) { |
5074 | push_text("Match " ); |
5075 | print_expression(p_match->test); |
5076 | push_line(" :" ); |
5077 | |
5078 | increase_indent(); |
5079 | for (int i = 0; i < p_match->branches.size(); i++) { |
5080 | print_match_branch(p_match->branches[i]); |
5081 | } |
5082 | decrease_indent(); |
5083 | } |
5084 | |
5085 | void GDScriptParser::TreePrinter::print_match_branch(MatchBranchNode *p_match_branch) { |
5086 | for (int i = 0; i < p_match_branch->patterns.size(); i++) { |
5087 | if (i > 0) { |
5088 | push_text(" , " ); |
5089 | } |
5090 | print_match_pattern(p_match_branch->patterns[i]); |
5091 | } |
5092 | |
5093 | push_line(" :" ); |
5094 | |
5095 | increase_indent(); |
5096 | print_suite(p_match_branch->block); |
5097 | decrease_indent(); |
5098 | } |
5099 | |
5100 | void GDScriptParser::TreePrinter::print_match_pattern(PatternNode *p_match_pattern) { |
5101 | switch (p_match_pattern->pattern_type) { |
5102 | case PatternNode::PT_LITERAL: |
5103 | print_literal(p_match_pattern->literal); |
5104 | break; |
5105 | case PatternNode::PT_WILDCARD: |
5106 | push_text("_" ); |
5107 | break; |
5108 | case PatternNode::PT_REST: |
5109 | push_text(".." ); |
5110 | break; |
5111 | case PatternNode::PT_BIND: |
5112 | push_text("Var " ); |
5113 | print_identifier(p_match_pattern->bind); |
5114 | break; |
5115 | case PatternNode::PT_EXPRESSION: |
5116 | print_expression(p_match_pattern->expression); |
5117 | break; |
5118 | case PatternNode::PT_ARRAY: |
5119 | push_text("[ " ); |
5120 | for (int i = 0; i < p_match_pattern->array.size(); i++) { |
5121 | if (i > 0) { |
5122 | push_text(" , " ); |
5123 | } |
5124 | print_match_pattern(p_match_pattern->array[i]); |
5125 | } |
5126 | push_text(" ]" ); |
5127 | break; |
5128 | case PatternNode::PT_DICTIONARY: |
5129 | push_text("{ " ); |
5130 | for (int i = 0; i < p_match_pattern->dictionary.size(); i++) { |
5131 | if (i > 0) { |
5132 | push_text(" , " ); |
5133 | } |
5134 | if (p_match_pattern->dictionary[i].key != nullptr) { |
5135 | // Key can be null for rest pattern. |
5136 | print_expression(p_match_pattern->dictionary[i].key); |
5137 | push_text(" : " ); |
5138 | } |
5139 | print_match_pattern(p_match_pattern->dictionary[i].value_pattern); |
5140 | } |
5141 | push_text(" }" ); |
5142 | break; |
5143 | } |
5144 | } |
5145 | |
5146 | void GDScriptParser::TreePrinter::print_parameter(ParameterNode *p_parameter) { |
5147 | print_identifier(p_parameter->identifier); |
5148 | if (p_parameter->datatype_specifier != nullptr) { |
5149 | push_text(" : " ); |
5150 | print_type(p_parameter->datatype_specifier); |
5151 | } |
5152 | if (p_parameter->initializer != nullptr) { |
5153 | push_text(" = " ); |
5154 | print_expression(p_parameter->initializer); |
5155 | } |
5156 | } |
5157 | |
5158 | void GDScriptParser::TreePrinter::print_preload(PreloadNode *p_preload) { |
5159 | push_text(R"(Preload ( ")" ); |
5160 | push_text(p_preload->resolved_path); |
5161 | push_text(R"(" )" ); |
5162 | } |
5163 | |
5164 | void GDScriptParser::TreePrinter::print_return(ReturnNode *p_return) { |
5165 | push_text("Return" ); |
5166 | if (p_return->return_value != nullptr) { |
5167 | push_text(" " ); |
5168 | print_expression(p_return->return_value); |
5169 | } |
5170 | push_line(); |
5171 | } |
5172 | |
5173 | void GDScriptParser::TreePrinter::print_self(SelfNode *p_self) { |
5174 | push_text("Self(" ); |
5175 | if (p_self->current_class->identifier != nullptr) { |
5176 | print_identifier(p_self->current_class->identifier); |
5177 | } else { |
5178 | push_text("<main class>" ); |
5179 | } |
5180 | push_text(")" ); |
5181 | } |
5182 | |
5183 | void GDScriptParser::TreePrinter::print_signal(SignalNode *p_signal) { |
5184 | push_text("Signal " ); |
5185 | print_identifier(p_signal->identifier); |
5186 | push_text("( " ); |
5187 | for (int i = 0; i < p_signal->parameters.size(); i++) { |
5188 | print_parameter(p_signal->parameters[i]); |
5189 | } |
5190 | push_line(" )" ); |
5191 | } |
5192 | |
5193 | void GDScriptParser::TreePrinter::print_subscript(SubscriptNode *p_subscript) { |
5194 | print_expression(p_subscript->base); |
5195 | if (p_subscript->is_attribute) { |
5196 | push_text("." ); |
5197 | print_identifier(p_subscript->attribute); |
5198 | } else { |
5199 | push_text("[ " ); |
5200 | print_expression(p_subscript->index); |
5201 | push_text(" ]" ); |
5202 | } |
5203 | } |
5204 | |
5205 | void GDScriptParser::TreePrinter::print_statement(Node *p_statement) { |
5206 | switch (p_statement->type) { |
5207 | case Node::ASSERT: |
5208 | print_assert(static_cast<AssertNode *>(p_statement)); |
5209 | break; |
5210 | case Node::VARIABLE: |
5211 | print_variable(static_cast<VariableNode *>(p_statement)); |
5212 | break; |
5213 | case Node::CONSTANT: |
5214 | print_constant(static_cast<ConstantNode *>(p_statement)); |
5215 | break; |
5216 | case Node::IF: |
5217 | print_if(static_cast<IfNode *>(p_statement)); |
5218 | break; |
5219 | case Node::FOR: |
5220 | print_for(static_cast<ForNode *>(p_statement)); |
5221 | break; |
5222 | case Node::WHILE: |
5223 | print_while(static_cast<WhileNode *>(p_statement)); |
5224 | break; |
5225 | case Node::MATCH: |
5226 | print_match(static_cast<MatchNode *>(p_statement)); |
5227 | break; |
5228 | case Node::RETURN: |
5229 | print_return(static_cast<ReturnNode *>(p_statement)); |
5230 | break; |
5231 | case Node::BREAK: |
5232 | push_line("Break" ); |
5233 | break; |
5234 | case Node::CONTINUE: |
5235 | push_line("Continue" ); |
5236 | break; |
5237 | case Node::PASS: |
5238 | push_line("Pass" ); |
5239 | break; |
5240 | case Node::BREAKPOINT: |
5241 | push_line("Breakpoint" ); |
5242 | break; |
5243 | case Node::ASSIGNMENT: |
5244 | print_assignment(static_cast<AssignmentNode *>(p_statement)); |
5245 | break; |
5246 | default: |
5247 | if (p_statement->is_expression()) { |
5248 | print_expression(static_cast<ExpressionNode *>(p_statement)); |
5249 | push_line(); |
5250 | } else { |
5251 | push_line(vformat("<unknown statement %d>" , p_statement->type)); |
5252 | } |
5253 | break; |
5254 | } |
5255 | } |
5256 | |
5257 | void GDScriptParser::TreePrinter::print_suite(SuiteNode *p_suite) { |
5258 | for (int i = 0; i < p_suite->statements.size(); i++) { |
5259 | print_statement(p_suite->statements[i]); |
5260 | } |
5261 | } |
5262 | |
5263 | void GDScriptParser::TreePrinter::print_ternary_op(TernaryOpNode *p_ternary_op) { |
5264 | // Surround in parenthesis for disambiguation. |
5265 | push_text("(" ); |
5266 | print_expression(p_ternary_op->true_expr); |
5267 | push_text(") IF (" ); |
5268 | print_expression(p_ternary_op->condition); |
5269 | push_text(") ELSE (" ); |
5270 | print_expression(p_ternary_op->false_expr); |
5271 | push_text(")" ); |
5272 | } |
5273 | |
5274 | void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) { |
5275 | if (p_type->type_chain.is_empty()) { |
5276 | push_text("Void" ); |
5277 | } else { |
5278 | for (int i = 0; i < p_type->type_chain.size(); i++) { |
5279 | if (i > 0) { |
5280 | push_text("." ); |
5281 | } |
5282 | print_identifier(p_type->type_chain[i]); |
5283 | } |
5284 | } |
5285 | } |
5286 | |
5287 | void GDScriptParser::TreePrinter::print_type_test(TypeTestNode *p_test) { |
5288 | print_expression(p_test->operand); |
5289 | push_text(" IS " ); |
5290 | print_type(p_test->test_type); |
5291 | } |
5292 | |
5293 | void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) { |
5294 | // Surround in parenthesis for disambiguation. |
5295 | push_text("(" ); |
5296 | switch (p_unary_op->operation) { |
5297 | case UnaryOpNode::OP_POSITIVE: |
5298 | push_text("+" ); |
5299 | break; |
5300 | case UnaryOpNode::OP_NEGATIVE: |
5301 | push_text("-" ); |
5302 | break; |
5303 | case UnaryOpNode::OP_LOGIC_NOT: |
5304 | push_text("NOT" ); |
5305 | break; |
5306 | case UnaryOpNode::OP_COMPLEMENT: |
5307 | push_text("~" ); |
5308 | break; |
5309 | } |
5310 | print_expression(p_unary_op->operand); |
5311 | // Surround in parenthesis for disambiguation. |
5312 | push_text(")" ); |
5313 | } |
5314 | |
5315 | void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) { |
5316 | for (const AnnotationNode *E : p_variable->annotations) { |
5317 | print_annotation(E); |
5318 | } |
5319 | |
5320 | push_text("Variable " ); |
5321 | print_identifier(p_variable->identifier); |
5322 | |
5323 | push_text(" : " ); |
5324 | if (p_variable->datatype_specifier != nullptr) { |
5325 | print_type(p_variable->datatype_specifier); |
5326 | } else if (p_variable->infer_datatype) { |
5327 | push_text("<inferred type>" ); |
5328 | } else { |
5329 | push_text("Variant" ); |
5330 | } |
5331 | |
5332 | increase_indent(); |
5333 | |
5334 | push_line(); |
5335 | push_text("= " ); |
5336 | if (p_variable->initializer == nullptr) { |
5337 | push_text("<default value>" ); |
5338 | } else { |
5339 | print_expression(p_variable->initializer); |
5340 | } |
5341 | push_line(); |
5342 | |
5343 | if (p_variable->property != VariableNode::PROP_NONE) { |
5344 | if (p_variable->getter != nullptr) { |
5345 | push_text("Get" ); |
5346 | if (p_variable->property == VariableNode::PROP_INLINE) { |
5347 | push_line(":" ); |
5348 | increase_indent(); |
5349 | print_suite(p_variable->getter->body); |
5350 | decrease_indent(); |
5351 | } else { |
5352 | push_line(" =" ); |
5353 | increase_indent(); |
5354 | print_identifier(p_variable->getter_pointer); |
5355 | push_line(); |
5356 | decrease_indent(); |
5357 | } |
5358 | } |
5359 | if (p_variable->setter != nullptr) { |
5360 | push_text("Set (" ); |
5361 | if (p_variable->property == VariableNode::PROP_INLINE) { |
5362 | if (p_variable->setter_parameter != nullptr) { |
5363 | print_identifier(p_variable->setter_parameter); |
5364 | } else { |
5365 | push_text("<missing>" ); |
5366 | } |
5367 | push_line("):" ); |
5368 | increase_indent(); |
5369 | print_suite(p_variable->setter->body); |
5370 | decrease_indent(); |
5371 | } else { |
5372 | push_line(" =" ); |
5373 | increase_indent(); |
5374 | print_identifier(p_variable->setter_pointer); |
5375 | push_line(); |
5376 | decrease_indent(); |
5377 | } |
5378 | } |
5379 | } |
5380 | |
5381 | decrease_indent(); |
5382 | push_line(); |
5383 | } |
5384 | |
5385 | void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) { |
5386 | push_text("While " ); |
5387 | print_expression(p_while->condition); |
5388 | push_line(" :" ); |
5389 | |
5390 | increase_indent(); |
5391 | print_suite(p_while->loop); |
5392 | decrease_indent(); |
5393 | } |
5394 | |
5395 | void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) { |
5396 | ERR_FAIL_COND_MSG(p_parser.get_tree() == nullptr, "Parse the code before printing the parse tree." ); |
5397 | |
5398 | if (p_parser.is_tool()) { |
5399 | push_line("@tool" ); |
5400 | } |
5401 | if (!p_parser.get_tree()->icon_path.is_empty()) { |
5402 | push_text(R"(@icon (")" ); |
5403 | push_text(p_parser.get_tree()->icon_path); |
5404 | push_line("\")" ); |
5405 | } |
5406 | print_class(p_parser.get_tree()); |
5407 | |
5408 | print_line(String(printed)); |
5409 | } |
5410 | |
5411 | #endif // DEBUG_ENABLED |
5412 | |