2023-01-05 13:25:55 +01:00
/**************************************************************************/
/* gdscript_parser.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2017-11-16 18:38:18 +01:00
# include "gdscript_parser.h"
2023-06-13 16:56:21 +02:00
# include "gdscript.h"
2024-01-22 15:31:55 +01:00
# include "gdscript_tokenizer_buffer.h"
2023-06-13 16:56:21 +02:00
2020-11-07 23:33:38 +01:00
# include "core/config/project_settings.h"
2021-06-11 14:51:48 +02:00
# include "core/io/file_access.h"
2018-09-11 18:13:45 +02:00
# include "core/io/resource_loader.h"
2020-05-02 00:14:56 +02:00
# include "core/math/math_defs.h"
2022-07-12 23:12:42 +02:00
# include "scene/main/multiplayer_api.h"
2020-05-02 00:14:56 +02:00
# ifdef DEBUG_ENABLED
# include "core/os/os.h"
2020-11-07 23:33:38 +01:00
# include "core/string/string_builder.h"
2023-01-19 02:56:00 +01:00
# include "servers/text_server.h"
2023-06-13 16:56:21 +02:00
# endif
2020-05-02 00:14:56 +02:00
2020-07-06 17:24:24 +02:00
# ifdef TOOLS_ENABLED
# include "editor/editor_settings.h"
2023-06-13 16:56:21 +02:00
# endif
2020-07-06 17:24:24 +02:00
2023-09-21 11:42:55 +02:00
// This function is used to determine that a type is "built-in" as opposed to native
// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded:
// `Variant::NIL` - `null` is literal, not a type.
// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type.
2020-05-02 00:14:56 +02:00
static HashMap < StringName , Variant : : Type > builtin_types ;
Variant : : Type GDScriptParser : : get_builtin_type ( const StringName & p_type ) {
2023-09-21 11:42:55 +02:00
if ( unlikely ( builtin_types . is_empty ( ) ) ) {
for ( int i = 0 ; i < Variant : : VARIANT_MAX ; i + + ) {
Variant : : Type type = ( Variant : : Type ) i ;
if ( type ! = Variant : : NIL & & type ! = Variant : : OBJECT ) {
builtin_types [ Variant : : get_type_name ( type ) ] = type ;
}
2020-05-02 00:14:56 +02:00
}
}
if ( builtin_types . has ( p_type ) ) {
return builtin_types [ p_type ] ;
}
return Variant : : VARIANT_MAX ;
}
2023-03-12 08:33:38 +01:00
# ifdef TOOLS_ENABLED
HashMap < String , String > GDScriptParser : : theme_color_names ;
# endif
2023-11-28 21:15:00 +01:00
HashMap < StringName , GDScriptParser : : AnnotationInfo > GDScriptParser : : valid_annotations ;
2020-08-01 21:00:26 +02:00
void GDScriptParser : : cleanup ( ) {
builtin_types . clear ( ) ;
2023-11-28 21:15:00 +01:00
valid_annotations . clear ( ) ;
2020-08-01 21:00:26 +02:00
}
2020-07-06 17:24:24 +02:00
void GDScriptParser : : get_annotation_list ( List < MethodInfo > * r_annotations ) const {
2022-05-08 10:09:19 +02:00
for ( const KeyValue < StringName , AnnotationInfo > & E : valid_annotations ) {
r_annotations - > push_back ( E . value . info ) ;
2020-07-06 17:24:24 +02:00
}
}
2022-07-04 17:56:34 +02:00
bool GDScriptParser : : annotation_exists ( const String & p_annotation_name ) const {
return valid_annotations . has ( p_annotation_name ) ;
}
2020-05-02 00:14:56 +02:00
GDScriptParser : : GDScriptParser ( ) {
// Register valid annotations.
2023-11-28 21:15:00 +01:00
if ( unlikely ( valid_annotations . is_empty ( ) ) ) {
register_annotation ( MethodInfo ( " @tool " ) , AnnotationInfo : : SCRIPT , & GDScriptParser : : tool_annotation ) ;
register_annotation ( MethodInfo ( " @icon " , PropertyInfo ( Variant : : STRING , " icon_path " ) ) , AnnotationInfo : : SCRIPT , & GDScriptParser : : icon_annotation ) ;
register_annotation ( MethodInfo ( " @static_unload " ) , AnnotationInfo : : SCRIPT , & GDScriptParser : : static_unload_annotation ) ;
register_annotation ( MethodInfo ( " @onready " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : onready_annotation ) ;
// Export annotations.
register_annotation ( MethodInfo ( " @export " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_NONE , Variant : : NIL > ) ;
register_annotation ( MethodInfo ( " @export_enum " , PropertyInfo ( Variant : : STRING , " names " ) ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_ENUM , Variant : : NIL > , varray ( ) , true ) ;
register_annotation ( MethodInfo ( " @export_file " , PropertyInfo ( Variant : : STRING , " filter " ) ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_FILE , Variant : : STRING > , varray ( " " ) , true ) ;
register_annotation ( MethodInfo ( " @export_dir " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_DIR , Variant : : STRING > ) ;
register_annotation ( MethodInfo ( " @export_global_file " , PropertyInfo ( Variant : : STRING , " filter " ) ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_GLOBAL_FILE , Variant : : STRING > , varray ( " " ) , true ) ;
register_annotation ( MethodInfo ( " @export_global_dir " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_GLOBAL_DIR , Variant : : STRING > ) ;
register_annotation ( MethodInfo ( " @export_multiline " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_MULTILINE_TEXT , Variant : : STRING > ) ;
register_annotation ( MethodInfo ( " @export_placeholder " , PropertyInfo ( Variant : : STRING , " placeholder " ) ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_PLACEHOLDER_TEXT , Variant : : STRING > ) ;
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 ) ;
register_annotation ( MethodInfo ( " @export_exp_easing " , PropertyInfo ( Variant : : STRING , " hints " ) ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_EXP_EASING , Variant : : FLOAT > , varray ( " " ) , true ) ;
register_annotation ( MethodInfo ( " @export_color_no_alpha " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_COLOR_NO_ALPHA , Variant : : COLOR > ) ;
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 ) ;
register_annotation ( MethodInfo ( " @export_flags " , PropertyInfo ( Variant : : STRING , " names " ) ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_FLAGS , Variant : : INT > , varray ( ) , true ) ;
register_annotation ( MethodInfo ( " @export_flags_2d_render " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_LAYERS_2D_RENDER , Variant : : INT > ) ;
register_annotation ( MethodInfo ( " @export_flags_2d_physics " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_LAYERS_2D_PHYSICS , Variant : : INT > ) ;
register_annotation ( MethodInfo ( " @export_flags_2d_navigation " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_LAYERS_2D_NAVIGATION , Variant : : INT > ) ;
register_annotation ( MethodInfo ( " @export_flags_3d_render " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_LAYERS_3D_RENDER , Variant : : INT > ) ;
register_annotation ( MethodInfo ( " @export_flags_3d_physics " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_LAYERS_3D_PHYSICS , Variant : : INT > ) ;
register_annotation ( MethodInfo ( " @export_flags_3d_navigation " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_LAYERS_3D_NAVIGATION , Variant : : INT > ) ;
register_annotation ( MethodInfo ( " @export_flags_avoidance " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_LAYERS_AVOIDANCE , Variant : : INT > ) ;
2024-04-15 21:10:47 +02:00
register_annotation ( MethodInfo ( " @export_storage " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_storage_annotation ) ;
2023-02-08 20:15:48 +01:00
register_annotation ( MethodInfo ( " @export_custom " , PropertyInfo ( Variant : : INT , " hint " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_CLASS_IS_ENUM , " PropertyHint " ) , PropertyInfo ( Variant : : STRING , " hint_string " ) , PropertyInfo ( Variant : : INT , " usage " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_CLASS_IS_BITFIELD , " PropertyUsageFlags " ) ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_custom_annotation , varray ( PROPERTY_USAGE_DEFAULT ) ) ;
2023-11-28 21:15:00 +01:00
// Export grouping annotations.
register_annotation ( MethodInfo ( " @export_category " , PropertyInfo ( Variant : : STRING , " name " ) ) , AnnotationInfo : : STANDALONE , & GDScriptParser : : export_group_annotations < PROPERTY_USAGE_CATEGORY > ) ;
register_annotation ( MethodInfo ( " @export_group " , PropertyInfo ( Variant : : STRING , " name " ) , PropertyInfo ( Variant : : STRING , " prefix " ) ) , AnnotationInfo : : STANDALONE , & GDScriptParser : : export_group_annotations < PROPERTY_USAGE_GROUP > , varray ( " " ) ) ;
register_annotation ( MethodInfo ( " @export_subgroup " , PropertyInfo ( Variant : : STRING , " name " ) , PropertyInfo ( Variant : : STRING , " prefix " ) ) , AnnotationInfo : : STANDALONE , & GDScriptParser : : export_group_annotations < PROPERTY_USAGE_SUBGROUP > , varray ( " " ) ) ;
// Warning annotations.
2024-02-28 15:23:11 +01:00
register_annotation ( MethodInfo ( " @warning_ignore " , PropertyInfo ( Variant : : STRING , " warning " ) ) , AnnotationInfo : : CLASS_LEVEL | AnnotationInfo : : STATEMENT , & GDScriptParser : : warning_annotations , varray ( ) , true ) ;
2023-11-28 21:15:00 +01:00
// Networking.
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 ) ) ;
}
2022-11-20 18:53:56 +01:00
2022-11-24 11:50:32 +01:00
# ifdef DEBUG_ENABLED
2022-11-20 18:53:56 +01:00
is_ignoring_warnings = ! ( bool ) GLOBAL_GET ( " debug/gdscript/warnings/enable " ) ;
2022-11-24 11:50:32 +01:00
# endif
2023-03-12 08:33:38 +01:00
# ifdef TOOLS_ENABLED
2024-06-07 17:00:41 +02:00
if ( unlikely ( theme_color_names . is_empty ( ) ) ) {
// Vectors.
2023-03-12 08:33:38 +01:00
theme_color_names . insert ( " x " , " axis_x_color " ) ;
theme_color_names . insert ( " y " , " axis_y_color " ) ;
theme_color_names . insert ( " z " , " axis_z_color " ) ;
theme_color_names . insert ( " w " , " axis_w_color " ) ;
2024-06-07 17:00:41 +02:00
// Color.
theme_color_names . insert ( " r " , " axis_x_color " ) ;
theme_color_names . insert ( " r8 " , " axis_x_color " ) ;
theme_color_names . insert ( " g " , " axis_y_color " ) ;
theme_color_names . insert ( " g8 " , " axis_y_color " ) ;
theme_color_names . insert ( " b " , " axis_z_color " ) ;
theme_color_names . insert ( " b8 " , " axis_z_color " ) ;
theme_color_names . insert ( " a " , " axis_w_color " ) ;
theme_color_names . insert ( " a8 " , " axis_w_color " ) ;
2023-03-12 08:33:38 +01:00
}
# endif
2020-05-02 00:14:56 +02:00
}
GDScriptParser : : ~ GDScriptParser ( ) {
while ( list ! = nullptr ) {
Node * element = list ;
list = list - > next ;
memdelete ( element ) ;
2020-05-14 16:41:43 +02:00
}
2024-04-13 01:13:25 +02:00
}
2014-02-10 02:10:30 +01:00
2024-04-13 01:13:25 +02:00
void GDScriptParser : : clear ( ) {
GDScriptParser tmp ;
tmp = * this ;
* this = GDScriptParser ( ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : push_error ( const String & p_message , const Node * p_origin ) {
// TODO: Improve error reporting by pointing at source code.
// TODO: Errors might point at more than one place at once (e.g. show previous declaration).
panic_mode = true ;
// TODO: Improve positional information.
if ( p_origin = = nullptr ) {
2023-11-28 22:09:11 +01:00
errors . push_back ( { p_message , previous . start_line , previous . start_column } ) ;
2020-05-02 00:14:56 +02:00
} else {
errors . push_back ( { p_message , p_origin - > start_line , p_origin - > leftmost_column } ) ;
}
}
2020-07-16 03:02:44 +02:00
# ifdef DEBUG_ENABLED
2020-06-12 00:31:28 +02:00
void GDScriptParser : : push_warning ( const Node * p_source , GDScriptWarning : : Code p_code , const Vector < String > & p_symbols ) {
2023-09-09 17:40:07 +02:00
ERR_FAIL_NULL ( p_source ) ;
2024-02-28 15:23:11 +01:00
ERR_FAIL_INDEX ( p_code , GDScriptWarning : : WARNING_MAX ) ;
2020-06-12 00:31:28 +02:00
if ( is_ignoring_warnings ) {
return ;
}
if ( GLOBAL_GET ( " debug/gdscript/warnings/exclude_addons " ) . booleanize ( ) & & script_path . begins_with ( " res://addons/ " ) ) {
return ;
}
2024-02-28 15:23:11 +01:00
GDScriptWarning : : WarnLevel warn_level = ( GDScriptWarning : : WarnLevel ) ( int ) GLOBAL_GET ( GDScriptWarning : : get_settings_path_from_code ( p_code ) ) ;
if ( warn_level = = GDScriptWarning : : IGNORE ) {
2022-01-04 13:32:43 +01:00
return ;
}
2024-02-28 15:23:11 +01:00
PendingWarning pw ;
pw . source = p_source ;
pw . code = p_code ;
pw . treated_as_error = warn_level = = GDScriptWarning : : ERROR ;
pw . symbols = p_symbols ;
2020-06-12 00:31:28 +02:00
2024-02-28 15:23:11 +01:00
pending_warnings . push_back ( pw ) ;
}
2020-06-12 00:31:28 +02:00
2024-02-28 15:23:11 +01:00
void GDScriptParser : : apply_pending_warnings ( ) {
for ( const PendingWarning & pw : pending_warnings ) {
if ( warning_ignored_lines [ pw . code ] . has ( pw . source - > start_line ) ) {
continue ;
}
2022-03-23 03:44:30 +01:00
2024-02-28 15:23:11 +01:00
GDScriptWarning warning ;
warning . code = pw . code ;
warning . symbols = pw . symbols ;
warning . start_line = pw . source - > start_line ;
warning . end_line = pw . source - > end_line ;
warning . leftmost_column = pw . source - > leftmost_column ;
warning . rightmost_column = pw . source - > rightmost_column ;
if ( pw . treated_as_error ) {
push_error ( warning . get_message ( ) + String ( " (Warning treated as error.) " ) , pw . source ) ;
continue ;
}
List < GDScriptWarning > : : Element * before = nullptr ;
for ( List < GDScriptWarning > : : Element * E = warnings . front ( ) ; E ; E = E - > next ( ) ) {
if ( E - > get ( ) . start_line > warning . start_line ) {
break ;
}
before = E ;
}
if ( before ) {
warnings . insert_after ( before , warning ) ;
} else {
warnings . push_front ( warning ) ;
2020-06-12 00:31:28 +02:00
}
}
2024-02-28 15:23:11 +01:00
pending_warnings . clear ( ) ;
2020-06-12 00:31:28 +02:00
}
2020-07-16 03:02:44 +02:00
# endif
2020-06-12 00:31:28 +02:00
2020-07-06 17:24:24 +02:00
void GDScriptParser : : make_completion_context ( CompletionType p_type , Node * p_node , int p_argument , bool p_force ) {
if ( ! for_completion | | ( ! p_force & & completion_context . type ! = COMPLETION_NONE ) ) {
return ;
}
2024-01-22 15:31:55 +01:00
if ( previous . cursor_place ! = GDScriptTokenizerText : : CURSOR_MIDDLE & & previous . cursor_place ! = GDScriptTokenizerText : : CURSOR_END & & current . cursor_place = = GDScriptTokenizerText : : CURSOR_NONE ) {
2020-07-06 17:24:24 +02:00
return ;
}
CompletionContext context ;
context . type = p_type ;
context . current_class = current_class ;
context . current_function = current_function ;
context . current_suite = current_suite ;
2024-01-22 15:31:55 +01:00
context . current_line = tokenizer - > get_cursor_line ( ) ;
2020-07-06 17:24:24 +02:00
context . current_argument = p_argument ;
context . node = p_node ;
2024-07-16 08:49:41 +02:00
context . parser = this ;
2020-07-06 17:24:24 +02:00
completion_context = context ;
}
void GDScriptParser : : make_completion_context ( CompletionType p_type , Variant : : Type p_builtin_type , bool p_force ) {
if ( ! for_completion | | ( ! p_force & & completion_context . type ! = COMPLETION_NONE ) ) {
return ;
}
2024-01-22 15:31:55 +01:00
if ( previous . cursor_place ! = GDScriptTokenizerText : : CURSOR_MIDDLE & & previous . cursor_place ! = GDScriptTokenizerText : : CURSOR_END & & current . cursor_place = = GDScriptTokenizerText : : CURSOR_NONE ) {
2020-07-06 17:24:24 +02:00
return ;
}
CompletionContext context ;
context . type = p_type ;
context . current_class = current_class ;
context . current_function = current_function ;
context . current_suite = current_suite ;
2024-01-22 15:31:55 +01:00
context . current_line = tokenizer - > get_cursor_line ( ) ;
2020-07-06 17:24:24 +02:00
context . builtin_type = p_builtin_type ;
2024-07-16 08:49:41 +02:00
context . parser = this ;
2020-07-06 17:24:24 +02:00
completion_context = context ;
}
void GDScriptParser : : push_completion_call ( Node * p_call ) {
if ( ! for_completion ) {
return ;
}
CompletionCall call ;
call . call = p_call ;
call . argument = 0 ;
completion_call_stack . push_back ( call ) ;
2024-01-22 15:31:55 +01:00
if ( previous . cursor_place = = GDScriptTokenizerText : : CURSOR_MIDDLE | | previous . cursor_place = = GDScriptTokenizerText : : CURSOR_END | | current . cursor_place = = GDScriptTokenizerText : : CURSOR_BEGINNING ) {
2020-07-06 17:24:24 +02:00
completion_call = call ;
}
}
void GDScriptParser : : pop_completion_call ( ) {
if ( ! for_completion ) {
return ;
}
2020-12-15 13:04:21 +01:00
ERR_FAIL_COND_MSG ( completion_call_stack . is_empty ( ) , " Trying to pop empty completion call stack " ) ;
2020-07-06 17:24:24 +02:00
completion_call_stack . pop_back ( ) ;
}
void GDScriptParser : : set_last_completion_call_arg ( int p_argument ) {
if ( ! for_completion | | passed_cursor ) {
return ;
}
2020-12-15 13:04:21 +01:00
ERR_FAIL_COND_MSG ( completion_call_stack . is_empty ( ) , " Trying to set argument on empty completion call stack " ) ;
2020-07-06 17:24:24 +02:00
completion_call_stack . back ( ) - > get ( ) . argument = p_argument ;
}
2024-05-24 07:30:16 +02:00
Error GDScriptParser : : parse ( const String & p_source_code , const String & p_script_path , bool p_for_completion , bool p_parse_body ) {
2020-05-02 00:14:56 +02:00
clear ( ) ;
2020-07-06 17:24:24 +02:00
String source = p_source_code ;
int cursor_line = - 1 ;
int cursor_column = - 1 ;
for_completion = p_for_completion ;
2024-05-24 07:30:16 +02:00
parse_body = p_parse_body ;
2020-07-06 17:24:24 +02:00
int tab_size = 4 ;
# ifdef TOOLS_ENABLED
if ( EditorSettings : : get_singleton ( ) ) {
2021-08-15 19:14:46 +02:00
tab_size = EditorSettings : : get_singleton ( ) - > get_setting ( " text_editor/behavior/indent/size " ) ;
2020-07-06 17:24:24 +02:00
}
# endif // TOOLS_ENABLED
if ( p_for_completion ) {
// Remove cursor sentinel char.
const Vector < String > lines = p_source_code . split ( " \n " ) ;
cursor_line = 1 ;
cursor_column = 1 ;
for ( int i = 0 ; i < lines . size ( ) ; i + + ) {
bool found = false ;
const String & line = lines [ i ] ;
for ( int j = 0 ; j < line . size ( ) ; j + + ) {
2020-07-27 12:43:20 +02:00
if ( line [ j ] = = char32_t ( 0xFFFF ) ) {
2020-07-06 17:24:24 +02:00
found = true ;
break ;
} else if ( line [ j ] = = ' \t ' ) {
cursor_column + = tab_size - 1 ;
}
cursor_column + + ;
}
if ( found ) {
break ;
}
cursor_line + + ;
cursor_column = 1 ;
}
source = source . replace_first ( String : : chr ( 0xFFFF ) , String ( ) ) ;
}
2024-01-22 15:31:55 +01:00
GDScriptTokenizerText * text_tokenizer = memnew ( GDScriptTokenizerText ) ;
text_tokenizer - > set_source_code ( source ) ;
tokenizer = text_tokenizer ;
tokenizer - > set_cursor_position ( cursor_line , cursor_column ) ;
2024-01-12 18:53:59 +01:00
script_path = p_script_path . simplify_path ( ) ;
2024-01-22 15:31:55 +01:00
current = tokenizer - > scan ( ) ;
2021-09-11 20:38:15 +02:00
// Avoid error or newline as the first token.
// The latter can mess with the parser when opening files filled exclusively with comments and newlines.
while ( current . type = = GDScriptTokenizer : : Token : : ERROR | | current . type = = GDScriptTokenizer : : Token : : NEWLINE ) {
if ( current . type = = GDScriptTokenizer : : Token : : ERROR ) {
push_error ( current . literal ) ;
}
2024-01-22 15:31:55 +01:00
current = tokenizer - > scan ( ) ;
2020-06-01 21:41:05 +02:00
}
2020-05-02 00:14:56 +02:00
2021-09-11 20:38:15 +02:00
# ifdef DEBUG_ENABLED
// Warn about parsing an empty script file:
if ( current . type = = GDScriptTokenizer : : Token : : TK_EOF ) {
// Create a dummy Node for the warning, pointing to the very beginning of the file
Node * nd = alloc_node < PassNode > ( ) ;
nd - > start_line = 1 ;
nd - > start_column = 0 ;
nd - > end_line = 1 ;
nd - > leftmost_column = 0 ;
nd - > rightmost_column = 0 ;
push_warning ( nd , GDScriptWarning : : EMPTY_FILE ) ;
}
# endif
2020-05-02 00:14:56 +02:00
push_multiline ( false ) ; // Keep one for the whole parsing.
parse_program ( ) ;
pop_multiline ( ) ;
2018-07-01 18:17:40 +02:00
2024-01-22 15:31:55 +01:00
memdelete ( text_tokenizer ) ;
2024-01-26 18:49:31 +01:00
tokenizer = nullptr ;
2024-01-22 15:31:55 +01:00
2020-05-02 00:14:56 +02:00
# ifdef DEBUG_ENABLED
if ( multiline_stack . size ( ) > 0 ) {
ERR_PRINT ( " Parser bug: Imbalanced multiline stack. " ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
# endif
2014-02-10 02:10:30 +01:00
2020-12-15 13:04:21 +01:00
if ( errors . is_empty ( ) ) {
2020-05-02 00:14:56 +02:00
return OK ;
} else {
return ERR_PARSE_ERROR ;
}
2014-02-10 02:10:30 +01:00
}
2024-01-22 15:31:55 +01:00
Error GDScriptParser : : parse_binary ( const Vector < uint8_t > & p_binary , const String & p_script_path ) {
GDScriptTokenizerBuffer * buffer_tokenizer = memnew ( GDScriptTokenizerBuffer ) ;
Error err = buffer_tokenizer - > set_code_buffer ( p_binary ) ;
if ( err ) {
2024-01-26 18:49:31 +01:00
memdelete ( buffer_tokenizer ) ;
2024-01-22 15:31:55 +01:00
return err ;
}
tokenizer = buffer_tokenizer ;
2024-07-09 15:16:45 +02:00
script_path = p_script_path . simplify_path ( ) ;
2024-01-22 15:31:55 +01:00
current = tokenizer - > scan ( ) ;
// Avoid error or newline as the first token.
// The latter can mess with the parser when opening files filled exclusively with comments and newlines.
while ( current . type = = GDScriptTokenizer : : Token : : ERROR | | current . type = = GDScriptTokenizer : : Token : : NEWLINE ) {
if ( current . type = = GDScriptTokenizer : : Token : : ERROR ) {
push_error ( current . literal ) ;
}
current = tokenizer - > scan ( ) ;
}
push_multiline ( false ) ; // Keep one for the whole parsing.
parse_program ( ) ;
pop_multiline ( ) ;
memdelete ( buffer_tokenizer ) ;
2024-01-26 18:49:31 +01:00
tokenizer = nullptr ;
2024-01-22 15:31:55 +01:00
if ( errors . is_empty ( ) ) {
return OK ;
} else {
return ERR_PARSE_ERROR ;
}
}
2020-05-02 00:14:56 +02:00
GDScriptTokenizer : : Token GDScriptParser : : advance ( ) {
2021-03-25 14:36:29 +01:00
lambda_ended = false ; // Empty marker since we're past the end in any case.
2020-05-02 00:14:56 +02:00
if ( current . type = = GDScriptTokenizer : : Token : : TK_EOF ) {
ERR_FAIL_COND_V_MSG ( current . type = = GDScriptTokenizer : : Token : : TK_EOF , current , " GDScript parser bug: Trying to advance past the end of stream. " ) ;
2020-03-06 20:40:47 +01:00
}
2020-12-15 13:04:21 +01:00
if ( for_completion & & ! completion_call_stack . is_empty ( ) ) {
2024-01-22 15:31:55 +01:00
if ( completion_call . call = = nullptr & & tokenizer - > is_past_cursor ( ) ) {
2020-07-06 17:24:24 +02:00
completion_call = completion_call_stack . back ( ) - > get ( ) ;
passed_cursor = true ;
}
}
2020-05-02 00:14:56 +02:00
previous = current ;
2024-01-22 15:31:55 +01:00
current = tokenizer - > scan ( ) ;
2020-05-02 00:14:56 +02:00
while ( current . type = = GDScriptTokenizer : : Token : : ERROR ) {
push_error ( current . literal ) ;
2024-01-22 15:31:55 +01:00
current = tokenizer - > scan ( ) ;
2020-05-02 00:14:56 +02:00
}
2023-09-15 17:59:30 +02:00
if ( previous . type ! = GDScriptTokenizer : : Token : : DEDENT ) { // `DEDENT` belongs to the next non-empty line.
for ( Node * n : nodes_in_progress ) {
update_extents ( n ) ;
}
2022-07-11 20:31:15 +02:00
}
2020-05-02 00:14:56 +02:00
return previous ;
2020-03-06 20:40:47 +01:00
}
2020-05-02 00:14:56 +02:00
bool GDScriptParser : : match ( GDScriptTokenizer : : Token : : Type p_token_type ) {
if ( ! check ( p_token_type ) ) {
2014-02-10 02:10:30 +01:00
return false ;
}
2020-05-02 00:14:56 +02:00
advance ( ) ;
return true ;
}
2014-02-10 02:10:30 +01:00
2021-03-25 14:36:29 +01:00
bool GDScriptParser : : check ( GDScriptTokenizer : : Token : : Type p_token_type ) const {
2020-05-02 00:14:56 +02:00
if ( p_token_type = = GDScriptTokenizer : : Token : : IDENTIFIER ) {
return current . is_identifier ( ) ;
2018-09-16 23:29:17 +02:00
}
2020-05-02 00:14:56 +02:00
return current . type = = p_token_type ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
bool GDScriptParser : : consume ( GDScriptTokenizer : : Token : : Type p_token_type , const String & p_error_message ) {
if ( match ( p_token_type ) ) {
2015-12-29 15:41:37 +01:00
return true ;
2020-05-02 00:14:56 +02:00
}
push_error ( p_error_message ) ;
return false ;
}
2014-02-10 02:10:30 +01:00
2021-03-25 14:36:29 +01:00
bool GDScriptParser : : is_at_end ( ) const {
2020-05-02 00:14:56 +02:00
return check ( GDScriptTokenizer : : Token : : TK_EOF ) ;
}
2019-10-13 21:48:18 +02:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : synchronize ( ) {
panic_mode = false ;
while ( ! is_at_end ( ) ) {
if ( previous . type = = GDScriptTokenizer : : Token : : NEWLINE | | previous . type = = GDScriptTokenizer : : Token : : SEMICOLON ) {
return ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
switch ( current . type ) {
case GDScriptTokenizer : : Token : : CLASS :
case GDScriptTokenizer : : Token : : FUNC :
case GDScriptTokenizer : : Token : : STATIC :
case GDScriptTokenizer : : Token : : VAR :
case GDScriptTokenizer : : Token : : CONST :
case GDScriptTokenizer : : Token : : SIGNAL :
//case GDScriptTokenizer::Token::IF: // Can also be inside expressions.
case GDScriptTokenizer : : Token : : FOR :
case GDScriptTokenizer : : Token : : WHILE :
case GDScriptTokenizer : : Token : : MATCH :
case GDScriptTokenizer : : Token : : RETURN :
case GDScriptTokenizer : : Token : : ANNOTATION :
return ;
default :
// Do nothing.
break ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
advance ( ) ;
2014-02-10 02:10:30 +01:00
}
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : push_multiline ( bool p_state ) {
multiline_stack . push_back ( p_state ) ;
2024-01-22 15:31:55 +01:00
tokenizer - > set_multiline_mode ( p_state ) ;
2020-05-02 00:14:56 +02:00
if ( p_state ) {
// Consume potential whitespace tokens already waiting in line.
while ( current . type = = GDScriptTokenizer : : Token : : NEWLINE | | current . type = = GDScriptTokenizer : : Token : : INDENT | | current . type = = GDScriptTokenizer : : Token : : DEDENT ) {
2024-01-22 15:31:55 +01:00
current = tokenizer - > scan ( ) ; // Don't call advance() here, as we don't want to change the previous token.
2020-05-02 00:14:56 +02:00
}
}
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : pop_multiline ( ) {
2024-01-19 13:21:39 +01:00
ERR_FAIL_COND_MSG ( multiline_stack . is_empty ( ) , " Parser bug: trying to pop from multiline stack without available value. " ) ;
2020-05-02 00:14:56 +02:00
multiline_stack . pop_back ( ) ;
2024-01-22 15:31:55 +01:00
tokenizer - > set_multiline_mode ( multiline_stack . size ( ) > 0 ? multiline_stack . back ( ) - > get ( ) : false ) ;
2020-05-02 00:14:56 +02:00
}
2021-03-25 14:36:29 +01:00
bool GDScriptParser : : is_statement_end_token ( ) const {
2020-08-18 02:30:39 +02:00
return check ( GDScriptTokenizer : : Token : : NEWLINE ) | | check ( GDScriptTokenizer : : Token : : SEMICOLON ) | | check ( GDScriptTokenizer : : Token : : TK_EOF ) ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2021-03-25 14:36:29 +01:00
bool GDScriptParser : : is_statement_end ( ) const {
return lambda_ended | | in_lambda | | is_statement_end_token ( ) ;
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : end_statement ( const String & p_context ) {
bool found = false ;
2020-08-18 02:30:39 +02:00
while ( is_statement_end ( ) & & ! is_at_end ( ) ) {
2020-05-02 00:14:56 +02:00
// Remove sequential newlines/semicolons.
2021-03-25 14:36:29 +01:00
if ( is_statement_end_token ( ) ) {
// Only consume if this is an actual token.
advance ( ) ;
} else if ( lambda_ended ) {
lambda_ended = false ; // Consume this "token".
found = true ;
break ;
} else {
if ( ! found ) {
lambda_ended = true ; // Mark the lambda as done since we found something else to end the statement.
found = true ;
}
break ;
}
2020-05-02 00:14:56 +02:00
found = true ;
}
2020-08-18 02:30:39 +02:00
if ( ! found & & ! is_at_end ( ) ) {
2020-05-02 00:14:56 +02:00
push_error ( vformat ( R " (Expected end of statement after %s, found " % s " instead.) " , p_context , current . get_name ( ) ) ) ;
}
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : parse_program ( ) {
head = alloc_node < ClassNode > ( ) ;
2024-02-28 15:23:11 +01:00
head - > start_line = 1 ;
head - > end_line = 1 ;
2024-02-26 08:46:24 +01:00
head - > fqcn = GDScript : : canonicalize_path ( script_path ) ;
2020-05-02 00:14:56 +02:00
current_class = head ;
2022-10-23 01:32:21 +02:00
bool can_have_class_or_extends = true ;
2014-02-10 02:10:30 +01:00
2024-02-28 15:23:11 +01:00
# define PUSH_PENDING_ANNOTATIONS_TO_HEAD \
if ( ! annotation_stack . is_empty ( ) ) { \
for ( AnnotationNode * annot : annotation_stack ) { \
head - > annotations . push_back ( annot ) ; \
} \
annotation_stack . clear ( ) ; \
}
2023-02-09 14:16:00 +01:00
while ( ! check ( GDScriptTokenizer : : Token : : TK_EOF ) ) {
if ( match ( GDScriptTokenizer : : Token : : ANNOTATION ) ) {
2024-02-28 15:23:11 +01:00
AnnotationNode * annotation = parse_annotation ( AnnotationInfo : : SCRIPT | AnnotationInfo : : CLASS_LEVEL | AnnotationInfo : : STANDALONE ) ;
2023-02-09 14:16:00 +01:00
if ( annotation ! = nullptr ) {
2024-02-28 15:23:11 +01:00
if ( annotation - > applies_to ( AnnotationInfo : : CLASS ) ) {
// We do not know in advance what the annotation will be applied to: the `head` class or the subsequent inner class.
// If we encounter `class_name`, `extends` or pure `SCRIPT` annotation, then it's `head`, otherwise it's an inner class.
annotation_stack . push_back ( annotation ) ;
} else if ( annotation - > applies_to ( AnnotationInfo : : SCRIPT ) ) {
PUSH_PENDING_ANNOTATIONS_TO_HEAD ;
if ( annotation - > name = = SNAME ( " @tool " ) | | annotation - > name = = SNAME ( " @icon " ) ) {
// Some annotations need to be resolved in the parser.
annotation - > apply ( this , head , nullptr ) ; // `head->outer == nullptr`.
2023-02-09 14:16:00 +01:00
} else {
head - > annotations . push_back ( annotation ) ;
}
2024-02-28 15:23:11 +01:00
} else if ( annotation - > applies_to ( AnnotationInfo : : STANDALONE ) ) {
if ( previous . type ! = GDScriptTokenizer : : Token : : NEWLINE ) {
push_error ( R " (Expected newline after a standalone annotation.) " ) ;
}
if ( annotation - > name = = SNAME ( " @export_category " ) | | annotation - > name = = SNAME ( " @export_group " ) | | annotation - > name = = SNAME ( " @export_subgroup " ) ) {
head - > add_member_group ( annotation ) ;
// This annotation must appear after script-level annotations and `class_name`/`extends`,
// so we stop looking for script-level stuff.
can_have_class_or_extends = false ;
break ;
} else {
// For potential non-group standalone annotations.
push_error ( R " (Unexpected standalone annotation.) " ) ;
}
2023-01-31 15:43:54 +01:00
} else {
2023-02-09 14:16:00 +01:00
annotation_stack . push_back ( annotation ) ;
2024-02-28 15:23:11 +01:00
// This annotation must appear after script-level annotations and `class_name`/`extends`,
2023-02-09 14:16:00 +01:00
// so we stop looking for script-level stuff.
can_have_class_or_extends = false ;
break ;
2023-01-31 15:43:54 +01:00
}
2014-02-10 02:10:30 +01:00
}
2023-02-09 14:16:00 +01:00
} else if ( check ( GDScriptTokenizer : : Token : : LITERAL ) & & current . literal . get_type ( ) = = Variant : : STRING ) {
// Allow strings in class body as multiline comments.
advance ( ) ;
if ( ! match ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
push_error ( " Expected newline after comment string. " ) ;
}
} else {
break ;
2014-02-10 02:10:30 +01:00
}
}
2022-10-23 01:32:21 +02:00
while ( can_have_class_or_extends ) {
2020-05-02 00:14:56 +02:00
// Order here doesn't matter, but there should be only one of each at most.
switch ( current . type ) {
case GDScriptTokenizer : : Token : : CLASS_NAME :
2024-02-28 15:23:11 +01:00
PUSH_PENDING_ANNOTATIONS_TO_HEAD ;
if ( head - > start_line = = 1 ) {
reset_extents ( head , current ) ;
}
2020-05-02 00:14:56 +02:00
advance ( ) ;
if ( head - > identifier ! = nullptr ) {
push_error ( R " ( " class_name " can only be used once.) " ) ;
} else {
parse_class_name ( ) ;
}
break ;
case GDScriptTokenizer : : Token : : EXTENDS :
2024-02-28 15:23:11 +01:00
PUSH_PENDING_ANNOTATIONS_TO_HEAD ;
if ( head - > start_line = = 1 ) {
reset_extents ( head , current ) ;
}
2020-05-02 00:14:56 +02:00
advance ( ) ;
if ( head - > extends_used ) {
push_error ( R " ( " extends " can only be used once.) " ) ;
} else {
parse_extends ( ) ;
2020-07-16 03:02:44 +02:00
end_statement ( " superclass " ) ;
2020-05-02 00:14:56 +02:00
}
break ;
2024-02-28 15:23:11 +01:00
case GDScriptTokenizer : : Token : : TK_EOF :
PUSH_PENDING_ANNOTATIONS_TO_HEAD ;
can_have_class_or_extends = false ;
break ;
2023-02-09 14:16:00 +01:00
case GDScriptTokenizer : : Token : : LITERAL :
if ( current . literal . get_type ( ) = = Variant : : STRING ) {
// Allow strings in class body as multiline comments.
advance ( ) ;
if ( ! match ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
push_error ( " Expected newline after comment string. " ) ;
}
break ;
}
[[fallthrough]] ;
2020-05-02 00:14:56 +02:00
default :
2022-10-23 01:32:21 +02:00
// No tokens are allowed between script annotations and class/extends.
can_have_class_or_extends = false ;
2020-05-02 00:14:56 +02:00
break ;
}
2014-12-17 02:31:57 +01:00
2020-05-02 00:14:56 +02:00
if ( panic_mode ) {
synchronize ( ) ;
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
}
2014-12-17 02:31:57 +01:00
2024-05-24 07:30:16 +02:00
// When the only thing needed is the class name and the icon, we don't need to parse the hole file.
// It really speed up the call to GDScriptLanguage::get_global_class_name especially for large script.
if ( ! parse_body ) {
return ;
}
2024-02-28 15:23:11 +01:00
# undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
2021-09-21 19:13:23 +02:00
parse_class_body ( true ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( head ) ;
2020-05-02 00:14:56 +02:00
2020-11-29 03:37:57 +01:00
# ifdef TOOLS_ENABLED
2024-01-22 15:31:55 +01:00
const HashMap < int , GDScriptTokenizer : : CommentData > & comments = tokenizer - > get_comments ( ) ;
2023-09-15 17:59:30 +02:00
int line = MIN ( max_script_doc_line , head - > end_line ) ;
while ( line > 0 ) {
if ( comments . has ( line ) & & comments [ line ] . new_line & & comments [ line ] . comment . begins_with ( " ## " ) ) {
head - > doc_data = parse_class_doc_comment ( line ) ;
break ;
2020-11-29 03:37:57 +01:00
}
2023-09-15 17:59:30 +02:00
line - - ;
2020-11-29 03:37:57 +01:00
}
2024-01-22 15:31:55 +01:00
2020-11-29 03:37:57 +01:00
# endif // TOOLS_ENABLED
2020-05-02 00:14:56 +02:00
if ( ! check ( GDScriptTokenizer : : Token : : TK_EOF ) ) {
push_error ( " Expected end of file. " ) ;
}
clear_unused_annotations ( ) ;
2014-12-17 02:31:57 +01:00
}
2024-04-13 01:13:25 +02:00
Ref < GDScriptParserRef > GDScriptParser : : get_depended_parser_for ( const String & p_path ) {
Ref < GDScriptParserRef > ref ;
if ( depended_parsers . has ( p_path ) ) {
ref = depended_parsers [ p_path ] ;
} else {
Error err = OK ;
ref = GDScriptCache : : get_parser ( p_path , GDScriptParserRef : : EMPTY , err , script_path ) ;
if ( ref . is_valid ( ) ) {
depended_parsers [ p_path ] = ref ;
}
}
return ref ;
}
const HashMap < String , Ref < GDScriptParserRef > > & GDScriptParser : : get_depended_parsers ( ) {
return depended_parsers ;
}
2022-12-11 03:57:35 +01:00
GDScriptParser : : ClassNode * GDScriptParser : : find_class ( const String & p_qualified_name ) const {
String first = p_qualified_name . get_slice ( " :: " , 0 ) ;
Vector < String > class_names ;
GDScriptParser : : ClassNode * result = nullptr ;
// Empty initial name means start at the head.
if ( first . is_empty ( ) | | ( head - > identifier & & first = = head - > identifier - > name ) ) {
class_names = p_qualified_name . split ( " :: " ) ;
result = head ;
} else if ( p_qualified_name . begins_with ( script_path ) ) {
// Script path could have a class path separator("::") in it.
class_names = p_qualified_name . trim_prefix ( script_path ) . split ( " :: " ) ;
result = head ;
} else if ( head - > has_member ( first ) ) {
class_names = p_qualified_name . split ( " :: " ) ;
GDScriptParser : : ClassNode : : Member member = head - > get_member ( first ) ;
if ( member . type = = GDScriptParser : : ClassNode : : Member : : CLASS ) {
result = member . m_class ;
}
}
// Starts at index 1 because index 0 was handled above.
for ( int i = 1 ; result ! = nullptr & & i < class_names . size ( ) ; i + + ) {
2023-11-18 23:40:56 +01:00
const String & current_name = class_names [ i ] ;
2022-12-11 03:57:35 +01:00
GDScriptParser : : ClassNode * next = nullptr ;
if ( result - > has_member ( current_name ) ) {
GDScriptParser : : ClassNode : : Member member = result - > get_member ( current_name ) ;
if ( member . type = = GDScriptParser : : ClassNode : : Member : : CLASS ) {
next = member . m_class ;
}
}
result = next ;
}
return result ;
}
bool GDScriptParser : : has_class ( const GDScriptParser : : ClassNode * p_class ) const {
if ( head - > fqcn . is_empty ( ) & & p_class - > fqcn . get_slice ( " :: " , 0 ) . is_empty ( ) ) {
return p_class = = head ;
} else if ( p_class - > fqcn . begins_with ( head - > fqcn ) ) {
return find_class ( p_class - > fqcn . trim_prefix ( head - > fqcn ) ) = = p_class ;
}
return false ;
}
2023-04-19 16:10:35 +02:00
GDScriptParser : : ClassNode * GDScriptParser : : parse_class ( bool p_is_static ) {
2020-05-02 00:14:56 +02:00
ClassNode * n_class = alloc_node < ClassNode > ( ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
ClassNode * previous_class = current_class ;
current_class = n_class ;
n_class - > outer = previous_class ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected identifier for the class name after " class " . ) " ) ) {
n_class - > identifier = parse_identifier ( ) ;
2022-11-08 12:51:20 +01:00
if ( n_class - > outer ) {
2022-10-09 18:41:28 +02:00
String fqcn = n_class - > outer - > fqcn ;
if ( fqcn . is_empty ( ) ) {
2024-02-26 08:46:24 +01:00
fqcn = GDScript : : canonicalize_path ( script_path ) ;
2022-10-09 18:41:28 +02:00
}
n_class - > fqcn = fqcn + " :: " + n_class - > identifier - > name ;
} else {
n_class - > fqcn = n_class - > identifier - > name ;
2022-11-08 12:51:20 +01:00
}
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2020-07-16 03:02:44 +02:00
if ( match ( GDScriptTokenizer : : Token : : EXTENDS ) ) {
2020-05-02 00:14:56 +02:00
parse_extends ( ) ;
}
2016-07-22 14:22:34 +02:00
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after class declaration.) " ) ;
2014-02-10 02:10:30 +01:00
2021-09-21 19:13:23 +02:00
bool multiline = match ( GDScriptTokenizer : : Token : : NEWLINE ) ;
if ( multiline & & ! consume ( GDScriptTokenizer : : Token : : INDENT , R " (Expected indented block after class declaration.) " ) ) {
2020-05-02 00:14:56 +02:00
current_class = previous_class ;
2022-07-11 20:31:15 +02:00
complete_extents ( n_class ) ;
2020-05-02 00:14:56 +02:00
return n_class ;
}
2014-04-05 23:50:09 +02:00
2020-08-31 15:27:11 +02:00
if ( match ( GDScriptTokenizer : : Token : : EXTENDS ) ) {
if ( n_class - > extends_used ) {
push_error ( R " (Cannot use " extends " more than once in the same class.) " ) ;
}
parse_extends ( ) ;
end_statement ( " superclass " ) ;
}
2021-09-21 19:13:23 +02:00
parse_class_body ( multiline ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( n_class ) ;
2019-09-02 13:46:38 +02:00
2021-09-21 19:13:23 +02:00
if ( multiline ) {
consume ( GDScriptTokenizer : : Token : : DEDENT , R " (Missing unindent at the end of the class body.) " ) ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
current_class = previous_class ;
return n_class ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : parse_class_name ( ) {
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected identifier for the global class name after " class_name " .) " ) ) {
current_class - > identifier = parse_identifier ( ) ;
2022-11-08 12:51:20 +01:00
current_class - > fqcn = String ( current_class - > identifier - > name ) ;
2020-05-02 00:14:56 +02:00
}
2017-01-08 06:04:53 +01:00
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : EXTENDS ) ) {
// Allow extends on the same line.
parse_extends ( ) ;
2020-07-16 03:02:44 +02:00
end_statement ( " superclass " ) ;
2020-05-02 00:14:56 +02:00
} else {
end_statement ( " class_name statement " ) ;
}
}
2017-01-08 06:04:53 +01:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : parse_extends ( ) {
current_class - > extends_used = true ;
2017-01-08 06:04:53 +01:00
2020-07-06 17:24:24 +02:00
int chain_index = 0 ;
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : LITERAL ) ) {
if ( previous . literal . get_type ( ) ! = Variant : : STRING ) {
push_error ( vformat ( R " (Only strings or identifiers can be used after " extends " , found " % s " instead.) " , Variant : : get_type_name ( previous . literal . get_type ( ) ) ) ) ;
}
current_class - > extends_path = previous . literal ;
2017-03-31 19:28:34 +02:00
2020-05-02 00:14:56 +02:00
if ( ! match ( GDScriptTokenizer : : Token : : PERIOD ) ) {
return ;
}
}
2017-01-08 06:04:53 +01:00
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_INHERIT_TYPE , current_class , chain_index + + ) ;
2020-05-02 00:14:56 +02:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected superclass name after " extends " .) " ) ) {
return ;
}
2023-03-08 21:06:29 +01:00
current_class - > extends . push_back ( parse_identifier ( ) ) ;
2017-01-08 06:04:53 +01:00
2020-05-02 00:14:56 +02:00
while ( match ( GDScriptTokenizer : : Token : : PERIOD ) ) {
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_INHERIT_TYPE , current_class , chain_index + + ) ;
2020-05-02 00:14:56 +02:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected superclass name after " . " .) " ) ) {
return ;
}
2023-03-08 21:06:29 +01:00
current_class - > extends . push_back ( parse_identifier ( ) ) ;
2020-05-02 00:14:56 +02:00
}
}
2017-02-16 14:29:18 +01:00
2024-03-08 03:29:49 +01:00
template < typename T >
2023-04-19 16:10:35 +02:00
void GDScriptParser : : parse_class_member ( T * ( GDScriptParser : : * p_parse_function ) ( bool ) , AnnotationInfo : : TargetKind p_target , const String & p_member_kind , bool p_is_static ) {
2020-05-02 00:14:56 +02:00
advance ( ) ;
2022-01-04 13:32:43 +01:00
2020-05-02 00:14:56 +02:00
// Consume annotations.
2022-01-04 13:32:43 +01:00
List < AnnotationNode * > annotations ;
2020-12-15 13:04:21 +01:00
while ( ! annotation_stack . is_empty ( ) ) {
2020-05-02 00:14:56 +02:00
AnnotationNode * last_annotation = annotation_stack . back ( ) - > get ( ) ;
if ( last_annotation - > applies_to ( p_target ) ) {
2022-01-04 13:32:43 +01:00
annotations . push_front ( last_annotation ) ;
2020-05-02 00:14:56 +02:00
annotation_stack . pop_back ( ) ;
} else {
push_error ( vformat ( R " (Annotation " % s " cannot be applied to a %s.) " , last_annotation - > name , p_member_kind ) ) ;
clear_unused_annotations ( ) ;
}
}
2020-11-29 03:37:57 +01:00
2023-04-19 16:10:35 +02:00
T * member = ( this - > * p_parse_function ) ( p_is_static ) ;
2022-01-04 13:32:43 +01:00
if ( member = = nullptr ) {
return ;
}
2023-09-15 17:59:30 +02:00
# ifdef TOOLS_ENABLED
int doc_comment_line = member - > start_line - 1 ;
# endif // TOOLS_ENABLED
2022-01-04 13:32:43 +01:00
for ( AnnotationNode * & annotation : annotations ) {
member - > annotations . push_back ( annotation ) ;
2023-09-15 17:59:30 +02:00
# ifdef TOOLS_ENABLED
if ( annotation - > start_line < = doc_comment_line ) {
doc_comment_line = annotation - > start_line - 1 ;
}
# endif // TOOLS_ENABLED
2022-01-04 13:32:43 +01:00
}
2020-11-29 03:37:57 +01:00
# ifdef TOOLS_ENABLED
2023-09-15 17:59:30 +02:00
if constexpr ( std : : is_same_v < T , ClassNode > ) {
if ( has_comment ( member - > start_line , true ) ) {
// Inline doc comment.
member - > doc_data = parse_class_doc_comment ( member - > start_line , true ) ;
2024-01-22 15:31:55 +01:00
} else if ( has_comment ( doc_comment_line , true ) & & tokenizer - > get_comments ( ) [ doc_comment_line ] . new_line ) {
2023-09-15 17:59:30 +02:00
// Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members.
// This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice.
member - > doc_data = parse_class_doc_comment ( doc_comment_line ) ;
2023-07-02 12:13:38 +02:00
}
2023-09-15 17:59:30 +02:00
} else {
if ( has_comment ( member - > start_line , true ) ) {
// Inline doc comment.
member - > doc_data = parse_doc_comment ( member - > start_line , true ) ;
2024-01-22 15:31:55 +01:00
} else if ( doc_comment_line > = min_member_doc_line & & has_comment ( doc_comment_line , true ) & & tokenizer - > get_comments ( ) [ doc_comment_line ] . new_line ) {
2023-09-15 17:59:30 +02:00
// Normal doc comment.
2023-07-02 12:13:38 +02:00
member - > doc_data = parse_doc_comment ( doc_comment_line ) ;
2020-11-29 03:37:57 +01:00
}
}
2023-09-15 17:59:30 +02:00
min_member_doc_line = member - > end_line + 1 ; // Prevent multiple members from using the same doc comment.
2020-11-29 03:37:57 +01:00
# endif // TOOLS_ENABLED
2020-05-02 00:14:56 +02:00
if ( member - > identifier ! = nullptr ) {
2021-11-06 19:12:19 +01:00
if ( ! ( ( String ) member - > identifier - > name ) . is_empty ( ) ) { // Enums may be unnamed.
if ( current_class - > members_indices . has ( member - > identifier - > name ) ) {
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 ) ;
} else {
current_class - > add_member ( member ) ;
}
2020-05-02 00:14:56 +02:00
} else {
current_class - > add_member ( member ) ;
}
}
}
2017-02-16 14:29:18 +01:00
2021-09-21 19:13:23 +02:00
void GDScriptParser : : parse_class_body ( bool p_is_multiline ) {
2020-05-02 00:14:56 +02:00
bool class_end = false ;
2023-04-19 16:10:35 +02:00
bool next_is_static = false ;
2020-05-02 00:14:56 +02:00
while ( ! class_end & & ! is_at_end ( ) ) {
2023-04-19 16:10:35 +02:00
GDScriptTokenizer : : Token token = current ;
switch ( token . type ) {
2020-05-02 00:14:56 +02:00
case GDScriptTokenizer : : Token : : VAR :
2023-04-19 16:10:35 +02:00
parse_class_member ( & GDScriptParser : : parse_variable , AnnotationInfo : : VARIABLE , " variable " , next_is_static ) ;
if ( next_is_static ) {
current_class - > has_static_data = true ;
}
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : CONST :
parse_class_member ( & GDScriptParser : : parse_constant , AnnotationInfo : : CONSTANT , " constant " ) ;
break ;
case GDScriptTokenizer : : Token : : SIGNAL :
parse_class_member ( & GDScriptParser : : parse_signal , AnnotationInfo : : SIGNAL , " signal " ) ;
break ;
case GDScriptTokenizer : : Token : : FUNC :
2023-04-19 16:10:35 +02:00
parse_class_member ( & GDScriptParser : : parse_function , AnnotationInfo : : FUNCTION , " function " , next_is_static ) ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : CLASS :
parse_class_member ( & GDScriptParser : : parse_class , AnnotationInfo : : CLASS , " class " ) ;
break ;
case GDScriptTokenizer : : Token : : ENUM :
parse_class_member ( & GDScriptParser : : parse_enum , AnnotationInfo : : NONE , " enum " ) ;
break ;
2023-04-19 16:10:35 +02:00
case GDScriptTokenizer : : Token : : STATIC : {
advance ( ) ;
next_is_static = true ;
if ( ! check ( GDScriptTokenizer : : Token : : FUNC ) & & ! check ( GDScriptTokenizer : : Token : : VAR ) ) {
push_error ( R " (Expected " func " or " var " after " static " .) " ) ;
}
} break ;
2020-05-02 00:14:56 +02:00
case GDScriptTokenizer : : Token : : ANNOTATION : {
advance ( ) ;
2022-07-03 21:30:08 +02:00
2024-02-28 15:23:11 +01:00
// Check for class-level and standalone annotations.
AnnotationNode * annotation = parse_annotation ( AnnotationInfo : : CLASS_LEVEL | AnnotationInfo : : STANDALONE ) ;
2020-05-02 00:14:56 +02:00
if ( annotation ! = nullptr ) {
2022-07-03 21:30:08 +02:00
if ( annotation - > applies_to ( AnnotationInfo : : STANDALONE ) ) {
if ( previous . type ! = GDScriptTokenizer : : Token : : NEWLINE ) {
push_error ( R " (Expected newline after a standalone annotation.) " ) ;
}
2023-01-31 15:43:54 +01:00
if ( annotation - > name = = SNAME ( " @export_category " ) | | annotation - > name = = SNAME ( " @export_group " ) | | annotation - > name = = SNAME ( " @export_subgroup " ) ) {
2023-01-27 14:25:15 +01:00
current_class - > add_member_group ( annotation ) ;
} else {
// For potential non-group standalone annotations.
2024-02-28 15:23:11 +01:00
push_error ( R " (Unexpected standalone annotation.) " ) ;
2023-01-27 14:25:15 +01:00
}
2024-02-28 15:23:11 +01:00
} else { // `AnnotationInfo::CLASS_LEVEL`.
2022-07-03 21:30:08 +02:00
annotation_stack . push_back ( annotation ) ;
}
2017-02-16 14:29:18 +01:00
}
2020-05-02 00:14:56 +02:00
break ;
2016-01-24 23:45:11 +01:00
}
2020-05-02 00:14:56 +02:00
case GDScriptTokenizer : : Token : : PASS :
2020-07-16 03:02:44 +02:00
advance ( ) ;
2020-05-02 00:14:56 +02:00
end_statement ( R " ( " pass " ) " ) ;
break ;
case GDScriptTokenizer : : Token : : DEDENT :
class_end = true ;
break ;
2023-02-09 14:16:00 +01:00
case GDScriptTokenizer : : Token : : LITERAL :
if ( current . literal . get_type ( ) = = Variant : : STRING ) {
// Allow strings in class body as multiline comments.
advance ( ) ;
if ( ! match ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
push_error ( " Expected newline after comment string. " ) ;
}
break ;
}
[[fallthrough]] ;
2020-05-02 00:14:56 +02:00
default :
2021-12-30 11:25:02 +01:00
// Display a completion with identifiers.
make_completion_context ( COMPLETION_IDENTIFIER , nullptr ) ;
2020-05-02 00:14:56 +02:00
push_error ( vformat ( R " (Unexpected " % s " in class body.) " , current . get_name ( ) ) ) ;
advance ( ) ;
break ;
}
2023-04-19 16:10:35 +02:00
if ( token . type ! = GDScriptTokenizer : : Token : : STATIC ) {
next_is_static = false ;
}
2020-05-02 00:14:56 +02:00
if ( panic_mode ) {
synchronize ( ) ;
}
2021-09-21 19:13:23 +02:00
if ( ! p_is_multiline ) {
class_end = true ;
}
2020-05-02 00:14:56 +02:00
}
}
2017-02-16 14:29:18 +01:00
2023-04-19 16:10:35 +02:00
GDScriptParser : : VariableNode * GDScriptParser : : parse_variable ( bool p_is_static ) {
return parse_variable ( p_is_static , true ) ;
2020-06-01 21:41:05 +02:00
}
2023-04-19 16:10:35 +02:00
GDScriptParser : : VariableNode * GDScriptParser : : parse_variable ( bool p_is_static , bool p_allow_property ) {
2022-07-11 20:31:15 +02:00
VariableNode * variable = alloc_node < VariableNode > ( ) ;
2020-05-02 00:14:56 +02:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected variable name after " var " .) " ) ) {
2022-07-11 20:31:15 +02:00
complete_extents ( variable ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
}
2017-02-16 14:29:18 +01:00
2021-12-13 09:39:16 +01:00
variable - > identifier = parse_identifier ( ) ;
variable - > export_info . name = variable - > identifier - > name ;
2023-04-19 16:10:35 +02:00
variable - > is_static = p_is_static ;
2014-12-07 06:04:20 +01:00
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : COLON ) ) {
2020-06-01 21:41:05 +02:00
if ( check ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
if ( p_allow_property ) {
advance ( ) ;
return parse_property ( variable , true ) ;
} else {
push_error ( R " (Expected type after " : " ) " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( variable ) ;
2020-06-01 21:41:05 +02:00
return nullptr ;
}
} else if ( check ( ( GDScriptTokenizer : : Token : : EQUAL ) ) ) {
2020-05-02 00:14:56 +02:00
// Infer type.
variable - > infer_datatype = true ;
} else {
2020-07-06 17:24:24 +02:00
if ( p_allow_property ) {
make_completion_context ( COMPLETION_PROPERTY_DECLARATION_OR_TYPE , variable ) ;
if ( check ( GDScriptTokenizer : : Token : : IDENTIFIER ) ) {
// Check if get or set.
if ( current . get_identifier ( ) = = " get " | | current . get_identifier ( ) = = " set " ) {
return parse_property ( variable , false ) ;
}
2020-06-01 21:41:05 +02:00
}
}
2020-05-02 00:14:56 +02:00
// Parse type.
variable - > datatype_specifier = parse_type ( ) ;
}
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : EQUAL ) ) {
// Initializer.
variable - > initializer = parse_expression ( false ) ;
2021-03-17 15:09:57 +01:00
if ( variable - > initializer = = nullptr ) {
push_error ( R " (Expected expression for variable initial value after " = " .) " ) ;
}
2020-06-12 00:31:28 +02:00
variable - > assignments + + ;
2020-05-02 00:14:56 +02:00
}
2018-05-30 04:16:54 +02:00
2020-06-01 21:41:05 +02:00
if ( p_allow_property & & match ( GDScriptTokenizer : : Token : : COLON ) ) {
if ( match ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
return parse_property ( variable , true ) ;
} else {
return parse_property ( variable , false ) ;
}
}
2022-07-11 20:31:15 +02:00
complete_extents ( variable ) ;
2020-05-02 00:14:56 +02:00
end_statement ( " variable declaration " ) ;
2018-05-30 04:16:54 +02:00
2020-05-02 00:14:56 +02:00
return variable ;
}
2014-02-10 02:10:30 +01:00
2020-06-01 21:41:05 +02:00
GDScriptParser : : VariableNode * GDScriptParser : : parse_property ( VariableNode * p_variable , bool p_need_indent ) {
if ( p_need_indent ) {
if ( ! consume ( GDScriptTokenizer : : Token : : INDENT , R " (Expected indented block for property after " : " .) " ) ) {
2022-07-11 20:31:15 +02:00
complete_extents ( p_variable ) ;
2020-06-01 21:41:05 +02:00
return nullptr ;
}
}
2020-07-06 17:24:24 +02:00
VariableNode * property = p_variable ;
make_completion_context ( COMPLETION_PROPERTY_DECLARATION , property ) ;
2020-06-01 21:41:05 +02:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected " get " or " set " for property declaration.) " ) ) {
2022-07-11 20:31:15 +02:00
complete_extents ( p_variable ) ;
2020-06-01 21:41:05 +02:00
return nullptr ;
}
IdentifierNode * function = parse_identifier ( ) ;
if ( check ( GDScriptTokenizer : : Token : : EQUAL ) ) {
p_variable - > property = VariableNode : : PROP_SETGET ;
} else {
p_variable - > property = VariableNode : : PROP_INLINE ;
if ( ! p_need_indent ) {
push_error ( " Property with inline code must go to an indented block. " ) ;
}
}
bool getter_used = false ;
bool setter_used = false ;
// Run with a loop because order doesn't matter.
for ( int i = 0 ; i < 2 ; i + + ) {
2022-11-27 08:56:53 +01:00
if ( function - > name = = SNAME ( " set " ) ) {
2020-06-01 21:41:05 +02:00
if ( setter_used ) {
push_error ( R " (Properties can only have one setter.) " ) ;
} else {
parse_property_setter ( property ) ;
setter_used = true ;
}
2022-11-27 08:56:53 +01:00
} else if ( function - > name = = SNAME ( " get " ) ) {
2020-06-01 21:41:05 +02:00
if ( getter_used ) {
push_error ( R " (Properties can only have one getter.) " ) ;
} else {
parse_property_getter ( property ) ;
getter_used = true ;
}
} else {
// TODO: Update message to only have the missing one if it's the case.
push_error ( R " (Expected " get " or " set " for property declaration.) " ) ;
}
if ( i = = 0 & & p_variable - > property = = VariableNode : : PROP_SETGET ) {
if ( match ( GDScriptTokenizer : : Token : : COMMA ) ) {
// Consume potential newline.
if ( match ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
if ( ! p_need_indent ) {
push_error ( R " (Inline setter/getter setting cannot span across multiple lines (use " \ \ " " if needed ) . ) " );
}
}
} else {
break ;
}
}
if ( ! match ( GDScriptTokenizer : : Token : : IDENTIFIER ) ) {
break ;
}
function = parse_identifier ( ) ;
}
2022-07-11 20:31:15 +02:00
complete_extents ( p_variable ) ;
2020-06-01 21:41:05 +02:00
if ( p_variable - > property = = VariableNode : : PROP_SETGET ) {
end_statement ( " property declaration " ) ;
}
if ( p_need_indent ) {
consume ( GDScriptTokenizer : : Token : : DEDENT , R " (Expected end of indented block for property.) " ) ;
}
return property ;
}
void GDScriptParser : : parse_property_setter ( VariableNode * p_variable ) {
switch ( p_variable - > property ) {
2021-09-06 07:04:43 +02:00
case VariableNode : : PROP_INLINE : {
2022-07-11 20:31:15 +02:00
FunctionNode * function = alloc_node < FunctionNode > ( ) ;
2021-09-06 07:04:43 +02:00
IdentifierNode * identifier = alloc_node < IdentifierNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( identifier ) ;
2021-09-06 07:04:43 +02:00
identifier - > name = " @ " + p_variable - > identifier - > name + " _setter " ;
function - > identifier = identifier ;
2023-04-19 16:10:35 +02:00
function - > is_static = p_variable - > is_static ;
2021-09-06 07:04:43 +02:00
2022-07-11 20:31:15 +02:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected " ( " after " set " .) " ) ;
2021-09-06 07:04:43 +02:00
ParameterNode * parameter = alloc_node < ParameterNode > ( ) ;
2022-07-11 20:31:15 +02:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected parameter name after " ( " .) " ) ) {
reset_extents ( parameter , previous ) ;
p_variable - > setter_parameter = parse_identifier ( ) ;
parameter - > identifier = p_variable - > setter_parameter ;
2021-10-15 20:53:24 +02:00
function - > parameters_indices [ parameter - > identifier - > name ] = 0 ;
function - > parameters . push_back ( parameter ) ;
2022-07-11 20:31:15 +02:00
}
complete_extents ( parameter ) ;
2021-09-06 07:04:43 +02:00
2022-07-11 20:31:15 +02:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected " ) " after parameter name.)* " ) ;
consume ( GDScriptTokenizer : : Token : : COLON , R " *(Expected " : " after " ) " .)* " ) ;
FunctionNode * previous_function = current_function ;
current_function = function ;
if ( p_variable - > setter_parameter ! = nullptr ) {
2021-10-15 20:53:24 +02:00
SuiteNode * body = alloc_node < SuiteNode > ( ) ;
body - > add_local ( parameter , function ) ;
function - > body = parse_suite ( " setter declaration " , body ) ;
p_variable - > setter = function ;
}
2021-09-06 07:04:43 +02:00
current_function = previous_function ;
2022-07-11 20:31:15 +02:00
complete_extents ( function ) ;
2021-09-06 07:04:43 +02:00
break ;
}
2020-06-01 21:41:05 +02:00
case VariableNode : : PROP_SETGET :
consume ( GDScriptTokenizer : : Token : : EQUAL , R " (Expected " = " after " set " ) " ) ;
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_PROPERTY_METHOD , p_variable ) ;
2020-06-01 21:41:05 +02:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected setter function name after " = " .) " ) ) {
p_variable - > setter_pointer = parse_identifier ( ) ;
}
break ;
case VariableNode : : PROP_NONE :
break ; // Unreachable.
}
}
void GDScriptParser : : parse_property_getter ( VariableNode * p_variable ) {
switch ( p_variable - > property ) {
2021-09-06 07:04:43 +02:00
case VariableNode : : PROP_INLINE : {
2022-07-11 20:31:15 +02:00
FunctionNode * function = alloc_node < FunctionNode > ( ) ;
2023-10-10 20:19:15 +02:00
if ( match ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN ) ) {
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected " ) " after " get ( " .)* " ) ;
consume ( GDScriptTokenizer : : Token : : COLON , R " *(Expected " : " after " get ( ) " .)* " ) ;
} else {
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " or " ( " after " get " .) " ) ;
}
2020-06-01 21:41:05 +02:00
2021-09-06 07:04:43 +02:00
IdentifierNode * identifier = alloc_node < IdentifierNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( identifier ) ;
2021-09-06 07:04:43 +02:00
identifier - > name = " @ " + p_variable - > identifier - > name + " _getter " ;
function - > identifier = identifier ;
2023-04-19 16:10:35 +02:00
function - > is_static = p_variable - > is_static ;
2021-09-06 07:04:43 +02:00
FunctionNode * previous_function = current_function ;
current_function = function ;
SuiteNode * body = alloc_node < SuiteNode > ( ) ;
function - > body = parse_suite ( " getter declaration " , body ) ;
p_variable - > getter = function ;
2022-07-11 20:31:15 +02:00
2021-09-06 07:04:43 +02:00
current_function = previous_function ;
2022-07-11 20:31:15 +02:00
complete_extents ( function ) ;
2020-06-01 21:41:05 +02:00
break ;
2021-09-06 07:04:43 +02:00
}
2020-06-01 21:41:05 +02:00
case VariableNode : : PROP_SETGET :
consume ( GDScriptTokenizer : : Token : : EQUAL , R " (Expected " = " after " get " ) " ) ;
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_PROPERTY_METHOD , p_variable ) ;
2020-06-01 21:41:05 +02:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected getter function name after " = " .) " ) ) {
p_variable - > getter_pointer = parse_identifier ( ) ;
}
break ;
case VariableNode : : PROP_NONE :
break ; // Unreachable.
}
}
2023-04-19 16:10:35 +02:00
GDScriptParser : : ConstantNode * GDScriptParser : : parse_constant ( bool p_is_static ) {
2022-07-11 20:31:15 +02:00
ConstantNode * constant = alloc_node < ConstantNode > ( ) ;
2020-05-02 00:14:56 +02:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected constant name after " const " .) " ) ) {
2023-07-10 19:10:57 +02:00
complete_extents ( constant ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
}
2018-05-30 04:16:54 +02:00
2020-05-02 00:14:56 +02:00
constant - > identifier = parse_identifier ( ) ;
2014-09-15 16:33:30 +02:00
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : COLON ) ) {
if ( check ( ( GDScriptTokenizer : : Token : : EQUAL ) ) ) {
// Infer type.
constant - > infer_datatype = true ;
} else {
// Parse type.
constant - > datatype_specifier = parse_type ( ) ;
}
}
2014-09-15 16:33:30 +02:00
2020-05-02 00:14:56 +02:00
if ( consume ( GDScriptTokenizer : : Token : : EQUAL , R " (Expected initializer after constant name.) " ) ) {
// Initializer.
constant - > initializer = parse_expression ( false ) ;
2014-09-15 16:33:30 +02:00
2020-05-02 00:14:56 +02:00
if ( constant - > initializer = = nullptr ) {
push_error ( R " (Expected initializer expression for constant.) " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( constant ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
}
2020-12-30 18:57:46 +01:00
} else {
2022-07-11 20:31:15 +02:00
complete_extents ( constant ) ;
2020-12-30 18:57:46 +01:00
return nullptr ;
2020-05-02 00:14:56 +02:00
}
2014-09-15 16:33:30 +02:00
2022-07-11 20:31:15 +02:00
complete_extents ( constant ) ;
2020-05-02 00:14:56 +02:00
end_statement ( " constant declaration " ) ;
2016-10-03 20:18:21 +02:00
2020-05-02 00:14:56 +02:00
return constant ;
}
2016-10-03 20:18:21 +02:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ParameterNode * GDScriptParser : : parse_parameter ( ) {
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected parameter name.) " ) ) {
return nullptr ;
}
2014-09-15 16:33:30 +02:00
2020-05-02 00:14:56 +02:00
ParameterNode * parameter = alloc_node < ParameterNode > ( ) ;
2021-12-13 09:39:16 +01:00
parameter - > identifier = parse_identifier ( ) ;
2014-09-15 16:33:30 +02:00
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : COLON ) ) {
if ( check ( ( GDScriptTokenizer : : Token : : EQUAL ) ) ) {
// Infer type.
parameter - > infer_datatype = true ;
} else {
// Parse type.
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_TYPE_NAME , parameter ) ;
2020-05-02 00:14:56 +02:00
parameter - > datatype_specifier = parse_type ( ) ;
}
}
2016-08-07 03:11:03 +02:00
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : EQUAL ) ) {
// Default value.
2022-12-22 21:43:36 +01:00
parameter - > initializer = parse_expression ( false ) ;
2020-05-02 00:14:56 +02:00
}
2014-09-15 16:33:30 +02:00
2022-07-11 20:31:15 +02:00
complete_extents ( parameter ) ;
2020-05-02 00:14:56 +02:00
return parameter ;
}
2014-09-15 16:33:30 +02:00
2023-04-19 16:10:35 +02:00
GDScriptParser : : SignalNode * GDScriptParser : : parse_signal ( bool p_is_static ) {
2022-07-11 20:31:15 +02:00
SignalNode * signal = alloc_node < SignalNode > ( ) ;
2020-05-02 00:14:56 +02:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected signal name after " signal " .) " ) ) {
2022-07-11 20:31:15 +02:00
complete_extents ( signal ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
}
2016-10-03 20:18:21 +02:00
2020-05-02 00:14:56 +02:00
signal - > identifier = parse_identifier ( ) ;
2014-09-15 16:33:30 +02:00
2021-09-21 18:40:39 +02:00
if ( check ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN ) ) {
push_multiline ( true ) ;
advance ( ) ;
2020-08-19 16:32:48 +02:00
do {
if ( check ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE ) ) {
// Allow for trailing comma.
break ;
}
2020-05-02 00:14:56 +02:00
ParameterNode * parameter = parse_parameter ( ) ;
if ( parameter = = nullptr ) {
2020-08-19 16:32:48 +02:00
push_error ( " Expected signal parameter name. " ) ;
2020-05-02 00:14:56 +02:00
break ;
2014-09-15 16:33:30 +02:00
}
2022-12-22 21:43:36 +01:00
if ( parameter - > initializer ! = nullptr ) {
2020-05-02 00:14:56 +02:00
push_error ( R " (Signal parameters cannot have a default value.) " ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
if ( signal - > parameters_indices . has ( parameter - > identifier - > name ) ) {
push_error ( vformat ( R " (Parameter with name " % s " was already declared for this signal.) " , parameter - > identifier - > name ) ) ;
} else {
signal - > parameters_indices [ parameter - > identifier - > name ] = signal - > parameters . size ( ) ;
signal - > parameters . push_back ( parameter ) ;
2014-12-17 02:31:57 +01:00
}
2020-08-19 16:32:48 +02:00
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) & & ! is_at_end ( ) ) ;
2021-09-21 18:40:39 +02:00
pop_multiline ( ) ;
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected closing " ) " after signal parameters.)* " ) ;
}
2014-12-17 02:31:57 +01:00
2022-07-11 20:31:15 +02:00
complete_extents ( signal ) ;
2020-05-02 00:14:56 +02:00
end_statement ( " signal declaration " ) ;
2018-01-18 22:03:34 +01:00
2020-05-02 00:14:56 +02:00
return signal ;
}
2018-01-18 22:03:34 +01:00
2023-04-19 16:10:35 +02:00
GDScriptParser : : EnumNode * GDScriptParser : : parse_enum ( bool p_is_static ) {
2020-05-02 00:14:56 +02:00
EnumNode * enum_node = alloc_node < EnumNode > ( ) ;
bool named = false ;
2018-01-18 22:03:34 +01:00
2023-10-10 20:19:15 +02:00
if ( match ( GDScriptTokenizer : : Token : : IDENTIFIER ) ) {
2020-05-02 00:14:56 +02:00
enum_node - > identifier = parse_identifier ( ) ;
named = true ;
}
2018-01-18 22:03:34 +01:00
2020-05-02 00:14:56 +02:00
push_multiline ( true ) ;
consume ( GDScriptTokenizer : : Token : : BRACE_OPEN , vformat ( R " (Expected " { " after %s.) " , named ? " enum name " : R " ( " enum " ) " ) ) ;
2023-09-15 17:59:30 +02:00
# ifdef TOOLS_ENABLED
int min_enum_value_doc_line = previous . end_line + 1 ;
# endif
2014-02-10 02:10:30 +01:00
2020-08-19 16:14:16 +02:00
HashMap < StringName , int > elements ;
2021-11-13 16:40:53 +01:00
# ifdef DEBUG_ENABLED
2021-11-06 19:12:19 +01:00
List < MethodInfo > gdscript_funcs ;
GDScriptLanguage : : get_singleton ( ) - > get_public_functions ( & gdscript_funcs ) ;
2021-11-13 16:40:53 +01:00
# endif
2021-11-06 19:12:19 +01:00
2020-05-02 00:14:56 +02:00
do {
if ( check ( GDScriptTokenizer : : Token : : BRACE_CLOSE ) ) {
break ; // Allow trailing comma.
}
2020-09-18 13:35:51 +02:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected identifier for enum key.) " ) ) {
2021-11-06 19:12:19 +01:00
GDScriptParser : : IdentifierNode * identifier = parse_identifier ( ) ;
2024-02-28 15:23:11 +01:00
EnumNode : : Value item ;
2021-11-06 19:12:19 +01:00
item . identifier = identifier ;
2020-08-18 22:44:20 +02:00
item . parent_enum = enum_node ;
item . line = previous . start_line ;
item . leftmost_column = previous . leftmost_column ;
2020-11-29 03:37:57 +01:00
item . rightmost_column = previous . rightmost_column ;
2020-05-02 00:14:56 +02:00
2020-08-19 16:14:16 +02:00
if ( elements . has ( item . identifier - > name ) ) {
push_error ( vformat ( R " (Name " % s " was already in this enum (at line %d).) " , item . identifier - > name , elements [ item . identifier - > name ] ) , item . identifier ) ;
} else if ( ! named ) {
2022-12-29 20:48:04 +01:00
if ( current_class - > members_indices . has ( item . identifier - > name ) ) {
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 ( ) ) ) ;
2018-01-18 22:03:34 +01:00
}
}
2014-12-17 02:31:57 +01:00
2020-08-19 16:14:16 +02:00
elements [ item . identifier - > name ] = item . line ;
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : EQUAL ) ) {
2020-08-18 22:44:20 +02:00
ExpressionNode * value = parse_expression ( false ) ;
if ( value = = nullptr ) {
push_error ( R " (Expected expression value after " = " .) " ) ;
2017-11-17 05:42:24 +01:00
}
2020-08-18 22:44:20 +02:00
item . custom_value = value ;
2020-05-02 00:14:56 +02:00
}
2020-08-18 22:44:20 +02:00
item . index = enum_node - > values . size ( ) ;
2020-05-02 00:14:56 +02:00
enum_node - > values . push_back ( item ) ;
2020-06-12 02:49:58 +02:00
if ( ! named ) {
2020-05-02 00:14:56 +02:00
// Add as member of current class.
current_class - > add_member ( item ) ;
}
}
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
2014-02-10 02:10:30 +01:00
2020-11-29 03:37:57 +01:00
# ifdef TOOLS_ENABLED
2021-05-20 12:07:26 +02:00
// Enum values documentation.
2020-11-29 03:37:57 +01:00
for ( int i = 0 ; i < enum_node - > values . size ( ) ; i + + ) {
2023-09-15 17:59:30 +02:00
int enum_value_line = enum_node - > values [ i ] . line ;
int doc_comment_line = enum_value_line - 1 ;
2023-07-02 12:13:38 +02:00
2023-09-15 17:59:30 +02:00
MemberDocData doc_data ;
if ( has_comment ( enum_value_line , true ) ) {
// Inline doc comment.
if ( i = = enum_node - > values . size ( ) - 1 | | enum_node - > values [ i + 1 ] . line > enum_value_line ) {
doc_data = parse_doc_comment ( enum_value_line , true ) ;
2020-11-29 03:37:57 +01:00
}
2024-01-22 15:31:55 +01:00
} else if ( doc_comment_line > = min_enum_value_doc_line & & has_comment ( doc_comment_line , true ) & & tokenizer - > get_comments ( ) [ doc_comment_line ] . new_line ) {
2023-09-15 17:59:30 +02:00
// Normal doc comment.
doc_data = parse_doc_comment ( doc_comment_line ) ;
2020-11-29 03:37:57 +01:00
}
2023-07-02 12:13:38 +02:00
if ( named ) {
2023-09-15 17:59:30 +02:00
enum_node - > values . write [ i ] . doc_data = doc_data ;
2023-07-02 12:13:38 +02:00
} else {
2023-09-15 17:59:30 +02:00
current_class - > set_enum_value_doc_data ( enum_node - > values [ i ] . identifier - > name , doc_data ) ;
2023-07-02 12:13:38 +02:00
}
2023-09-15 17:59:30 +02:00
min_enum_value_doc_line = enum_value_line + 1 ; // Prevent multiple enum values from using the same doc comment.
2020-11-29 03:37:57 +01:00
}
# endif // TOOLS_ENABLED
2023-09-15 17:59:30 +02:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : BRACE_CLOSE , R " (Expected closing " } " for enum.) " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( enum_node ) ;
2020-05-02 00:14:56 +02:00
end_statement ( " enum " ) ;
2014-12-17 02:31:57 +01:00
2020-05-02 00:14:56 +02:00
return enum_node ;
}
2014-02-10 02:10:30 +01:00
2021-03-25 14:36:29 +01:00
void GDScriptParser : : parse_function_signature ( FunctionNode * p_function , SuiteNode * p_body , const String & p_type ) {
2020-05-02 00:14:56 +02:00
if ( ! check ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE ) & & ! is_at_end ( ) ) {
bool default_used = false ;
do {
if ( check ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE ) ) {
// Allow for trailing comma.
break ;
}
ParameterNode * parameter = parse_parameter ( ) ;
if ( parameter = = nullptr ) {
break ;
2014-12-17 02:31:57 +01:00
}
2022-12-22 21:43:36 +01:00
if ( parameter - > initializer ! = nullptr ) {
2020-05-02 00:14:56 +02:00
default_used = true ;
} else {
if ( default_used ) {
2023-04-06 08:04:22 +02:00
push_error ( " Cannot have mandatory parameters after optional parameters. " ) ;
2020-05-02 00:14:56 +02:00
continue ;
2020-05-14 16:41:43 +02:00
}
2017-11-17 05:42:24 +01:00
}
2021-03-25 14:36:29 +01:00
if ( p_function - > parameters_indices . has ( parameter - > identifier - > name ) ) {
push_error ( vformat ( R " (Parameter with name " % s " was already declared for this %s.) " , parameter - > identifier - > name , p_type ) ) ;
2020-05-02 00:14:56 +02:00
} else {
2021-03-25 14:36:29 +01:00
p_function - > parameters_indices [ parameter - > identifier - > name ] = p_function - > parameters . size ( ) ;
p_function - > parameters . push_back ( parameter ) ;
2021-03-26 13:03:16 +01:00
p_body - > add_local ( parameter , current_function ) ;
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
}
2014-12-17 02:31:57 +01:00
2020-05-02 00:14:56 +02:00
pop_multiline ( ) ;
2021-03-25 14:36:29 +01:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , vformat ( R " *(Expected closing " ) " after %s parameters.)* " , p_type ) ) ;
2014-12-17 02:31:57 +01:00
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : FORWARD_ARROW ) ) {
2021-03-25 14:36:29 +01:00
make_completion_context ( COMPLETION_TYPE_NAME_OR_VOID , p_function ) ;
p_function - > return_type = parse_type ( true ) ;
if ( p_function - > return_type = = nullptr ) {
2020-08-19 19:08:52 +02:00
push_error ( R " (Expected return type or " void " after " - > " .) " ) ;
}
2020-05-02 00:14:56 +02:00
}
2014-12-17 02:31:57 +01:00
2023-04-19 16:10:35 +02:00
if ( ! p_function - > source_lambda & & p_function - > identifier & & p_function - > identifier - > name = = GDScriptLanguage : : get_singleton ( ) - > strings . _static_init ) {
if ( ! p_function - > is_static ) {
push_error ( R " (Static constructor must be declared static.) " ) ;
}
if ( p_function - > parameters . size ( ) ! = 0 ) {
push_error ( R " (Static constructor cannot have parameters.) " ) ;
}
current_class - > has_static_data = true ;
}
2020-05-02 00:14:56 +02:00
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
2021-03-25 14:36:29 +01:00
consume ( GDScriptTokenizer : : Token : : COLON , vformat ( R " (Expected " : " after %s declaration.) " , p_type ) ) ;
}
2023-04-19 16:10:35 +02:00
GDScriptParser : : FunctionNode * GDScriptParser : : parse_function ( bool p_is_static ) {
2022-07-11 20:31:15 +02:00
FunctionNode * function = alloc_node < FunctionNode > ( ) ;
2021-03-25 14:36:29 +01:00
make_completion_context ( COMPLETION_OVERRIDE_METHOD , function ) ;
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected function name after " func " .) " ) ) {
2022-07-11 20:31:15 +02:00
complete_extents ( function ) ;
2021-03-25 14:36:29 +01:00
return nullptr ;
}
FunctionNode * previous_function = current_function ;
current_function = function ;
function - > identifier = parse_identifier ( ) ;
2023-04-19 16:10:35 +02:00
function - > is_static = p_is_static ;
2021-03-25 14:36:29 +01:00
SuiteNode * body = alloc_node < SuiteNode > ( ) ;
SuiteNode * previous_suite = current_suite ;
current_suite = body ;
push_multiline ( true ) ;
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected opening " ( " after function name.) " ) ;
parse_function_signature ( function , body , " function " ) ;
2018-05-28 18:38:35 +02:00
2020-06-10 23:18:10 +02:00
current_suite = previous_suite ;
function - > body = parse_suite ( " function declaration " , body ) ;
2019-01-17 22:17:06 +01:00
2020-05-02 00:14:56 +02:00
current_function = previous_function ;
2022-07-11 20:31:15 +02:00
complete_extents ( function ) ;
2020-05-02 00:14:56 +02:00
return function ;
}
2019-03-05 22:19:02 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : AnnotationNode * GDScriptParser : : parse_annotation ( uint32_t p_valid_targets ) {
AnnotationNode * annotation = alloc_node < AnnotationNode > ( ) ;
2016-06-30 03:17:55 +02:00
2020-05-02 00:14:56 +02:00
annotation - > name = previous . literal ;
2014-02-10 02:10:30 +01:00
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_ANNOTATION , annotation ) ;
2020-05-02 00:14:56 +02:00
bool valid = true ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( ! valid_annotations . has ( annotation - > name ) ) {
push_error ( vformat ( R " (Unrecognized annotation: " % s " .) " , annotation - > name ) ) ;
valid = false ;
}
annotation - > info = & valid_annotations [ annotation - > name ] ;
if ( ! annotation - > applies_to ( p_valid_targets ) ) {
2023-01-24 11:10:45 +01:00
if ( annotation - > applies_to ( AnnotationInfo : : SCRIPT ) ) {
push_error ( vformat ( R " (Annotation " % s " must be at the top of the script, before " extends " and " class_name " .) " , annotation - > name ) ) ;
} else {
push_error ( vformat ( R " (Annotation " % s " is not allowed in this level.) " , annotation - > name ) ) ;
}
2020-05-02 00:14:56 +02:00
valid = false ;
}
2023-02-09 16:42:22 +01:00
if ( check ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN ) ) {
push_multiline ( true ) ;
advance ( ) ;
2020-05-02 00:14:56 +02:00
// Arguments.
2020-07-06 17:24:24 +02:00
push_completion_call ( annotation ) ;
make_completion_context ( COMPLETION_ANNOTATION_ARGUMENTS , annotation , 0 , true ) ;
2023-02-09 16:42:22 +01:00
int argument_index = 0 ;
do {
if ( check ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE ) ) {
// Allow for trailing comma.
break ;
}
2020-05-02 00:14:56 +02:00
2023-02-09 16:42:22 +01:00
make_completion_context ( COMPLETION_ANNOTATION_ARGUMENTS , annotation , argument_index , true ) ;
set_last_completion_call_arg ( argument_index + + ) ;
ExpressionNode * argument = parse_expression ( false ) ;
if ( argument = = nullptr ) {
push_error ( " Expected expression as the annotation argument. " ) ;
valid = false ;
continue ;
}
annotation - > arguments . push_back ( argument ) ;
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) & & ! is_at_end ( ) ) ;
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected " ) " after annotation arguments.)* " ) ;
2020-07-06 17:24:24 +02:00
pop_completion_call ( ) ;
2020-05-02 00:14:56 +02:00
}
2022-07-11 20:31:15 +02:00
complete_extents ( annotation ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
match ( GDScriptTokenizer : : Token : : NEWLINE ) ; // Newline after annotation is optional.
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( valid ) {
2023-01-31 15:43:54 +01:00
valid = validate_annotation_arguments ( annotation ) ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return valid ? annotation : nullptr ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : clear_unused_annotations ( ) {
2021-07-24 15:46:25 +02:00
for ( const AnnotationNode * annotation : annotation_stack ) {
2023-04-06 08:04:22 +02:00
push_error ( vformat ( R " (Annotation " % s " does not precede a valid target, so it will have no effect.) " , annotation - > name ) , annotation ) ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
annotation_stack . clear ( ) ;
}
2022-07-11 22:02:55 +02:00
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 ) {
2020-05-02 00:14:56 +02:00
ERR_FAIL_COND_V_MSG ( valid_annotations . has ( p_info . name ) , false , vformat ( R " (Annotation " % s " already registered.) " , p_info . name ) ) ;
2018-08-26 18:31:23 +02:00
2020-05-02 00:14:56 +02:00
AnnotationInfo new_annotation ;
new_annotation . info = p_info ;
2022-07-11 22:02:55 +02:00
new_annotation . info . default_arguments = p_default_arguments ;
2020-05-02 00:14:56 +02:00
if ( p_is_vararg ) {
new_annotation . info . flags | = METHOD_FLAG_VARARG ;
}
new_annotation . apply = p_apply ;
new_annotation . target_kind = p_target_kinds ;
2018-08-26 18:31:23 +02:00
2020-05-02 00:14:56 +02:00
valid_annotations [ p_info . name ] = new_annotation ;
return true ;
}
2018-08-26 18:31:23 +02:00
2021-03-25 14:36:29 +01:00
GDScriptParser : : SuiteNode * GDScriptParser : : parse_suite ( const String & p_context , SuiteNode * p_suite , bool p_for_lambda ) {
2020-06-10 23:18:10 +02:00
SuiteNode * suite = p_suite ! = nullptr ? p_suite : alloc_node < SuiteNode > ( ) ;
2020-05-02 00:14:56 +02:00
suite - > parent_block = current_suite ;
2021-03-26 13:03:16 +01:00
suite - > parent_function = current_function ;
2020-05-02 00:14:56 +02:00
current_suite = suite ;
2014-02-10 02:10:30 +01:00
2023-06-01 20:46:37 +02:00
if ( ! p_for_lambda & & suite - > parent_block ! = nullptr & & suite - > parent_block - > is_in_loop ) {
// Do not reset to false if true is set before calling parse_suite().
suite - > is_in_loop = true ;
}
2020-05-02 00:14:56 +02:00
bool multiline = false ;
2014-02-10 02:10:30 +01:00
2021-03-25 14:36:29 +01:00
if ( match ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
2020-05-02 00:14:56 +02:00
multiline = true ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( multiline ) {
if ( ! consume ( GDScriptTokenizer : : Token : : INDENT , vformat ( R " (Expected indented block after %s.) " , p_context ) ) ) {
current_suite = suite - > parent_block ;
2022-07-11 20:31:15 +02:00
complete_extents ( suite ) ;
2020-05-02 00:14:56 +02:00
return suite ;
}
}
2022-07-11 20:31:15 +02:00
reset_extents ( suite , current ) ;
2020-05-02 00:14:56 +02:00
2021-03-25 14:36:29 +01:00
int error_count = 0 ;
2020-05-02 00:14:56 +02:00
do {
2023-02-16 04:49:42 +01:00
if ( is_at_end ( ) | | ( ! multiline & & previous . type = = GDScriptTokenizer : : Token : : SEMICOLON & & check ( GDScriptTokenizer : : Token : : NEWLINE ) ) ) {
2021-09-21 19:13:23 +02:00
break ;
}
2020-05-02 00:14:56 +02:00
Node * statement = parse_statement ( ) ;
if ( statement = = nullptr ) {
2021-03-25 14:36:29 +01:00
if ( error_count + + > 100 ) {
push_error ( " Too many statement errors. " , suite ) ;
break ;
}
2020-05-02 00:14:56 +02:00
continue ;
}
suite - > statements . push_back ( statement ) ;
// Register locals.
switch ( statement - > type ) {
case Node : : VARIABLE : {
VariableNode * variable = static_cast < VariableNode * > ( statement ) ;
const SuiteNode : : Local & local = current_suite - > get_local ( variable - > identifier - > name ) ;
if ( local . type ! = SuiteNode : : Local : : UNDEFINED ) {
2022-11-18 21:20:26 +01:00
push_error ( vformat ( R " (There is already a %s named " % s " declared in this scope.) " , local . get_name ( ) , variable - > identifier - > name ) , variable - > identifier ) ;
2020-05-02 00:14:56 +02:00
}
2021-03-26 13:03:16 +01:00
current_suite - > add_local ( variable , current_function ) ;
2020-05-02 00:14:56 +02:00
break ;
}
case Node : : CONSTANT : {
ConstantNode * constant = static_cast < ConstantNode * > ( statement ) ;
const SuiteNode : : Local & local = current_suite - > get_local ( constant - > identifier - > name ) ;
if ( local . type ! = SuiteNode : : Local : : UNDEFINED ) {
String name ;
if ( local . type = = SuiteNode : : Local : : CONSTANT ) {
name = " constant " ;
} else {
name = " variable " ;
2020-05-14 16:41:43 +02:00
}
2022-11-18 21:20:26 +01:00
push_error ( vformat ( R " (There is already a %s named " % s " declared in this scope.) " , name , constant - > identifier - > name ) , constant - > identifier ) ;
2014-02-10 02:10:30 +01:00
}
2021-03-26 13:03:16 +01:00
current_suite - > add_local ( constant , current_function ) ;
2020-05-02 00:14:56 +02:00
break ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
default :
break ;
}
2014-02-10 02:10:30 +01:00
2021-09-21 19:13:23 +02:00
} while ( ( multiline | | previous . type = = GDScriptTokenizer : : Token : : SEMICOLON ) & & ! check ( GDScriptTokenizer : : Token : : DEDENT ) & & ! lambda_ended & & ! is_at_end ( ) ) ;
2014-02-10 02:10:30 +01:00
2022-07-11 20:31:15 +02:00
complete_extents ( suite ) ;
2020-05-02 00:14:56 +02:00
if ( multiline ) {
2021-03-25 14:36:29 +01:00
if ( ! lambda_ended ) {
consume ( GDScriptTokenizer : : Token : : DEDENT , vformat ( R " (Missing unindent at the end of %s.) " , p_context ) ) ;
} else {
match ( GDScriptTokenizer : : Token : : DEDENT ) ;
}
} else if ( previous . type = = GDScriptTokenizer : : Token : : SEMICOLON ) {
consume ( GDScriptTokenizer : : Token : : NEWLINE , vformat ( R " (Expected newline after " ; " at the end of %s.) " , p_context ) ) ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2021-03-25 14:36:29 +01:00
if ( p_for_lambda ) {
lambda_ended = true ;
}
2020-05-02 00:14:56 +02:00
current_suite = suite - > parent_block ;
return suite ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : Node * GDScriptParser : : parse_statement ( ) {
Node * result = nullptr ;
2020-07-16 03:02:44 +02:00
# ifdef DEBUG_ENABLED
2020-06-12 00:31:28 +02:00
bool unreachable = current_suite - > has_return & & ! current_suite - > has_unreachable_code ;
2020-07-16 03:02:44 +02:00
# endif
2020-06-12 00:31:28 +02:00
2024-02-28 15:23:11 +01:00
List < AnnotationNode * > annotations ;
if ( current . type ! = GDScriptTokenizer : : Token : : ANNOTATION ) {
while ( ! annotation_stack . is_empty ( ) ) {
AnnotationNode * last_annotation = annotation_stack . back ( ) - > get ( ) ;
if ( last_annotation - > applies_to ( AnnotationInfo : : STATEMENT ) ) {
annotations . push_front ( last_annotation ) ;
annotation_stack . pop_back ( ) ;
} else {
push_error ( vformat ( R " (Annotation " % s " cannot be applied to a statement.) " , last_annotation - > name ) ) ;
clear_unused_annotations ( ) ;
}
}
}
2022-01-04 13:32:43 +01:00
2020-05-02 00:14:56 +02:00
switch ( current . type ) {
case GDScriptTokenizer : : Token : : PASS :
advance ( ) ;
result = alloc_node < PassNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( result ) ;
2020-05-02 00:14:56 +02:00
end_statement ( R " ( " pass " ) " ) ;
break ;
case GDScriptTokenizer : : Token : : VAR :
advance ( ) ;
2023-04-19 16:10:35 +02:00
result = parse_variable ( false , false ) ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : CONST :
advance ( ) ;
2023-04-19 16:10:35 +02:00
result = parse_constant ( false ) ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : IF :
advance ( ) ;
result = parse_if ( ) ;
break ;
case GDScriptTokenizer : : Token : : FOR :
advance ( ) ;
result = parse_for ( ) ;
break ;
case GDScriptTokenizer : : Token : : WHILE :
advance ( ) ;
result = parse_while ( ) ;
break ;
case GDScriptTokenizer : : Token : : MATCH :
advance ( ) ;
result = parse_match ( ) ;
break ;
case GDScriptTokenizer : : Token : : BREAK :
advance ( ) ;
result = parse_break ( ) ;
break ;
case GDScriptTokenizer : : Token : : CONTINUE :
advance ( ) ;
result = parse_continue ( ) ;
break ;
case GDScriptTokenizer : : Token : : RETURN : {
advance ( ) ;
ReturnNode * n_return = alloc_node < ReturnNode > ( ) ;
if ( ! is_statement_end ( ) ) {
2023-04-19 16:10:35 +02:00
if ( current_function & & ( current_function - > identifier - > name = = GDScriptLanguage : : get_singleton ( ) - > strings . _init | | current_function - > identifier - > name = = GDScriptLanguage : : get_singleton ( ) - > strings . _static_init ) ) {
2020-07-16 03:02:44 +02:00
push_error ( R " (Constructor cannot return a value.) " ) ;
}
2020-05-02 00:14:56 +02:00
n_return - > return_value = parse_expression ( false ) ;
2021-03-25 14:36:29 +01:00
} else if ( in_lambda & & ! is_statement_end_token ( ) ) {
// Try to parse it anyway as this might not be the statement end in a lambda.
// If this fails the expression will be nullptr, but that's the same as no return, so it's fine.
n_return - > return_value = parse_expression ( false ) ;
2020-05-02 00:14:56 +02:00
}
2022-07-11 20:31:15 +02:00
complete_extents ( n_return ) ;
2020-05-02 00:14:56 +02:00
result = n_return ;
2020-06-12 00:31:28 +02:00
current_suite - > has_return = true ;
2020-05-02 00:14:56 +02:00
end_statement ( " return statement " ) ;
break ;
}
case GDScriptTokenizer : : Token : : BREAKPOINT :
advance ( ) ;
result = alloc_node < BreakpointNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( result ) ;
2020-05-02 00:14:56 +02:00
end_statement ( R " ( " breakpoint " ) " ) ;
break ;
case GDScriptTokenizer : : Token : : ASSERT :
advance ( ) ;
result = parse_assert ( ) ;
break ;
case GDScriptTokenizer : : Token : : ANNOTATION : {
advance ( ) ;
AnnotationNode * annotation = parse_annotation ( AnnotationInfo : : STATEMENT ) ;
if ( annotation ! = nullptr ) {
annotation_stack . push_back ( annotation ) ;
}
break ;
}
default : {
// Expression statement.
ExpressionNode * expression = parse_expression ( true ) ; // Allow assignment here.
2021-03-25 14:36:29 +01:00
bool has_ended_lambda = false ;
2020-06-01 21:41:05 +02:00
if ( expression = = nullptr ) {
2021-03-25 14:36:29 +01:00
if ( in_lambda ) {
// If it's not a valid expression beginning, it might be the continuation of the outer expression where this lambda is.
lambda_ended = true ;
has_ended_lambda = true ;
} else {
2022-07-11 01:42:40 +02:00
advance ( ) ;
2021-03-25 14:36:29 +01:00
push_error ( vformat ( R " (Expected statement, found " % s " instead.) " , previous . get_name ( ) ) ) ;
}
2022-07-11 02:32:28 +02:00
} else {
end_statement ( " expression " ) ;
2020-06-01 21:41:05 +02:00
}
2021-03-25 14:36:29 +01:00
lambda_ended = lambda_ended | | has_ended_lambda ;
2020-05-02 00:14:56 +02:00
result = expression ;
2020-06-12 00:31:28 +02:00
2020-07-16 03:02:44 +02:00
# ifdef DEBUG_ENABLED
2020-06-12 00:31:28 +02:00
if ( expression ! = nullptr ) {
switch ( expression - > type ) {
case Node : : ASSIGNMENT :
case Node : : AWAIT :
2024-02-28 15:23:11 +01:00
case Node : : CALL :
2020-06-12 00:31:28 +02:00
// Fine.
break ;
2024-05-16 21:11:56 +02:00
case Node : : PRELOAD :
// `preload` is a function-like keyword.
push_warning ( expression , GDScriptWarning : : RETURN_VALUE_DISCARDED , " preload " ) ;
break ;
2022-05-23 17:25:03 +02:00
case Node : : LAMBDA :
// Standalone lambdas can't be used, so make this an error.
push_error ( " Standalone lambdas cannot be accessed. Consider assigning it to a variable. " , expression ) ;
break ;
2023-02-09 14:16:00 +01:00
case Node : : LITERAL :
2024-02-28 15:23:11 +01:00
// Allow strings as multiline comments.
if ( static_cast < GDScriptParser : : LiteralNode * > ( expression ) - > value . get_type ( ) ! = Variant : : STRING ) {
push_warning ( expression , GDScriptWarning : : STANDALONE_EXPRESSION ) ;
2023-02-09 14:16:00 +01:00
}
2024-02-28 15:23:11 +01:00
break ;
case Node : : TERNARY_OPERATOR :
push_warning ( expression , GDScriptWarning : : STANDALONE_TERNARY ) ;
break ;
2020-06-12 00:31:28 +02:00
default :
push_warning ( expression , GDScriptWarning : : STANDALONE_EXPRESSION ) ;
}
}
2020-07-16 03:02:44 +02:00
# endif
2020-05-02 00:14:56 +02:00
break ;
}
}
2014-02-10 02:10:30 +01:00
2024-02-28 15:23:11 +01:00
if ( result ! = nullptr & & ! annotations . is_empty ( ) ) {
for ( AnnotationNode * & annotation : annotations ) {
result - > annotations . push_back ( annotation ) ;
2022-01-04 13:32:43 +01:00
}
}
2020-07-16 03:02:44 +02:00
# ifdef DEBUG_ENABLED
2020-11-13 15:09:52 +01:00
if ( unreachable & & result ! = nullptr ) {
2020-06-12 00:31:28 +02:00
current_suite - > has_unreachable_code = true ;
2020-10-31 02:21:50 +01:00
if ( current_function ) {
2021-03-25 14:36:29 +01:00
push_warning ( result , GDScriptWarning : : UNREACHABLE_CODE , current_function - > identifier ? current_function - > identifier - > name : " <anonymous lambda> " ) ;
2020-10-31 02:21:50 +01:00
} else {
// TODO: Properties setters and getters with unreachable code are not being warned
}
2020-06-12 00:31:28 +02:00
}
2020-07-16 03:02:44 +02:00
# endif
2020-06-12 00:31:28 +02:00
2020-05-02 00:14:56 +02:00
if ( panic_mode ) {
synchronize ( ) ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return result ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : AssertNode * GDScriptParser : : parse_assert ( ) {
// TODO: Add assert message.
AssertNode * assert = alloc_node < AssertNode > ( ) ;
2014-02-10 02:10:30 +01:00
2022-12-28 03:34:00 +01:00
push_multiline ( true ) ;
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected " ( " after " assert " .) " ) ;
2022-12-28 03:34:00 +01:00
2020-05-02 00:14:56 +02:00
assert - > condition = parse_expression ( false ) ;
if ( assert - > condition = = nullptr ) {
push_error ( " Expected expression to assert. " ) ;
2022-12-28 03:34:00 +01:00
pop_multiline ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( assert ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
}
2014-02-10 02:10:30 +01:00
2022-12-28 03:34:00 +01:00
if ( match ( GDScriptTokenizer : : Token : : COMMA ) & & ! check ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE ) ) {
2020-12-27 06:57:50 +01:00
assert - > message = parse_expression ( false ) ;
if ( assert - > message = = nullptr ) {
push_error ( R " (Expected error message for assert after " , " .) " ) ;
2022-12-28 03:34:00 +01:00
pop_multiline ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( assert ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
}
2022-12-28 03:34:00 +01:00
match ( GDScriptTokenizer : : Token : : COMMA ) ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2022-12-28 03:34:00 +01:00
pop_multiline ( ) ;
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected " ) " after assert expression.)* " ) ;
2014-02-10 02:10:30 +01:00
2022-07-11 20:31:15 +02:00
complete_extents ( assert ) ;
2020-05-02 00:14:56 +02:00
end_statement ( R " ( " assert " ) " ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return assert ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : BreakNode * GDScriptParser : : parse_break ( ) {
if ( ! can_break ) {
push_error ( R " (Cannot use " break " outside of a loop.) " ) ;
}
2022-07-11 20:31:15 +02:00
BreakNode * break_node = alloc_node < BreakNode > ( ) ;
complete_extents ( break_node ) ;
2020-05-02 00:14:56 +02:00
end_statement ( R " ( " break " ) " ) ;
2022-07-11 20:31:15 +02:00
return break_node ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ContinueNode * GDScriptParser : : parse_continue ( ) {
if ( ! can_continue ) {
2023-01-21 17:33:05 +01:00
push_error ( R " (Cannot use " continue " outside of a loop.) " ) ;
2020-05-02 00:14:56 +02:00
}
2020-06-12 00:31:28 +02:00
current_suite - > has_continue = true ;
2020-08-07 19:51:56 +02:00
ContinueNode * cont = alloc_node < ContinueNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( cont ) ;
end_statement ( R " ( " continue " ) " ) ;
2020-08-07 19:51:56 +02:00
return cont ;
2020-05-02 00:14:56 +02:00
}
2016-11-26 13:40:13 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ForNode * GDScriptParser : : parse_for ( ) {
ForNode * n_for = alloc_node < ForNode > ( ) ;
2016-11-26 13:40:13 +01:00
2020-05-02 00:14:56 +02:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected loop variable name after " for " .) " ) ) {
n_for - > variable = parse_identifier ( ) ;
}
2014-02-10 02:10:30 +01:00
2023-08-04 11:19:11 +02:00
if ( match ( GDScriptTokenizer : : Token : : COLON ) ) {
n_for - > datatype_specifier = parse_type ( ) ;
if ( n_for - > datatype_specifier = = nullptr ) {
push_error ( R " (Expected type specifier after " : " .) " ) ;
}
}
if ( n_for - > datatype_specifier = = nullptr ) {
consume ( GDScriptTokenizer : : Token : : IN , R " (Expected " in " or " : " after " for " variable name.) " ) ;
} else {
consume ( GDScriptTokenizer : : Token : : IN , R " (Expected " in " after " for " variable type specifier.) " ) ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
n_for - > list = parse_expression ( false ) ;
2014-02-10 02:10:30 +01:00
2021-10-02 19:53:56 +02:00
if ( ! n_for - > list ) {
2023-04-06 08:04:22 +02:00
push_error ( R " (Expected iterable after " in " .) " ) ;
2021-10-02 19:53:56 +02:00
}
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " for " condition.) " ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
// Save break/continue state.
bool could_break = can_break ;
bool could_continue = can_continue ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
// Allow break/continue.
can_break = true ;
can_continue = true ;
2014-02-10 02:10:30 +01:00
2020-06-10 23:18:10 +02:00
SuiteNode * suite = alloc_node < SuiteNode > ( ) ;
2020-07-16 03:02:44 +02:00
if ( n_for - > variable ) {
2022-07-04 11:30:39 +02:00
const SuiteNode : : Local & local = current_suite - > get_local ( n_for - > variable - > name ) ;
if ( local . type ! = SuiteNode : : Local : : UNDEFINED ) {
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 ) ;
}
2021-03-26 13:03:16 +01:00
suite - > add_local ( SuiteNode : : Local ( n_for - > variable , current_function ) ) ;
2020-07-16 03:02:44 +02:00
}
2023-06-01 20:46:37 +02:00
suite - > is_in_loop = true ;
2020-06-10 23:18:10 +02:00
n_for - > loop = parse_suite ( R " ( " for " block) " , suite ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( n_for ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
// Reset break/continue state.
can_break = could_break ;
can_continue = could_continue ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return n_for ;
}
2018-08-26 18:31:23 +02:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : IfNode * GDScriptParser : : parse_if ( const String & p_token ) {
IfNode * n_if = alloc_node < IfNode > ( ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
n_if - > condition = parse_expression ( false ) ;
if ( n_if - > condition = = nullptr ) {
push_error ( vformat ( R " (Expected conditional expression after " % s " .) " , p_token ) ) ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : COLON , vformat ( R " (Expected " : " after " % s " condition.) " , p_token ) ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
n_if - > true_block = parse_suite ( vformat ( R " ( " % s " block) " , p_token ) ) ;
2020-07-06 17:24:24 +02:00
n_if - > true_block - > parent_if = n_if ;
2014-02-10 02:10:30 +01:00
2020-06-12 00:31:28 +02:00
if ( n_if - > true_block - > has_continue ) {
current_suite - > has_continue = true ;
}
2020-05-02 00:14:56 +02:00
if ( match ( GDScriptTokenizer : : Token : : ELIF ) ) {
SuiteNode * else_block = alloc_node < SuiteNode > ( ) ;
2023-01-29 20:29:23 +01:00
else_block - > parent_function = current_function ;
else_block - > parent_block = current_suite ;
SuiteNode * previous_suite = current_suite ;
current_suite = else_block ;
2022-07-11 20:31:15 +02:00
IfNode * elif = parse_if ( " elif " ) ;
2020-05-02 00:14:56 +02:00
else_block - > statements . push_back ( elif ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( else_block ) ;
2020-05-02 00:14:56 +02:00
n_if - > false_block = else_block ;
2023-01-29 20:29:23 +01:00
current_suite = previous_suite ;
2020-05-02 00:14:56 +02:00
} else if ( match ( GDScriptTokenizer : : Token : : ELSE ) ) {
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " else " .) " ) ;
n_if - > false_block = parse_suite ( R " ( " else " block) " ) ;
}
2022-07-11 20:31:15 +02:00
complete_extents ( n_if ) ;
2014-02-10 02:10:30 +01:00
2020-06-12 00:31:28 +02:00
if ( n_if - > false_block ! = nullptr & & n_if - > false_block - > has_return & & n_if - > true_block - > has_return ) {
current_suite - > has_return = true ;
}
if ( n_if - > false_block ! = nullptr & & n_if - > false_block - > has_continue ) {
current_suite - > has_continue = true ;
}
2020-05-02 00:14:56 +02:00
return n_if ;
}
2014-12-17 02:31:57 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : MatchNode * GDScriptParser : : parse_match ( ) {
2024-02-28 15:23:11 +01:00
MatchNode * match_node = alloc_node < MatchNode > ( ) ;
2014-02-10 02:10:30 +01:00
2024-02-28 15:23:11 +01:00
match_node - > test = parse_expression ( false ) ;
if ( match_node - > test = = nullptr ) {
2020-05-02 00:14:56 +02:00
push_error ( R " (Expected expression to test after " match " .) " ) ;
}
2019-07-03 16:28:50 +02:00
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " match " expression.) " ) ;
consume ( GDScriptTokenizer : : Token : : NEWLINE , R " (Expected a newline after " match " statement.) " ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( ! consume ( GDScriptTokenizer : : Token : : INDENT , R " (Expected an indented block after " match " statement.) " ) ) {
2024-02-28 15:23:11 +01:00
complete_extents ( match_node ) ;
return match_node ;
2020-05-02 00:14:56 +02:00
}
2014-12-17 02:31:57 +01:00
2020-06-12 00:31:28 +02:00
bool all_have_return = true ;
bool have_wildcard = false ;
2024-02-28 15:23:11 +01:00
List < AnnotationNode * > match_branch_annotation_stack ;
2020-05-02 00:14:56 +02:00
while ( ! check ( GDScriptTokenizer : : Token : : DEDENT ) & & ! is_at_end ( ) ) {
2024-02-28 15:23:11 +01:00
if ( match ( GDScriptTokenizer : : Token : : PASS ) ) {
consume ( GDScriptTokenizer : : Token : : NEWLINE , R " (Expected newline after " pass " .) " ) ;
continue ;
}
if ( match ( GDScriptTokenizer : : Token : : ANNOTATION ) ) {
AnnotationNode * annotation = parse_annotation ( AnnotationInfo : : STATEMENT ) ;
if ( annotation = = nullptr ) {
continue ;
}
if ( annotation - > name ! = SNAME ( " @warning_ignore " ) ) {
push_error ( vformat ( R " (Annotation " % s " is not allowed in this level.) " , annotation - > name ) , annotation ) ;
continue ;
}
match_branch_annotation_stack . push_back ( annotation ) ;
continue ;
}
2020-05-02 00:14:56 +02:00
MatchBranchNode * branch = parse_match_branch ( ) ;
if ( branch = = nullptr ) {
2021-08-24 21:27:17 +02:00
advance ( ) ;
2020-05-02 00:14:56 +02:00
continue ;
}
2020-06-12 00:31:28 +02:00
2024-02-28 15:23:11 +01:00
for ( AnnotationNode * annotation : match_branch_annotation_stack ) {
branch - > annotations . push_back ( annotation ) ;
}
match_branch_annotation_stack . clear ( ) ;
2020-07-16 03:02:44 +02:00
# ifdef DEBUG_ENABLED
2023-01-28 23:49:14 +01:00
if ( have_wildcard & & ! branch - > patterns . is_empty ( ) ) {
2020-06-12 00:31:28 +02:00
push_warning ( branch - > patterns [ 0 ] , GDScriptWarning : : UNREACHABLE_PATTERN ) ;
}
2023-01-30 19:18:36 +01:00
# endif
2020-06-12 00:31:28 +02:00
2023-01-28 23:49:14 +01:00
have_wildcard = have_wildcard | | branch - > has_wildcard ;
all_have_return = all_have_return & & branch - > block - > has_return ;
2024-02-28 15:23:11 +01:00
match_node - > branches . push_back ( branch ) ;
2020-05-02 00:14:56 +02:00
}
2024-02-28 15:23:11 +01:00
complete_extents ( match_node ) ;
2014-12-17 02:31:57 +01:00
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : DEDENT , R " (Expected an indented block after " match " statement.) " ) ;
2014-12-17 02:31:57 +01:00
2021-11-21 15:53:21 +01:00
if ( all_have_return & & have_wildcard ) {
2020-06-12 00:31:28 +02:00
current_suite - > has_return = true ;
}
2024-02-28 15:23:11 +01:00
for ( const AnnotationNode * annotation : match_branch_annotation_stack ) {
push_error ( vformat ( R " (Annotation " % s " does not precede a valid target, so it will have no effect.) " , annotation - > name ) , annotation ) ;
}
match_branch_annotation_stack . clear ( ) ;
return match_node ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : MatchBranchNode * GDScriptParser : : parse_match_branch ( ) {
MatchBranchNode * branch = alloc_node < MatchBranchNode > ( ) ;
2022-07-11 20:31:15 +02:00
reset_extents ( branch , current ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
bool has_bind = false ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
do {
PatternNode * pattern = parse_match_pattern ( ) ;
if ( pattern = = nullptr ) {
continue ;
}
2022-04-04 09:47:08 +02:00
if ( pattern - > binds . size ( ) > 0 ) {
2020-05-02 00:14:56 +02:00
has_bind = true ;
}
if ( branch - > patterns . size ( ) > 0 & & has_bind ) {
push_error ( R " (Cannot use a variable bind with multiple patterns.) " ) ;
}
if ( pattern - > pattern_type = = PatternNode : : PT_REST ) {
push_error ( R " (Rest pattern can only be used inside array and dictionary patterns.) " ) ;
2020-06-12 00:31:28 +02:00
} else if ( pattern - > pattern_type = = PatternNode : : PT_BIND | | pattern - > pattern_type = = PatternNode : : PT_WILDCARD ) {
branch - > has_wildcard = true ;
2020-05-02 00:14:56 +02:00
}
branch - > patterns . push_back ( pattern ) ;
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
2014-02-10 02:10:30 +01:00
2020-12-15 13:04:21 +01:00
if ( branch - > patterns . is_empty ( ) ) {
2020-05-02 00:14:56 +02:00
push_error ( R " (No pattern found for " match " branch.) " ) ;
}
2014-02-10 02:10:30 +01:00
2023-07-31 12:47:26 +02:00
bool has_guard = false ;
if ( match ( GDScriptTokenizer : : Token : : WHEN ) ) {
// Pattern guard.
// Create block for guard because it also needs to access the bound variables from patterns, and we don't want to add them to the outer scope.
branch - > guard_body = alloc_node < SuiteNode > ( ) ;
if ( branch - > patterns . size ( ) > 0 ) {
for ( const KeyValue < StringName , IdentifierNode * > & E : branch - > patterns [ 0 ] - > binds ) {
SuiteNode : : Local local ( E . value , current_function ) ;
local . type = SuiteNode : : Local : : PATTERN_BIND ;
branch - > guard_body - > add_local ( local ) ;
}
}
SuiteNode * parent_block = current_suite ;
branch - > guard_body - > parent_block = parent_block ;
current_suite = branch - > guard_body ;
ExpressionNode * guard = parse_expression ( false ) ;
if ( guard = = nullptr ) {
push_error ( R " (Expected expression for pattern guard after " when " .) " ) ;
} else {
branch - > guard_body - > statements . append ( guard ) ;
}
current_suite = parent_block ;
complete_extents ( branch - > guard_body ) ;
has_guard = true ;
branch - > has_wildcard = false ; // If it has a guard, the wildcard might still not match.
}
if ( ! consume ( GDScriptTokenizer : : Token : : COLON , vformat ( R " (Expected " : " %s after " match " %s.) " , has_guard ? " " : R " ( or " when " ) " , has_guard ? " pattern guard " : " patterns " ) ) ) {
2022-07-11 20:31:15 +02:00
complete_extents ( branch ) ;
2021-08-24 21:27:17 +02:00
return nullptr ;
2021-08-25 15:42:48 +02:00
}
2014-02-10 02:10:30 +01:00
2020-06-10 23:18:10 +02:00
SuiteNode * suite = alloc_node < SuiteNode > ( ) ;
if ( branch - > patterns . size ( ) > 0 ) {
2022-05-08 10:09:19 +02:00
for ( const KeyValue < StringName , IdentifierNode * > & E : branch - > patterns [ 0 ] - > binds ) {
SuiteNode : : Local local ( E . value , current_function ) ;
2022-04-04 09:47:08 +02:00
local . type = SuiteNode : : Local : : PATTERN_BIND ;
2020-06-10 23:18:10 +02:00
suite - > add_local ( local ) ;
}
}
branch - > block = parse_suite ( " match pattern block " , suite ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( branch ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return branch ;
}
2014-02-10 02:10:30 +01:00
2020-06-10 23:18:10 +02:00
GDScriptParser : : PatternNode * GDScriptParser : : parse_match_pattern ( PatternNode * p_root_pattern ) {
2020-05-02 00:14:56 +02:00
PatternNode * pattern = alloc_node < PatternNode > ( ) ;
2022-07-11 20:31:15 +02:00
reset_extents ( pattern , current ) ;
2018-05-30 04:16:51 +02:00
2020-05-02 00:14:56 +02:00
switch ( current . type ) {
2020-06-10 23:18:10 +02:00
case GDScriptTokenizer : : Token : : VAR : {
2020-05-02 00:14:56 +02:00
// Bind.
advance ( ) ;
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected bind name after " var " .) " ) ) {
2022-07-11 20:31:15 +02:00
complete_extents ( pattern ) ;
2020-04-02 01:20:12 +02:00
return nullptr ;
2018-05-30 04:16:51 +02:00
}
2020-05-02 00:14:56 +02:00
pattern - > pattern_type = PatternNode : : PT_BIND ;
pattern - > bind = parse_identifier ( ) ;
2020-06-10 23:18:10 +02:00
PatternNode * root_pattern = p_root_pattern = = nullptr ? pattern : p_root_pattern ;
if ( p_root_pattern ! = nullptr ) {
if ( p_root_pattern - > has_bind ( pattern - > bind - > name ) ) {
push_error ( vformat ( R " (Bind variable name " % s " was already used in this pattern.) " , pattern - > bind - > name ) ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( pattern ) ;
2020-06-10 23:18:10 +02:00
return nullptr ;
}
}
if ( current_suite - > has_local ( pattern - > bind - > name ) ) {
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 ) ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( pattern ) ;
2020-06-10 23:18:10 +02:00
return nullptr ;
}
root_pattern - > binds [ pattern - > bind - > name ] = pattern - > bind ;
} break ;
2020-05-02 00:14:56 +02:00
case GDScriptTokenizer : : Token : : UNDERSCORE :
// Wildcard.
advance ( ) ;
pattern - > pattern_type = PatternNode : : PT_WILDCARD ;
break ;
case GDScriptTokenizer : : Token : : PERIOD_PERIOD :
// Rest.
advance ( ) ;
pattern - > pattern_type = PatternNode : : PT_REST ;
break ;
case GDScriptTokenizer : : Token : : BRACKET_OPEN : {
// Array.
2024-04-08 04:13:10 +02:00
push_multiline ( true ) ;
2020-05-02 00:14:56 +02:00
advance ( ) ;
pattern - > pattern_type = PatternNode : : PT_ARRAY ;
2024-04-08 04:13:10 +02:00
do {
if ( is_at_end ( ) | | check ( GDScriptTokenizer : : Token : : BRACKET_CLOSE ) ) {
break ;
}
PatternNode * sub_pattern = parse_match_pattern ( p_root_pattern ! = nullptr ? p_root_pattern : pattern ) ;
if ( sub_pattern = = nullptr ) {
continue ;
}
if ( pattern - > rest_used ) {
push_error ( R " (The " . . " pattern must be the last element in the pattern array.) " ) ;
} else if ( sub_pattern - > pattern_type = = PatternNode : : PT_REST ) {
pattern - > rest_used = true ;
}
pattern - > array . push_back ( sub_pattern ) ;
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : BRACKET_CLOSE , R " (Expected " ] " to close the array pattern.) " ) ;
2024-04-08 04:13:10 +02:00
pop_multiline ( ) ;
2020-05-02 00:14:56 +02:00
break ;
2018-05-30 04:16:51 +02:00
}
2020-05-02 00:14:56 +02:00
case GDScriptTokenizer : : Token : : BRACE_OPEN : {
// Dictionary.
2024-04-08 04:13:10 +02:00
push_multiline ( true ) ;
2020-05-02 00:14:56 +02:00
advance ( ) ;
pattern - > pattern_type = PatternNode : : PT_DICTIONARY ;
2021-08-24 21:27:17 +02:00
do {
if ( check ( GDScriptTokenizer : : Token : : BRACE_CLOSE ) | | is_at_end ( ) ) {
break ;
}
if ( match ( GDScriptTokenizer : : Token : : PERIOD_PERIOD ) ) {
// Rest.
if ( pattern - > rest_used ) {
push_error ( R " (The " . . " pattern must be the last element in the pattern dictionary.) " ) ;
} else {
PatternNode * sub_pattern = alloc_node < PatternNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( sub_pattern ) ;
2021-08-24 21:27:17 +02:00
sub_pattern - > pattern_type = PatternNode : : PT_REST ;
pattern - > dictionary . push_back ( { nullptr , sub_pattern } ) ;
pattern - > rest_used = true ;
}
} else {
ExpressionNode * key = parse_expression ( false ) ;
if ( key = = nullptr ) {
push_error ( R " (Expected expression as key for dictionary pattern.) " ) ;
}
if ( match ( GDScriptTokenizer : : Token : : COLON ) ) {
// Value pattern.
PatternNode * sub_pattern = parse_match_pattern ( p_root_pattern ! = nullptr ? p_root_pattern : pattern ) ;
if ( sub_pattern = = nullptr ) {
continue ;
}
2020-05-02 00:14:56 +02:00
if ( pattern - > rest_used ) {
push_error ( R " (The " . . " pattern must be the last element in the pattern dictionary.) " ) ;
2021-08-24 21:27:17 +02:00
} else if ( sub_pattern - > pattern_type = = PatternNode : : PT_REST ) {
push_error ( R " (The " . . " pattern cannot be used as a value.) " ) ;
2020-05-02 00:14:56 +02:00
} else {
2021-08-24 21:27:17 +02:00
pattern - > dictionary . push_back ( { key , sub_pattern } ) ;
2020-05-02 00:14:56 +02:00
}
} else {
2021-08-24 21:27:17 +02:00
// Key match only.
pattern - > dictionary . push_back ( { key , nullptr } ) ;
2020-05-02 00:14:56 +02:00
}
2021-08-24 21:27:17 +02:00
}
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : BRACE_CLOSE , R " (Expected " } " to close the dictionary pattern.) " ) ;
2024-04-08 04:13:10 +02:00
pop_multiline ( ) ;
2020-05-02 00:14:56 +02:00
break ;
}
default : {
// Expression.
ExpressionNode * expression = parse_expression ( false ) ;
if ( expression = = nullptr ) {
push_error ( R " (Expected expression for match pattern.) " ) ;
2023-07-10 19:10:57 +02:00
complete_extents ( pattern ) ;
2021-08-24 21:27:17 +02:00
return nullptr ;
2020-05-02 00:14:56 +02:00
} else {
2021-08-24 21:27:17 +02:00
if ( expression - > type = = GDScriptParser : : Node : : LITERAL ) {
pattern - > pattern_type = PatternNode : : PT_LITERAL ;
} else {
pattern - > pattern_type = PatternNode : : PT_EXPRESSION ;
}
2020-05-02 00:14:56 +02:00
pattern - > expression = expression ;
2014-04-05 23:50:09 +02:00
}
2020-05-02 00:14:56 +02:00
break ;
2014-04-05 23:50:09 +02:00
}
2020-05-02 00:14:56 +02:00
}
2022-07-11 20:31:15 +02:00
complete_extents ( pattern ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return pattern ;
}
2014-02-10 02:10:30 +01:00
2020-06-10 23:18:10 +02:00
bool GDScriptParser : : PatternNode : : has_bind ( const StringName & p_name ) {
return binds . has ( p_name ) ;
}
GDScriptParser : : IdentifierNode * GDScriptParser : : PatternNode : : get_bind ( const StringName & p_name ) {
return binds [ p_name ] ;
}
2020-05-02 00:14:56 +02:00
GDScriptParser : : WhileNode * GDScriptParser : : parse_while ( ) {
WhileNode * n_while = alloc_node < WhileNode > ( ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
n_while - > condition = parse_expression ( false ) ;
if ( n_while - > condition = = nullptr ) {
push_error ( R " (Expected conditional expression after " while " .) " ) ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " while " condition.) " ) ;
2017-08-24 05:06:56 +02:00
2020-05-02 00:14:56 +02:00
// Save break/continue state.
bool could_break = can_break ;
bool could_continue = can_continue ;
2017-03-05 16:44:50 +01:00
2020-05-02 00:14:56 +02:00
// Allow break/continue.
can_break = true ;
can_continue = true ;
2017-08-24 05:06:56 +02:00
2023-06-01 20:46:37 +02:00
SuiteNode * suite = alloc_node < SuiteNode > ( ) ;
suite - > is_in_loop = true ;
n_while - > loop = parse_suite ( R " ( " while " block) " , suite ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( n_while ) ;
2020-05-02 00:14:56 +02:00
// Reset break/continue state.
can_break = could_break ;
can_continue = could_continue ;
return n_while ;
}
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_precedence ( Precedence p_precedence , bool p_can_assign , bool p_stop_on_assign ) {
// Switch multiline mode on for grouping tokens.
// Do this early to avoid the tokenizer generating whitespace tokens.
switch ( current . type ) {
case GDScriptTokenizer : : Token : : PARENTHESIS_OPEN :
case GDScriptTokenizer : : Token : : BRACE_OPEN :
case GDScriptTokenizer : : Token : : BRACKET_OPEN :
push_multiline ( true ) ;
break ;
default :
break ; // Nothing to do.
}
2020-07-16 03:02:44 +02:00
// Completion can appear whenever an expression is expected.
make_completion_context ( COMPLETION_IDENTIFIER , nullptr ) ;
2021-03-25 14:36:29 +01:00
GDScriptTokenizer : : Token token = current ;
2023-02-09 15:17:37 +01:00
GDScriptTokenizer : : Token : : Type token_type = token . type ;
if ( token . is_identifier ( ) ) {
// Allow keywords that can be treated as identifiers.
token_type = GDScriptTokenizer : : Token : : IDENTIFIER ;
}
ParseFunction prefix_rule = get_rule ( token_type ) - > prefix ;
2020-05-02 00:14:56 +02:00
if ( prefix_rule = = nullptr ) {
// Expected expression. Let the caller give the proper error message.
return nullptr ;
}
2021-03-25 14:36:29 +01:00
advance ( ) ; // Only consume the token if there's a valid rule.
2020-05-02 00:14:56 +02:00
ExpressionNode * previous_operand = ( this - > * prefix_rule ) ( nullptr , p_can_assign ) ;
while ( p_precedence < = get_rule ( current . type ) - > precedence ) {
2023-06-20 00:47:18 +02:00
if ( previous_operand = = nullptr | | ( p_stop_on_assign & & current . type = = GDScriptTokenizer : : Token : : EQUAL ) | | lambda_ended ) {
2020-05-02 00:14:56 +02:00
return previous_operand ;
}
// Also switch multiline mode on here for infix operators.
switch ( current . type ) {
// case GDScriptTokenizer::Token::BRACE_OPEN: // Not an infix operator.
case GDScriptTokenizer : : Token : : PARENTHESIS_OPEN :
case GDScriptTokenizer : : Token : : BRACKET_OPEN :
push_multiline ( true ) ;
2020-05-10 13:00:47 +02:00
break ;
default :
2020-05-02 00:14:56 +02:00
break ; // Nothing to do.
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
token = advance ( ) ;
ParseFunction infix_rule = get_rule ( token . type ) - > infix ;
previous_operand = ( this - > * infix_rule ) ( previous_operand , p_can_assign ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
return previous_operand ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_expression ( bool p_can_assign , bool p_stop_on_assign ) {
return parse_precedence ( PREC_ASSIGNMENT , p_can_assign , p_stop_on_assign ) ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : IdentifierNode * GDScriptParser : : parse_identifier ( ) {
2023-01-19 02:56:00 +01:00
IdentifierNode * identifier = static_cast < IdentifierNode * > ( parse_identifier ( nullptr , false ) ) ;
# ifdef DEBUG_ENABLED
// Check for spoofing here (if available in TextServer) since this isn't called inside expressions. This is only relevant for declarations.
2024-02-28 15:23:11 +01:00
if ( identifier & & TS - > has_feature ( TextServer : : FEATURE_UNICODE_SECURITY ) & & TS - > spoof_check ( identifier - > name ) ) {
2023-01-19 02:56:00 +01:00
push_warning ( identifier , GDScriptWarning : : CONFUSABLE_IDENTIFIER , identifier - > name . operator String ( ) ) ;
}
# endif
return identifier ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_identifier ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
if ( ! previous . is_identifier ( ) ) {
2022-12-11 03:57:35 +01:00
ERR_FAIL_V_MSG ( nullptr , " Parser bug: parsing identifier node without identifier token. " ) ;
2020-05-02 00:14:56 +02:00
}
IdentifierNode * identifier = alloc_node < IdentifierNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( identifier ) ;
2020-06-01 21:41:05 +02:00
identifier - > name = previous . get_identifier ( ) ;
2024-01-22 15:31:55 +01:00
if ( identifier - > name . operator String ( ) . is_empty ( ) ) {
print_line ( " Empty identifier found. " ) ;
}
2023-07-25 13:21:49 +02:00
identifier - > suite = current_suite ;
2020-06-10 23:18:10 +02:00
if ( current_suite ! = nullptr & & current_suite - > has_local ( identifier - > name ) ) {
const SuiteNode : : Local & declaration = current_suite - > get_local ( identifier - > name ) ;
2021-03-26 13:03:16 +01:00
identifier - > source_function = declaration . source_function ;
2020-06-10 23:18:10 +02:00
switch ( declaration . type ) {
case SuiteNode : : Local : : CONSTANT :
identifier - > source = IdentifierNode : : LOCAL_CONSTANT ;
identifier - > constant_source = declaration . constant ;
2020-06-12 00:31:28 +02:00
declaration . constant - > usages + + ;
2020-06-10 23:18:10 +02:00
break ;
case SuiteNode : : Local : : VARIABLE :
identifier - > source = IdentifierNode : : LOCAL_VARIABLE ;
identifier - > variable_source = declaration . variable ;
2020-06-12 00:31:28 +02:00
declaration . variable - > usages + + ;
2020-06-10 23:18:10 +02:00
break ;
case SuiteNode : : Local : : PARAMETER :
identifier - > source = IdentifierNode : : FUNCTION_PARAMETER ;
identifier - > parameter_source = declaration . parameter ;
2020-06-12 00:31:28 +02:00
declaration . parameter - > usages + + ;
2020-06-10 23:18:10 +02:00
break ;
case SuiteNode : : Local : : FOR_VARIABLE :
identifier - > source = IdentifierNode : : LOCAL_ITERATOR ;
identifier - > bind_source = declaration . bind ;
2020-06-12 00:31:28 +02:00
declaration . bind - > usages + + ;
2020-06-10 23:18:10 +02:00
break ;
case SuiteNode : : Local : : PATTERN_BIND :
identifier - > source = IdentifierNode : : LOCAL_BIND ;
identifier - > bind_source = declaration . bind ;
2020-06-12 00:31:28 +02:00
declaration . bind - > usages + + ;
2020-06-10 23:18:10 +02:00
break ;
case SuiteNode : : Local : : UNDEFINED :
ERR_FAIL_V_MSG ( nullptr , " Undefined local found. " ) ;
}
}
2020-05-02 00:14:56 +02:00
return identifier ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : LiteralNode * GDScriptParser : : parse_literal ( ) {
return static_cast < LiteralNode * > ( parse_literal ( nullptr , false ) ) ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_literal ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
if ( previous . type ! = GDScriptTokenizer : : Token : : LITERAL ) {
push_error ( " Parser bug: parsing literal node without literal token. " ) ;
ERR_FAIL_V_MSG ( nullptr , " Parser bug: parsing literal node without literal token. " ) ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
LiteralNode * literal = alloc_node < LiteralNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( literal ) ;
2020-05-02 00:14:56 +02:00
literal - > value = previous . literal ;
return literal ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_self ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
2020-08-31 15:01:45 +02:00
if ( current_function & & current_function - > is_static ) {
push_error ( R " (Cannot use " self " inside a static function.) " ) ;
2020-07-16 03:02:44 +02:00
}
2020-05-02 00:14:56 +02:00
SelfNode * self = alloc_node < SelfNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( self ) ;
2020-05-02 00:14:56 +02:00
self - > current_class = current_class ;
return self ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_builtin_constant ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
GDScriptTokenizer : : Token : : Type op_type = previous . type ;
LiteralNode * constant = alloc_node < LiteralNode > ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( constant ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
switch ( op_type ) {
case GDScriptTokenizer : : Token : : CONST_PI :
constant - > value = Math_PI ;
break ;
case GDScriptTokenizer : : Token : : CONST_TAU :
constant - > value = Math_TAU ;
break ;
case GDScriptTokenizer : : Token : : CONST_INF :
2021-07-21 10:40:31 +02:00
constant - > value = INFINITY ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : CONST_NAN :
2021-07-21 10:40:31 +02:00
constant - > value = NAN ;
2020-05-02 00:14:56 +02:00
break ;
default :
return nullptr ; // Unreachable.
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return constant ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_unary_operator ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
GDScriptTokenizer : : Token : : Type op_type = previous . type ;
UnaryOpNode * operation = alloc_node < UnaryOpNode > ( ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
switch ( op_type ) {
case GDScriptTokenizer : : Token : : MINUS :
operation - > operation = UnaryOpNode : : OP_NEGATIVE ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_NEGATE ;
2020-05-02 00:14:56 +02:00
operation - > operand = parse_precedence ( PREC_SIGN , false ) ;
2021-09-15 16:08:59 +02:00
if ( operation - > operand = = nullptr ) {
push_error ( R " (Expected expression after " - " operator.) " ) ;
}
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : PLUS :
operation - > operation = UnaryOpNode : : OP_POSITIVE ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_POSITIVE ;
2020-05-02 00:14:56 +02:00
operation - > operand = parse_precedence ( PREC_SIGN , false ) ;
2021-09-15 16:08:59 +02:00
if ( operation - > operand = = nullptr ) {
push_error ( R " (Expected expression after " + " operator.) " ) ;
}
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : TILDE :
operation - > operation = UnaryOpNode : : OP_COMPLEMENT ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_BIT_NEGATE ;
2020-05-02 00:14:56 +02:00
operation - > operand = parse_precedence ( PREC_BIT_NOT , false ) ;
2021-09-15 16:08:59 +02:00
if ( operation - > operand = = nullptr ) {
push_error ( R " (Expected expression after " ~ " operator.) " ) ;
}
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : NOT :
case GDScriptTokenizer : : Token : : BANG :
operation - > operation = UnaryOpNode : : OP_LOGIC_NOT ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_NOT ;
2020-05-02 00:14:56 +02:00
operation - > operand = parse_precedence ( PREC_LOGIC_NOT , false ) ;
2021-09-15 16:08:59 +02:00
if ( operation - > operand = = nullptr ) {
push_error ( vformat ( R " (Expected expression after " % s " operator.) " , op_type = = GDScriptTokenizer : : Token : : NOT ? " not " : " ! " ) ) ;
}
2020-05-02 00:14:56 +02:00
break ;
default :
2022-07-11 20:31:15 +02:00
complete_extents ( operation ) ;
2020-05-02 00:14:56 +02:00
return nullptr ; // Unreachable.
}
2022-07-11 20:31:15 +02:00
complete_extents ( operation ) ;
2016-08-25 20:18:35 +02:00
2020-05-02 00:14:56 +02:00
return operation ;
}
2014-02-10 02:10:30 +01:00
2020-09-01 09:39:17 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_binary_not_in_operator ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
// check that NOT is followed by IN by consuming it before calling parse_binary_operator which will only receive a plain IN
2022-07-11 20:31:15 +02:00
UnaryOpNode * operation = alloc_node < UnaryOpNode > ( ) ;
reset_extents ( operation , p_previous_operand ) ;
update_extents ( operation ) ;
2020-09-01 09:39:17 +02:00
consume ( GDScriptTokenizer : : Token : : IN , R " (Expected " in " after " not " in content-test operator.) " ) ;
ExpressionNode * in_operation = parse_binary_operator ( p_previous_operand , p_can_assign ) ;
operation - > operation = UnaryOpNode : : OP_LOGIC_NOT ;
operation - > variant_op = Variant : : OP_NOT ;
operation - > operand = in_operation ;
2022-07-11 20:31:15 +02:00
complete_extents ( operation ) ;
2020-09-01 09:39:17 +02:00
return operation ;
}
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_binary_operator ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
GDScriptTokenizer : : Token op = previous ;
BinaryOpNode * operation = alloc_node < BinaryOpNode > ( ) ;
2022-07-11 20:31:15 +02:00
reset_extents ( operation , p_previous_operand ) ;
update_extents ( operation ) ;
2017-03-05 16:44:50 +01:00
2020-05-02 00:14:56 +02:00
Precedence precedence = ( Precedence ) ( get_rule ( op . type ) - > precedence + 1 ) ;
operation - > left_operand = p_previous_operand ;
operation - > right_operand = parse_precedence ( precedence , false ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( operation ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( operation - > right_operand = = nullptr ) {
2023-08-13 00:38:10 +02:00
push_error ( vformat ( R " (Expected expression after " % s " operator.) " , op . get_name ( ) ) ) ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2020-06-10 23:18:10 +02:00
// TODO: Also for unary, ternary, and assignment.
2020-05-02 00:14:56 +02:00
switch ( op . type ) {
case GDScriptTokenizer : : Token : : PLUS :
operation - > operation = BinaryOpNode : : OP_ADDITION ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_ADD ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : MINUS :
operation - > operation = BinaryOpNode : : OP_SUBTRACTION ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_SUBTRACT ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : STAR :
operation - > operation = BinaryOpNode : : OP_MULTIPLICATION ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_MULTIPLY ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : SLASH :
operation - > operation = BinaryOpNode : : OP_DIVISION ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_DIVIDE ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : PERCENT :
operation - > operation = BinaryOpNode : : OP_MODULO ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_MODULE ;
2020-05-02 00:14:56 +02:00
break ;
2022-03-07 18:25:21 +01:00
case GDScriptTokenizer : : Token : : STAR_STAR :
operation - > operation = BinaryOpNode : : OP_POWER ;
operation - > variant_op = Variant : : OP_POWER ;
break ;
2020-05-02 00:14:56 +02:00
case GDScriptTokenizer : : Token : : LESS_LESS :
operation - > operation = BinaryOpNode : : OP_BIT_LEFT_SHIFT ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_SHIFT_LEFT ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : GREATER_GREATER :
operation - > operation = BinaryOpNode : : OP_BIT_RIGHT_SHIFT ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_SHIFT_RIGHT ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : AMPERSAND :
operation - > operation = BinaryOpNode : : OP_BIT_AND ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_BIT_AND ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : PIPE :
2020-06-10 23:18:10 +02:00
operation - > operation = BinaryOpNode : : OP_BIT_OR ;
operation - > variant_op = Variant : : OP_BIT_OR ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : CARET :
operation - > operation = BinaryOpNode : : OP_BIT_XOR ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_BIT_XOR ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : AND :
case GDScriptTokenizer : : Token : : AMPERSAND_AMPERSAND :
operation - > operation = BinaryOpNode : : OP_LOGIC_AND ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_AND ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : OR :
case GDScriptTokenizer : : Token : : PIPE_PIPE :
operation - > operation = BinaryOpNode : : OP_LOGIC_OR ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_OR ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : IN :
operation - > operation = BinaryOpNode : : OP_CONTENT_TEST ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_IN ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : EQUAL_EQUAL :
operation - > operation = BinaryOpNode : : OP_COMP_EQUAL ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_EQUAL ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : BANG_EQUAL :
operation - > operation = BinaryOpNode : : OP_COMP_NOT_EQUAL ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_NOT_EQUAL ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : LESS :
operation - > operation = BinaryOpNode : : OP_COMP_LESS ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_LESS ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : LESS_EQUAL :
operation - > operation = BinaryOpNode : : OP_COMP_LESS_EQUAL ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_LESS_EQUAL ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : GREATER :
operation - > operation = BinaryOpNode : : OP_COMP_GREATER ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_GREATER ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : GREATER_EQUAL :
operation - > operation = BinaryOpNode : : OP_COMP_GREATER_EQUAL ;
2020-06-10 23:18:10 +02:00
operation - > variant_op = Variant : : OP_GREATER_EQUAL ;
2020-05-02 00:14:56 +02:00
break ;
default :
return nullptr ; // Unreachable.
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return operation ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_ternary_operator ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
// Only one ternary operation exists, so no abstraction here.
TernaryOpNode * operation = alloc_node < TernaryOpNode > ( ) ;
2022-07-11 20:31:15 +02:00
reset_extents ( operation , p_previous_operand ) ;
update_extents ( operation ) ;
2014-02-10 02:10:30 +01:00
2022-07-11 20:31:15 +02:00
operation - > true_expr = p_previous_operand ;
2020-05-02 00:14:56 +02:00
operation - > condition = parse_precedence ( PREC_TERNARY , false ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( operation - > condition = = nullptr ) {
push_error ( R " (Expected expression as ternary condition after " if " .) " ) ;
}
2017-03-05 16:44:50 +01:00
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : ELSE , R " (Expected " else " after ternary operator condition.) " ) ;
2016-08-25 20:18:35 +02:00
2020-05-02 00:14:56 +02:00
operation - > false_expr = parse_precedence ( PREC_TERNARY , false ) ;
2016-08-25 20:18:35 +02:00
2021-09-15 16:43:36 +02:00
if ( operation - > false_expr = = nullptr ) {
push_error ( R " (Expected expression after " else " .) " ) ;
}
2022-07-11 20:31:15 +02:00
complete_extents ( operation ) ;
2020-05-02 00:14:56 +02:00
return operation ;
}
2016-08-25 20:18:35 +02:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_assignment ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
if ( ! p_can_assign ) {
push_error ( " Assignment is not allowed inside an expression. " ) ;
return parse_expression ( false ) ; // Return the following expression.
}
2021-11-21 21:17:35 +01:00
if ( p_previous_operand = = nullptr ) {
return parse_expression ( false ) ; // Return the following expression.
}
2016-08-25 20:18:35 +02:00
2020-05-02 00:14:56 +02:00
switch ( p_previous_operand - > type ) {
2020-06-12 00:31:28 +02:00
case Node : : IDENTIFIER : {
2020-07-16 03:02:44 +02:00
# ifdef DEBUG_ENABLED
2020-06-12 00:31:28 +02:00
// Get source to store assignment count.
// Also remove one usage since assignment isn't usage.
IdentifierNode * id = static_cast < IdentifierNode * > ( p_previous_operand ) ;
switch ( id - > source ) {
case IdentifierNode : : LOCAL_VARIABLE :
id - > variable_source - > usages - - ;
break ;
case IdentifierNode : : LOCAL_CONSTANT :
id - > constant_source - > usages - - ;
break ;
case IdentifierNode : : FUNCTION_PARAMETER :
id - > parameter_source - > usages - - ;
break ;
case IdentifierNode : : LOCAL_ITERATOR :
case IdentifierNode : : LOCAL_BIND :
id - > bind_source - > usages - - ;
break ;
default :
break ;
}
2020-07-16 03:02:44 +02:00
# endif
} break ;
2020-05-02 00:14:56 +02:00
case Node : : SUBSCRIPT :
// Okay.
break ;
default :
push_error ( R " (Only identifier, attribute access, and subscription access can be used as assignment target.) " ) ;
return parse_expression ( false ) ; // Return the following expression.
}
2016-08-25 20:18:35 +02:00
2020-05-02 00:14:56 +02:00
AssignmentNode * assignment = alloc_node < AssignmentNode > ( ) ;
2022-07-11 20:31:15 +02:00
reset_extents ( assignment , p_previous_operand ) ;
update_extents ( assignment ) ;
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_ASSIGN , assignment ) ;
2020-05-02 00:14:56 +02:00
switch ( previous . type ) {
case GDScriptTokenizer : : Token : : EQUAL :
assignment - > operation = AssignmentNode : : OP_NONE ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_MAX ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : PLUS_EQUAL :
assignment - > operation = AssignmentNode : : OP_ADDITION ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_ADD ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : MINUS_EQUAL :
assignment - > operation = AssignmentNode : : OP_SUBTRACTION ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_SUBTRACT ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : STAR_EQUAL :
assignment - > operation = AssignmentNode : : OP_MULTIPLICATION ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_MULTIPLY ;
2020-05-02 00:14:56 +02:00
break ;
2022-03-07 18:25:21 +01:00
case GDScriptTokenizer : : Token : : STAR_STAR_EQUAL :
assignment - > operation = AssignmentNode : : OP_POWER ;
assignment - > variant_op = Variant : : OP_POWER ;
break ;
2020-05-02 00:14:56 +02:00
case GDScriptTokenizer : : Token : : SLASH_EQUAL :
assignment - > operation = AssignmentNode : : OP_DIVISION ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_DIVIDE ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : PERCENT_EQUAL :
assignment - > operation = AssignmentNode : : OP_MODULO ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_MODULE ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : LESS_LESS_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_SHIFT_LEFT ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_SHIFT_LEFT ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : GREATER_GREATER_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_SHIFT_RIGHT ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_SHIFT_RIGHT ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : AMPERSAND_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_AND ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_BIT_AND ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : PIPE_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_OR ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_BIT_OR ;
2020-05-02 00:14:56 +02:00
break ;
case GDScriptTokenizer : : Token : : CARET_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_XOR ;
2020-08-07 14:51:09 +02:00
assignment - > variant_op = Variant : : OP_BIT_XOR ;
2020-05-02 00:14:56 +02:00
break ;
default :
break ; // Unreachable.
}
assignment - > assignee = p_previous_operand ;
assignment - > assigned_value = parse_expression ( false ) ;
2021-08-25 18:32:24 +02:00
if ( assignment - > assigned_value = = nullptr ) {
push_error ( R " (Expected an expression after " = " .) " ) ;
}
2022-07-11 20:31:15 +02:00
complete_extents ( assignment ) ;
2016-08-25 20:18:35 +02:00
2020-05-02 00:14:56 +02:00
return assignment ;
}
2016-08-25 20:18:35 +02:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_await ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
AwaitNode * await = alloc_node < AwaitNode > ( ) ;
2021-08-25 00:33:17 +02:00
ExpressionNode * element = parse_precedence ( PREC_AWAIT , false ) ;
if ( element = = nullptr ) {
push_error ( R " (Expected signal or coroutine after " await " .) " ) ;
}
await - > to_await = element ;
2022-07-11 20:31:15 +02:00
complete_extents ( await ) ;
2016-08-25 20:18:35 +02:00
2021-08-13 00:19:55 +02:00
if ( current_function ) { // Might be null in a getter or setter.
current_function - > is_coroutine = true ;
}
2020-06-11 00:53:25 +02:00
2020-05-02 00:14:56 +02:00
return await ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_array ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
ArrayNode * array = alloc_node < ArrayNode > ( ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( ! check ( GDScriptTokenizer : : Token : : BRACKET_CLOSE ) ) {
do {
if ( check ( GDScriptTokenizer : : Token : : BRACKET_CLOSE ) ) {
// Allow for trailing comma.
break ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
ExpressionNode * element = parse_expression ( false ) ;
if ( element = = nullptr ) {
push_error ( R " (Expected expression as array element.) " ) ;
} else {
array - > elements . push_back ( element ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) & & ! is_at_end ( ) ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : BRACKET_CLOSE , R " (Expected closing " ] " after array elements.) " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( array ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return array ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_dictionary ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
DictionaryNode * dictionary = alloc_node < DictionaryNode > ( ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
bool decided_style = false ;
if ( ! check ( GDScriptTokenizer : : Token : : BRACE_CLOSE ) ) {
do {
if ( check ( GDScriptTokenizer : : Token : : BRACE_CLOSE ) ) {
// Allow for trailing comma.
break ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
// Key.
ExpressionNode * key = parse_expression ( false , true ) ; // Stop on "=" so we can check for Lua table style.
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( key = = nullptr ) {
push_error ( R " (Expected expression as dictionary key.) " ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
if ( ! decided_style ) {
switch ( current . type ) {
case GDScriptTokenizer : : Token : : COLON :
dictionary - > style = DictionaryNode : : PYTHON_DICT ;
break ;
case GDScriptTokenizer : : Token : : EQUAL :
dictionary - > style = DictionaryNode : : LUA_TABLE ;
break ;
default :
push_error ( R " (Expected " : " or " = " after dictionary key.) " ) ;
break ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
decided_style = true ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
switch ( dictionary - > style ) {
case DictionaryNode : : LUA_TABLE :
2021-09-15 14:56:24 +02:00
if ( key ! = nullptr & & key - > type ! = Node : : IDENTIFIER & & key - > type ! = Node : : LITERAL ) {
2023-03-20 22:44:27 +01:00
push_error ( R " (Expected identifier or string as Lua-style dictionary key (e.g " { key = value } " ).) " ) ;
2021-09-15 14:56:24 +02:00
advance ( ) ;
break ;
}
if ( key ! = nullptr & & key - > type = = Node : : LITERAL & & static_cast < LiteralNode * > ( key ) - > value . get_type ( ) ! = Variant : : STRING ) {
2023-03-20 22:44:27 +01:00
push_error ( R " (Expected identifier or string as Lua-style dictionary key (e.g " { key = value } " ).) " ) ;
2021-08-10 15:32:07 +02:00
advance ( ) ;
break ;
2020-05-02 00:14:56 +02:00
}
if ( ! match ( GDScriptTokenizer : : Token : : EQUAL ) ) {
if ( match ( GDScriptTokenizer : : Token : : COLON ) ) {
push_error ( R " (Expected " = " after dictionary key. Mixing dictionary styles is not allowed.) " ) ;
advance ( ) ; // Consume wrong separator anyway.
} else {
push_error ( R " (Expected " = " after dictionary key.) " ) ;
}
}
2021-08-24 13:19:40 +02:00
if ( key ! = nullptr ) {
key - > is_constant = true ;
2021-09-15 14:56:24 +02:00
if ( key - > type = = Node : : IDENTIFIER ) {
key - > reduced_value = static_cast < IdentifierNode * > ( key ) - > name ;
} else if ( key - > type = = Node : : LITERAL ) {
key - > reduced_value = StringName ( static_cast < LiteralNode * > ( key ) - > value . operator String ( ) ) ;
}
2021-08-24 13:19:40 +02:00
}
2020-05-02 00:14:56 +02:00
break ;
case DictionaryNode : : PYTHON_DICT :
if ( ! match ( GDScriptTokenizer : : Token : : COLON ) ) {
if ( match ( GDScriptTokenizer : : Token : : EQUAL ) ) {
push_error ( R " (Expected " : " after dictionary key. Mixing dictionary styles is not allowed.) " ) ;
advance ( ) ; // Consume wrong separator anyway.
} else {
push_error ( R " (Expected " : " after dictionary key.) " ) ;
}
}
break ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
// Value.
ExpressionNode * value = parse_expression ( false ) ;
if ( value = = nullptr ) {
push_error ( R " (Expected expression as dictionary value.) " ) ;
2017-03-05 16:44:50 +01:00
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( key ! = nullptr & & value ! = nullptr ) {
dictionary - > elements . push_back ( { key , value } ) ;
}
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) & & ! is_at_end ( ) ) ;
}
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : BRACE_CLOSE , R " (Expected closing " } " after dictionary elements.) " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( dictionary ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return dictionary ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_grouping ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
ExpressionNode * grouped = parse_expression ( false ) ;
pop_multiline ( ) ;
2020-08-05 21:42:33 +02:00
if ( grouped = = nullptr ) {
push_error ( R " (Expected grouping expression.) " ) ;
} else {
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected closing " ) " after grouping expression.)* " ) ;
}
2020-05-02 00:14:56 +02:00
return grouped ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_attribute ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
SubscriptNode * attribute = alloc_node < SubscriptNode > ( ) ;
2022-07-11 20:31:15 +02:00
reset_extents ( attribute , p_previous_operand ) ;
update_extents ( attribute ) ;
2014-02-10 02:10:30 +01:00
2020-07-06 17:24:24 +02:00
if ( for_completion ) {
bool is_builtin = false ;
2021-04-05 16:17:59 +02:00
if ( p_previous_operand & & p_previous_operand - > type = = Node : : IDENTIFIER ) {
2020-07-06 17:24:24 +02:00
const IdentifierNode * id = static_cast < const IdentifierNode * > ( p_previous_operand ) ;
Variant : : Type builtin_type = get_builtin_type ( id - > name ) ;
if ( builtin_type < Variant : : VARIANT_MAX ) {
2022-03-10 12:55:54 +01:00
make_completion_context ( COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD , builtin_type , true ) ;
2020-07-06 17:24:24 +02:00
is_builtin = true ;
}
}
if ( ! is_builtin ) {
make_completion_context ( COMPLETION_ATTRIBUTE , attribute , - 1 , true ) ;
}
}
2020-05-02 00:14:56 +02:00
attribute - > base = p_previous_operand ;
2014-02-10 02:10:30 +01:00
2023-04-05 19:52:01 +02:00
if ( current . is_node_name ( ) ) {
current . type = GDScriptTokenizer : : Token : : IDENTIFIER ;
}
2020-05-02 00:14:56 +02:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected identifier after " . " for attribute access.) " ) ) {
2022-07-11 20:31:15 +02:00
complete_extents ( attribute ) ;
2020-07-06 17:24:24 +02:00
return attribute ;
2020-05-02 00:14:56 +02:00
}
2022-02-18 13:52:46 +01:00
attribute - > is_attribute = true ;
2020-05-02 00:14:56 +02:00
attribute - > attribute = parse_identifier ( ) ;
2014-02-10 02:10:30 +01:00
2022-07-11 20:31:15 +02:00
complete_extents ( attribute ) ;
2020-05-02 00:14:56 +02:00
return attribute ;
}
2016-07-22 14:22:34 +02:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_subscript ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
SubscriptNode * subscript = alloc_node < SubscriptNode > ( ) ;
2022-07-11 20:31:15 +02:00
reset_extents ( subscript , p_previous_operand ) ;
update_extents ( subscript ) ;
2014-02-10 02:10:30 +01:00
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_SUBSCRIPT , subscript ) ;
2020-05-02 00:14:56 +02:00
subscript - > base = p_previous_operand ;
subscript - > index = parse_expression ( false ) ;
2014-02-10 02:10:30 +01:00
2021-09-17 19:31:51 +02:00
if ( subscript - > index = = nullptr ) {
push_error ( R " (Expected expression after " [ " .) " ) ;
}
2020-05-02 00:14:56 +02:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : BRACKET_CLOSE , R " (Expected " ] " after subscription index.) " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( subscript ) ;
2014-09-15 16:33:30 +02:00
2020-05-02 00:14:56 +02:00
return subscript ;
}
2014-09-15 16:33:30 +02:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_cast ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
CastNode * cast = alloc_node < CastNode > ( ) ;
2022-07-11 20:31:15 +02:00
reset_extents ( cast , p_previous_operand ) ;
update_extents ( cast ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
cast - > operand = p_previous_operand ;
cast - > cast_type = parse_type ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( cast ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( cast - > cast_type = = nullptr ) {
push_error ( R " (Expected type specifier after " as " .) " ) ;
return p_previous_operand ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
return cast ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_call ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
CallNode * call = alloc_node < CallNode > ( ) ;
2022-07-11 20:31:15 +02:00
reset_extents ( call , p_previous_operand ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( previous . type = = GDScriptTokenizer : : Token : : SUPER ) {
// Super call.
call - > is_super = true ;
push_multiline ( true ) ;
if ( match ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN ) ) {
// Implicit call to the parent method of the same name.
if ( current_function = = nullptr ) {
push_error ( R " (Cannot use implicit " super " call outside of a function.) " ) ;
pop_multiline ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( call ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
}
2022-03-30 16:53:49 +02:00
if ( current_function - > identifier ) {
call - > function_name = current_function - > identifier - > name ;
} else {
call - > function_name = SNAME ( " <anonymous> " ) ;
}
2020-05-02 00:14:56 +02:00
} else {
consume ( GDScriptTokenizer : : Token : : PERIOD , R " (Expected " . " or " ( " after " super " .) " ) ;
2020-07-16 03:02:44 +02:00
make_completion_context ( COMPLETION_SUPER_METHOD , call , true ) ;
2020-05-02 00:14:56 +02:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected function name after " . " .) " ) ) {
pop_multiline ( ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( call ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
}
2020-06-10 23:18:10 +02:00
IdentifierNode * identifier = parse_identifier ( ) ;
call - > callee = identifier ;
call - > function_name = identifier - > name ;
2020-05-02 00:14:56 +02:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected " ( " after function name.) " ) ;
}
} else {
call - > callee = p_previous_operand ;
2020-06-10 23:18:10 +02:00
2020-08-05 21:41:46 +02:00
if ( call - > callee = = nullptr ) {
push_error ( R " *(Cannot call on an expression. Use " . call ( ) " if it's a Callable.)* " ) ;
} else if ( call - > callee - > type = = Node : : IDENTIFIER ) {
2020-06-10 23:18:10 +02:00
call - > function_name = static_cast < IdentifierNode * > ( call - > callee ) - > name ;
2020-07-16 03:02:44 +02:00
make_completion_context ( COMPLETION_METHOD , call - > callee ) ;
2020-06-10 23:18:10 +02:00
} else if ( call - > callee - > type = = Node : : SUBSCRIPT ) {
SubscriptNode * attribute = static_cast < SubscriptNode * > ( call - > callee ) ;
if ( attribute - > is_attribute ) {
2020-07-16 03:02:44 +02:00
if ( attribute - > attribute ) {
call - > function_name = attribute - > attribute - > name ;
}
make_completion_context ( COMPLETION_ATTRIBUTE_METHOD , call - > callee ) ;
2020-06-10 23:18:10 +02:00
} else {
// TODO: The analyzer can see if this is actually a Callable and give better error message.
push_error ( R " *(Cannot call on an expression. Use " . call ( ) " if it's a Callable.)* " ) ;
}
} else {
push_error ( R " *(Cannot call on an expression. Use " . call ( ) " if it's a Callable.)* " ) ;
}
2020-05-02 00:14:56 +02:00
}
2014-11-06 01:20:42 +01:00
2020-11-06 09:53:51 +01:00
// Arguments.
CompletionType ct = COMPLETION_CALL_ARGUMENTS ;
2022-11-27 08:56:53 +01:00
if ( call - > function_name = = SNAME ( " load " ) ) {
2020-11-06 09:53:51 +01:00
ct = COMPLETION_RESOURCE_PATH ;
2020-05-02 00:14:56 +02:00
}
2020-11-06 09:53:51 +01:00
push_completion_call ( call ) ;
int argument_index = 0 ;
do {
make_completion_context ( ct , call , argument_index + + , true ) ;
if ( check ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE ) ) {
// Allow for trailing comma.
break ;
}
2024-01-22 15:31:55 +01:00
bool use_identifier_completion = current . cursor_place = = GDScriptTokenizerText : : CURSOR_END | | current . cursor_place = = GDScriptTokenizerText : : CURSOR_MIDDLE ;
2020-11-06 09:53:51 +01:00
ExpressionNode * argument = parse_expression ( false ) ;
if ( argument = = nullptr ) {
push_error ( R " (Expected expression as the function argument.) " ) ;
} else {
call - > arguments . push_back ( argument ) ;
2022-08-22 10:59:24 +02:00
2022-10-02 11:56:46 +02:00
if ( argument - > type = = Node : : IDENTIFIER & & use_identifier_completion ) {
2022-08-22 10:59:24 +02:00
completion_context . type = COMPLETION_IDENTIFIER ;
}
2020-11-06 09:53:51 +01:00
}
ct = COMPLETION_CALL_ARGUMENTS ;
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
pop_completion_call ( ) ;
2014-11-06 01:20:42 +01:00
2020-05-02 00:14:56 +02:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected closing " ) " after call arguments.)* " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( call ) ;
2014-11-06 01:20:42 +01:00
2020-05-02 00:14:56 +02:00
return call ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_get_node ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
2023-11-10 12:01:17 +01:00
// We want code completion after a DOLLAR even if the current code is invalid.
make_completion_context ( COMPLETION_GET_NODE , nullptr , - 1 , true ) ;
2022-05-26 17:56:39 +02:00
if ( ! current . is_node_name ( ) & & ! check ( GDScriptTokenizer : : Token : : LITERAL ) & & ! check ( GDScriptTokenizer : : Token : : SLASH ) & & ! check ( GDScriptTokenizer : : Token : : PERCENT ) ) {
push_error ( vformat ( R " (Expected node path as string or identifier after " % s " .) " , previous . get_name ( ) ) ) ;
return nullptr ;
}
if ( check ( GDScriptTokenizer : : Token : : LITERAL ) ) {
if ( current . literal . get_type ( ) ! = Variant : : STRING ) {
push_error ( vformat ( R " (Expected node path as string or identifier after " % s " .) " , previous . get_name ( ) ) ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
}
2022-05-26 17:56:39 +02:00
}
GetNodeNode * get_node = alloc_node < GetNodeNode > ( ) ;
// Store the last item in the path so the parser knows what to expect.
// Allow allows more specific error messages.
enum PathState {
PATH_STATE_START ,
PATH_STATE_SLASH ,
PATH_STATE_PERCENT ,
PATH_STATE_NODE_NAME ,
} path_state = PATH_STATE_START ;
if ( previous . type = = GDScriptTokenizer : : Token : : DOLLAR ) {
// Detect initial slash, which will be handled in the loop if it matches.
match ( GDScriptTokenizer : : Token : : SLASH ) ;
} else {
get_node - > use_dollar = false ;
}
int context_argument = 0 ;
do {
if ( previous . type = = GDScriptTokenizer : : Token : : PERCENT ) {
if ( path_state ! = PATH_STATE_START & & path_state ! = PATH_STATE_SLASH ) {
push_error ( R " ( " % " is only valid in the beginning of a node name (either after " $ " or after " / " )) " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( get_node ) ;
2021-10-28 20:16:22 +02:00
return nullptr ;
}
2024-02-28 15:23:11 +01:00
2022-05-26 17:56:39 +02:00
get_node - > full_path + = " % " ;
path_state = PATH_STATE_PERCENT ;
} else if ( previous . type = = GDScriptTokenizer : : Token : : SLASH ) {
if ( path_state ! = PATH_STATE_START & & path_state ! = PATH_STATE_NODE_NAME ) {
push_error ( R " ( " / " is only valid at the beginning of the path or after a node name.) " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( get_node ) ;
2022-05-26 17:56:39 +02:00
return nullptr ;
}
get_node - > full_path + = " / " ;
path_state = PATH_STATE_SLASH ;
}
2023-11-10 12:01:17 +01:00
make_completion_context ( COMPLETION_GET_NODE , get_node , context_argument + + , true ) ;
2022-05-26 17:56:39 +02:00
if ( match ( GDScriptTokenizer : : Token : : LITERAL ) ) {
if ( previous . literal . get_type ( ) ! = Variant : : STRING ) {
String previous_token ;
switch ( path_state ) {
case PATH_STATE_START :
previous_token = " $ " ;
break ;
case PATH_STATE_PERCENT :
previous_token = " % " ;
break ;
case PATH_STATE_SLASH :
previous_token = " / " ;
break ;
default :
break ;
}
push_error ( vformat ( R " (Expected node path as string or identifier after " % s " .) " , previous_token ) ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( get_node ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
2014-02-10 02:10:30 +01:00
}
2022-05-26 17:56:39 +02:00
get_node - > full_path + = previous . literal . operator String ( ) ;
path_state = PATH_STATE_NODE_NAME ;
} else if ( current . is_node_name ( ) ) {
2020-08-19 15:19:05 +02:00
advance ( ) ;
2024-02-28 15:23:11 +01:00
2023-02-09 15:17:37 +01:00
String identifier = previous . get_identifier ( ) ;
# ifdef DEBUG_ENABLED
// Check spoofing.
if ( TS - > has_feature ( TextServer : : FEATURE_UNICODE_SECURITY ) & & TS - > spoof_check ( identifier ) ) {
push_warning ( get_node , GDScriptWarning : : CONFUSABLE_IDENTIFIER , identifier ) ;
}
# endif
get_node - > full_path + = identifier ;
2022-05-26 17:56:39 +02:00
path_state = PATH_STATE_NODE_NAME ;
} else if ( ! check ( GDScriptTokenizer : : Token : : SLASH ) & & ! check ( GDScriptTokenizer : : Token : : PERCENT ) ) {
push_error ( vformat ( R " (Unexpected " % s " in node path.) " , current . get_name ( ) ) ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( get_node ) ;
2022-05-26 17:56:39 +02:00
return nullptr ;
}
} while ( match ( GDScriptTokenizer : : Token : : SLASH ) | | match ( GDScriptTokenizer : : Token : : PERCENT ) ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( get_node ) ;
2022-05-26 17:56:39 +02:00
return get_node ;
2020-05-02 00:14:56 +02:00
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_preload ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
PreloadNode * preload = alloc_node < PreloadNode > ( ) ;
preload - > resolved_path = " <missing path> " ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
push_multiline ( true ) ;
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected " ( " after " preload " .) " ) ;
2016-12-29 11:31:19 +01:00
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_RESOURCE_PATH , preload ) ;
push_completion_call ( preload ) ;
2020-05-02 00:14:56 +02:00
preload - > path = parse_expression ( false ) ;
if ( preload - > path = = nullptr ) {
push_error ( R " (Expected resource path after " ( " .) " ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
2020-07-06 17:24:24 +02:00
pop_completion_call ( ) ;
2020-05-02 00:14:56 +02:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected " ) " after preload path.)* " ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( preload ) ;
2020-05-02 00:14:56 +02:00
return preload ;
2014-02-10 02:10:30 +01:00
}
2021-03-25 14:36:29 +01:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_lambda ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
LambdaNode * lambda = alloc_node < LambdaNode > ( ) ;
2021-03-26 13:03:16 +01:00
lambda - > parent_function = current_function ;
2023-08-23 11:37:18 +02:00
lambda - > parent_lambda = current_lambda ;
2021-03-25 14:36:29 +01:00
FunctionNode * function = alloc_node < FunctionNode > ( ) ;
2021-03-26 13:03:16 +01:00
function - > source_lambda = lambda ;
function - > is_static = current_function ! = nullptr ? current_function - > is_static : false ;
2021-03-25 14:36:29 +01:00
if ( match ( GDScriptTokenizer : : Token : : IDENTIFIER ) ) {
function - > identifier = parse_identifier ( ) ;
}
bool multiline_context = multiline_stack . back ( ) - > get ( ) ;
// Reset the multiline stack since we don't want the multiline mode one in the lambda body.
push_multiline ( false ) ;
if ( multiline_context ) {
2024-01-22 15:31:55 +01:00
tokenizer - > push_expression_indented_block ( ) ;
2021-03-25 14:36:29 +01:00
}
push_multiline ( true ) ; // For the parameters.
if ( function - > identifier ) {
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected opening " ( " after lambda name.) " ) ;
} else {
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected opening " ( " after " func " .) " ) ;
}
FunctionNode * previous_function = current_function ;
current_function = function ;
2023-08-23 11:37:18 +02:00
LambdaNode * previous_lambda = current_lambda ;
current_lambda = lambda ;
2021-03-25 14:36:29 +01:00
SuiteNode * body = alloc_node < SuiteNode > ( ) ;
2022-05-24 02:38:31 +02:00
body - > parent_function = current_function ;
body - > parent_block = current_suite ;
2021-03-25 14:36:29 +01:00
SuiteNode * previous_suite = current_suite ;
current_suite = body ;
parse_function_signature ( function , body , " lambda " ) ;
current_suite = previous_suite ;
bool previous_in_lambda = in_lambda ;
in_lambda = true ;
2023-02-23 03:13:09 +01:00
// Save break/continue state.
bool could_break = can_break ;
bool could_continue = can_continue ;
// Disallow break/continue.
can_break = false ;
can_continue = false ;
2021-03-25 14:36:29 +01:00
function - > body = parse_suite ( " lambda declaration " , body , true ) ;
2022-07-11 20:31:15 +02:00
complete_extents ( function ) ;
complete_extents ( lambda ) ;
2021-03-25 14:36:29 +01:00
pop_multiline ( ) ;
if ( multiline_context ) {
// If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens.
while ( check ( GDScriptTokenizer : : Token : : DEDENT ) | | check ( GDScriptTokenizer : : Token : : INDENT ) | | check ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
2024-01-22 15:31:55 +01:00
current = tokenizer - > scan ( ) ; // Not advance() since we don't want to change the previous token.
2021-03-25 14:36:29 +01:00
}
2024-01-22 15:31:55 +01:00
tokenizer - > pop_expression_indented_block ( ) ;
2021-03-25 14:36:29 +01:00
}
current_function = previous_function ;
2023-08-23 11:37:18 +02:00
current_lambda = previous_lambda ;
2021-03-25 14:36:29 +01:00
in_lambda = previous_in_lambda ;
lambda - > function = function ;
2023-02-23 03:13:09 +01:00
// Reset break/continue state.
can_break = could_break ;
can_continue = could_continue ;
2021-03-25 14:36:29 +01:00
return lambda ;
}
2023-02-17 00:16:24 +01:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_type_test ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
2024-02-04 13:40:19 +01:00
// x is not int
// ^ ^^^ ExpressionNode, TypeNode
// ^^^^^^^^^^^^ TypeTestNode
// ^^^^^^^^^^^^ UnaryOpNode
UnaryOpNode * not_node = nullptr ;
if ( match ( GDScriptTokenizer : : Token : : NOT ) ) {
not_node = alloc_node < UnaryOpNode > ( ) ;
not_node - > operation = UnaryOpNode : : OP_LOGIC_NOT ;
not_node - > variant_op = Variant : : OP_NOT ;
reset_extents ( not_node , p_previous_operand ) ;
update_extents ( not_node ) ;
}
2023-02-17 00:16:24 +01:00
TypeTestNode * type_test = alloc_node < TypeTestNode > ( ) ;
reset_extents ( type_test , p_previous_operand ) ;
update_extents ( type_test ) ;
type_test - > operand = p_previous_operand ;
type_test - > test_type = parse_type ( ) ;
complete_extents ( type_test ) ;
2024-02-04 13:40:19 +01:00
if ( not_node ! = nullptr ) {
not_node - > operand = type_test ;
complete_extents ( not_node ) ;
}
2023-02-17 00:16:24 +01:00
if ( type_test - > test_type = = nullptr ) {
2024-02-04 13:40:19 +01:00
if ( not_node = = nullptr ) {
push_error ( R " (Expected type specifier after " is " .) " ) ;
} else {
push_error ( R " (Expected type specifier after " is not " .) " ) ;
}
}
if ( not_node ! = nullptr ) {
return not_node ;
2023-02-17 00:16:24 +01:00
}
return type_test ;
}
2021-09-21 19:38:14 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_yield ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
2023-07-10 13:08:11 +02:00
push_error ( R " ( " yield " was removed in Godot 4. Use " await " instead.) " ) ;
2021-09-21 19:38:14 +02:00
return nullptr ;
}
2020-05-02 00:14:56 +02:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_invalid_token ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
// Just for better error messages.
GDScriptTokenizer : : Token : : Type invalid = previous . type ;
switch ( invalid ) {
case GDScriptTokenizer : : Token : : QUESTION_MARK :
push_error ( R " (Unexpected " ? " in source. If you want a ternary operator, use " truthy_value if true_condition else falsy_value " .) " ) ;
break ;
default :
return nullptr ; // Unreachable.
2020-05-14 16:41:43 +02:00
}
2020-05-02 00:14:56 +02:00
// Return the previous expression.
return p_previous_operand ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
GDScriptParser : : TypeNode * GDScriptParser : : parse_type ( bool p_allow_void ) {
2020-07-06 17:24:24 +02:00
TypeNode * type = alloc_node < TypeNode > ( ) ;
make_completion_context ( p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME , type ) ;
2020-05-02 00:14:56 +02:00
if ( ! match ( GDScriptTokenizer : : Token : : IDENTIFIER ) ) {
if ( match ( GDScriptTokenizer : : Token : : VOID ) ) {
if ( p_allow_void ) {
2022-07-11 20:31:15 +02:00
complete_extents ( type ) ;
TypeNode * void_type = type ;
2020-07-16 03:02:44 +02:00
return void_type ;
2020-05-02 00:14:56 +02:00
} else {
push_error ( R " ( " void " is only allowed for a function return type.) " ) ;
2020-05-14 16:41:43 +02:00
}
2020-03-09 00:24:12 +01:00
}
2020-05-02 00:14:56 +02:00
// Leave error message to the caller who knows the context.
2022-07-11 20:31:15 +02:00
complete_extents ( type ) ;
2020-05-02 00:14:56 +02:00
return nullptr ;
2020-03-09 00:24:12 +01:00
}
2020-06-10 23:18:10 +02:00
IdentifierNode * type_element = parse_identifier ( ) ;
2020-03-09 00:24:12 +01:00
2020-06-10 23:18:10 +02:00
type - > type_chain . push_back ( type_element ) ;
2021-03-09 16:32:35 +01:00
if ( match ( GDScriptTokenizer : : Token : : BRACKET_OPEN ) ) {
// Typed collection (like Array[int]).
2023-09-14 20:31:07 +02:00
bool first_pass = true ;
do {
TypeNode * container_type = parse_type ( false ) ; // Don't allow void for element type.
if ( container_type = = nullptr ) {
push_error ( vformat ( R " (Expected type for collection after " % s " .) " , first_pass ? " [ " : " , " ) ) ;
complete_extents ( type ) ;
type = nullptr ;
break ;
} else if ( container_type - > container_types . size ( ) > 0 ) {
push_error ( " Nested typed collections are not supported. " ) ;
} else {
type - > container_types . append ( container_type ) ;
}
first_pass = false ;
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
2021-03-09 16:32:35 +01:00
consume ( GDScriptTokenizer : : Token : : BRACKET_CLOSE , R " (Expected closing " ] " after collection type.) " ) ;
2022-07-11 20:31:15 +02:00
if ( type ! = nullptr ) {
complete_extents ( type ) ;
}
2021-03-09 16:32:35 +01:00
return type ;
}
2020-07-06 17:24:24 +02:00
int chain_index = 1 ;
2020-06-10 23:18:10 +02:00
while ( match ( GDScriptTokenizer : : Token : : PERIOD ) ) {
2020-07-06 17:24:24 +02:00
make_completion_context ( COMPLETION_TYPE_ATTRIBUTE , type , chain_index + + ) ;
2020-06-10 23:18:10 +02:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected inner type name after " . " .) " ) ) {
type_element = parse_identifier ( ) ;
type - > type_chain . push_back ( type_element ) ;
2020-03-09 00:24:12 +01:00
}
2020-05-02 00:14:56 +02:00
}
2022-07-11 20:31:15 +02:00
complete_extents ( type ) ;
2020-05-02 00:14:56 +02:00
return type ;
}
2020-11-29 03:37:57 +01:00
# ifdef TOOLS_ENABLED
2023-03-17 22:37:52 +01:00
enum DocLineState {
DOC_LINE_NORMAL ,
DOC_LINE_IN_CODE ,
DOC_LINE_IN_CODEBLOCK ,
2024-03-08 20:16:55 +01:00
DOC_LINE_IN_KBD ,
2023-03-17 22:37:52 +01:00
} ;
static String _process_doc_line ( const String & p_line , const String & p_text , const String & p_space_prefix , DocLineState & r_state ) {
String line = p_line ;
if ( r_state = = DOC_LINE_NORMAL ) {
line = line . strip_edges ( true , false ) ;
} else {
line = line . trim_prefix ( p_space_prefix ) ;
2020-11-29 03:37:57 +01:00
}
2023-03-17 22:37:52 +01:00
String line_join ;
if ( ! p_text . is_empty ( ) ) {
if ( r_state = = DOC_LINE_NORMAL ) {
if ( p_text . ends_with ( " [/codeblock] " ) ) {
line_join = " \n " ;
} else if ( ! p_text . ends_with ( " [br] " ) ) {
line_join = " " ;
}
2020-11-29 03:37:57 +01:00
} else {
2023-03-17 22:37:52 +01:00
line_join = " \n " ;
2020-11-29 03:37:57 +01:00
}
2023-03-17 22:37:52 +01:00
}
String result ;
int from = 0 ;
int buffer_start = 0 ;
const int len = line . length ( ) ;
bool process = true ;
while ( process ) {
switch ( r_state ) {
case DOC_LINE_NORMAL : {
int lb_pos = line . find_char ( ' [ ' , from ) ;
if ( lb_pos < 0 ) {
process = false ;
break ;
}
int rb_pos = line . find_char ( ' ] ' , lb_pos + 1 ) ;
if ( rb_pos < 0 ) {
process = false ;
break ;
}
from = rb_pos + 1 ;
String tag = line . substr ( lb_pos + 1 , rb_pos - lb_pos - 1 ) ;
2024-03-08 20:16:55 +01:00
if ( tag = = " code " | | tag . begins_with ( " code " ) ) {
2023-03-17 22:37:52 +01:00
r_state = DOC_LINE_IN_CODE ;
2024-03-08 20:16:55 +01:00
} else if ( tag = = " codeblock " | | tag . begins_with ( " codeblock " ) ) {
2023-03-17 22:37:52 +01:00
if ( lb_pos = = 0 ) {
line_join = " \n " ;
} else {
result + = line . substr ( buffer_start , lb_pos - buffer_start ) + ' \n ' ;
}
2024-03-08 20:16:55 +01:00
result + = " [ " + tag + " ] " ;
2023-03-17 22:37:52 +01:00
if ( from < len ) {
result + = ' \n ' ;
}
r_state = DOC_LINE_IN_CODEBLOCK ;
buffer_start = from ;
2024-03-08 20:16:55 +01:00
} else if ( tag = = " kbd " ) {
r_state = DOC_LINE_IN_KBD ;
2023-03-17 22:37:52 +01:00
}
} break ;
case DOC_LINE_IN_CODE : {
int pos = line . find ( " [/code] " , from ) ;
if ( pos < 0 ) {
process = false ;
break ;
}
2024-03-08 20:16:55 +01:00
from = pos + 7 ; // `len("[/code]")`.
2023-03-17 22:37:52 +01:00
r_state = DOC_LINE_NORMAL ;
} break ;
case DOC_LINE_IN_CODEBLOCK : {
int pos = line . find ( " [/codeblock] " , from ) ;
if ( pos < 0 ) {
process = false ;
break ;
}
2024-03-08 20:16:55 +01:00
from = pos + 12 ; // `len("[/codeblock]")`.
2023-03-17 22:37:52 +01:00
if ( pos = = 0 ) {
line_join = " \n " ;
} else {
result + = line . substr ( buffer_start , pos - buffer_start ) + ' \n ' ;
}
result + = " [/codeblock] " ;
if ( from < len ) {
result + = ' \n ' ;
}
r_state = DOC_LINE_NORMAL ;
buffer_start = from ;
} break ;
2024-03-08 20:16:55 +01:00
case DOC_LINE_IN_KBD : {
int pos = line . find ( " [/kbd] " , from ) ;
if ( pos < 0 ) {
process = false ;
break ;
}
from = pos + 6 ; // `len("[/kbd]")`.
r_state = DOC_LINE_NORMAL ;
} break ;
2020-11-29 03:37:57 +01:00
}
}
2023-03-17 22:37:52 +01:00
result + = line . substr ( buffer_start ) ;
if ( r_state = = DOC_LINE_NORMAL ) {
result = result . strip_edges ( false , true ) ;
}
return line_join + result ;
2020-11-29 03:37:57 +01:00
}
2023-04-21 15:32:26 +02:00
bool GDScriptParser : : has_comment ( int p_line , bool p_must_be_doc ) {
2024-01-22 15:31:55 +01:00
bool has_comment = tokenizer - > get_comments ( ) . has ( p_line ) ;
2023-04-21 15:32:26 +02:00
// If there are no comments or if we don't care whether the comment
// is a docstring, we have our result.
if ( ! p_must_be_doc | | ! has_comment ) {
return has_comment ;
}
2024-01-22 15:31:55 +01:00
return tokenizer - > get_comments ( ) [ p_line ] . comment . begins_with ( " ## " ) ;
2020-11-29 03:37:57 +01:00
}
2023-07-02 12:13:38 +02:00
GDScriptParser : : MemberDocData GDScriptParser : : parse_doc_comment ( int p_line , bool p_single_line ) {
2023-09-15 17:59:30 +02:00
ERR_FAIL_COND_V ( ! has_comment ( p_line , true ) , MemberDocData ( ) ) ;
2023-07-02 12:13:38 +02:00
2024-01-22 15:31:55 +01:00
const HashMap < int , GDScriptTokenizer : : CommentData > & comments = tokenizer - > get_comments ( ) ;
2020-11-29 03:37:57 +01:00
int line = p_line ;
2023-09-15 17:59:30 +02:00
if ( ! p_single_line ) {
while ( comments . has ( line - 1 ) & & comments [ line - 1 ] . new_line & & comments [ line - 1 ] . comment . begins_with ( " ## " ) ) {
line - - ;
2020-11-29 03:37:57 +01:00
}
}
2023-09-15 17:59:30 +02:00
max_script_doc_line = MIN ( max_script_doc_line , line - 1 ) ;
2023-03-17 22:37:52 +01:00
String space_prefix ;
2023-09-15 17:59:30 +02:00
{
2023-03-17 22:37:52 +01:00
int i = 2 ;
for ( ; i < comments [ line ] . comment . length ( ) ; i + + ) {
if ( comments [ line ] . comment [ i ] ! = ' ' ) {
break ;
}
}
space_prefix = String ( " " ) . repeat ( i - 2 ) ;
}
2023-09-15 17:59:30 +02:00
DocLineState state = DOC_LINE_NORMAL ;
MemberDocData result ;
2020-11-29 03:37:57 +01:00
2023-09-15 17:59:30 +02:00
while ( line < = p_line ) {
2023-03-17 22:37:52 +01:00
String doc_line = comments [ line ] . comment . trim_prefix ( " ## " ) ;
2020-11-29 03:37:57 +01:00
line + + ;
2023-07-02 12:13:38 +02:00
if ( state = = DOC_LINE_NORMAL ) {
String stripped_line = doc_line . strip_edges ( ) ;
2024-02-12 14:55:02 +01:00
if ( stripped_line = = " @deprecated " | | stripped_line . begins_with ( " @deprecated: " ) ) {
2023-07-02 12:13:38 +02:00
result . is_deprecated = true ;
2024-02-12 14:55:02 +01:00
if ( stripped_line . begins_with ( " @deprecated: " ) ) {
result . deprecated_message = stripped_line . trim_prefix ( " @deprecated: " ) . strip_edges ( ) ;
}
2023-07-02 12:13:38 +02:00
continue ;
2024-02-12 14:55:02 +01:00
} else if ( stripped_line = = " @experimental " | | stripped_line . begins_with ( " @experimental: " ) ) {
2023-07-02 12:13:38 +02:00
result . is_experimental = true ;
2024-02-12 14:55:02 +01:00
if ( stripped_line . begins_with ( " @experimental: " ) ) {
result . experimental_message = stripped_line . trim_prefix ( " @experimental: " ) . strip_edges ( ) ;
}
2023-07-02 12:13:38 +02:00
continue ;
}
}
result . description + = _process_doc_line ( doc_line , result . description , space_prefix , state ) ;
2020-11-29 03:37:57 +01:00
}
2023-07-02 12:13:38 +02:00
return result ;
2020-11-29 03:37:57 +01:00
}
2023-09-15 17:59:30 +02:00
GDScriptParser : : ClassDocData GDScriptParser : : parse_class_doc_comment ( int p_line , bool p_single_line ) {
ERR_FAIL_COND_V ( ! has_comment ( p_line , true ) , ClassDocData ( ) ) ;
2023-07-02 12:13:38 +02:00
2024-01-22 15:31:55 +01:00
const HashMap < int , GDScriptTokenizer : : CommentData > & comments = tokenizer - > get_comments ( ) ;
2020-11-29 03:37:57 +01:00
int line = p_line ;
2023-09-15 17:59:30 +02:00
if ( ! p_single_line ) {
while ( comments . has ( line - 1 ) & & comments [ line - 1 ] . new_line & & comments [ line - 1 ] . comment . begins_with ( " ## " ) ) {
2020-11-29 03:37:57 +01:00
line - - ;
}
}
2023-09-15 17:59:30 +02:00
max_script_doc_line = MIN ( max_script_doc_line , line - 1 ) ;
2023-03-17 22:37:52 +01:00
String space_prefix ;
2023-09-15 17:59:30 +02:00
{
2023-03-17 22:37:52 +01:00
int i = 2 ;
for ( ; i < comments [ line ] . comment . length ( ) ; i + + ) {
if ( comments [ line ] . comment [ i ] ! = ' ' ) {
break ;
}
}
space_prefix = String ( " " ) . repeat ( i - 2 ) ;
}
2023-09-15 17:59:30 +02:00
DocLineState state = DOC_LINE_NORMAL ;
bool is_in_brief = true ;
ClassDocData result ;
2020-11-29 03:37:57 +01:00
2023-09-15 17:59:30 +02:00
while ( line < = p_line ) {
2023-07-02 12:13:38 +02:00
String doc_line = comments [ line ] . comment . trim_prefix ( " ## " ) ;
line + + ;
2020-11-29 03:37:57 +01:00
2023-03-17 22:37:52 +01:00
if ( state = = DOC_LINE_NORMAL ) {
String stripped_line = doc_line . strip_edges ( ) ;
2023-07-02 12:13:38 +02:00
// A blank line separates the description from the brief.
if ( is_in_brief & & ! result . brief . is_empty ( ) & & stripped_line . is_empty ( ) ) {
is_in_brief = false ;
2023-03-17 22:37:52 +01:00
continue ;
2023-07-02 12:13:38 +02:00
}
if ( stripped_line . begins_with ( " @tutorial " ) ) {
String title , link ;
2023-03-17 22:37:52 +01:00
int begin_scan = String ( " @tutorial " ) . length ( ) ;
if ( begin_scan > = stripped_line . length ( ) ) {
continue ; // Invalid syntax.
2020-11-29 03:37:57 +01:00
}
2023-03-17 22:37:52 +01:00
if ( stripped_line [ begin_scan ] = = ' : ' ) { // No title.
// Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional.
title = " " ;
link = stripped_line . trim_prefix ( " @tutorial: " ) . strip_edges ( ) ;
} else {
/* Syntax:
* @ tutorial ( The Title Here ) : https : //the.url/
* ^ open ^ close ^ colon ^ url
*/
int open_bracket_pos = begin_scan , close_bracket_pos = 0 ;
while ( open_bracket_pos < stripped_line . length ( ) & & ( stripped_line [ open_bracket_pos ] = = ' ' | | stripped_line [ open_bracket_pos ] = = ' \t ' ) ) {
open_bracket_pos + + ;
}
if ( open_bracket_pos = = stripped_line . length ( ) | | stripped_line [ open_bracket_pos + + ] ! = ' ( ' ) {
continue ; // Invalid syntax.
}
close_bracket_pos = open_bracket_pos ;
while ( close_bracket_pos < stripped_line . length ( ) & & stripped_line [ close_bracket_pos ] ! = ' ) ' ) {
close_bracket_pos + + ;
}
if ( close_bracket_pos = = stripped_line . length ( ) ) {
continue ; // Invalid syntax.
}
2020-11-29 03:37:57 +01:00
2023-03-17 22:37:52 +01:00
int colon_pos = close_bracket_pos + 1 ;
while ( colon_pos < stripped_line . length ( ) & & ( stripped_line [ colon_pos ] = = ' ' | | stripped_line [ colon_pos ] = = ' \t ' ) ) {
colon_pos + + ;
}
if ( colon_pos = = stripped_line . length ( ) | | stripped_line [ colon_pos + + ] ! = ' : ' ) {
continue ; // Invalid syntax.
}
2020-11-29 03:37:57 +01:00
2023-03-17 22:37:52 +01:00
title = stripped_line . substr ( open_bracket_pos , close_bracket_pos - open_bracket_pos ) . strip_edges ( ) ;
link = stripped_line . substr ( colon_pos ) . strip_edges ( ) ;
2020-11-29 03:37:57 +01:00
}
2023-03-17 22:37:52 +01:00
2023-07-02 12:13:38 +02:00
result . tutorials . append ( Pair < String , String > ( title , link ) ) ;
continue ;
2024-02-12 14:55:02 +01:00
} else if ( stripped_line = = " @deprecated " | | stripped_line . begins_with ( " @deprecated: " ) ) {
2023-07-02 12:13:38 +02:00
result . is_deprecated = true ;
2024-02-12 14:55:02 +01:00
if ( stripped_line . begins_with ( " @deprecated: " ) ) {
result . deprecated_message = stripped_line . trim_prefix ( " @deprecated: " ) . strip_edges ( ) ;
}
2023-07-02 12:13:38 +02:00
continue ;
2024-02-12 14:55:02 +01:00
} else if ( stripped_line = = " @experimental " | | stripped_line . begins_with ( " @experimental: " ) ) {
2023-07-02 12:13:38 +02:00
result . is_experimental = true ;
2024-02-12 14:55:02 +01:00
if ( stripped_line . begins_with ( " @experimental: " ) ) {
result . experimental_message = stripped_line . trim_prefix ( " @experimental: " ) . strip_edges ( ) ;
}
2023-07-02 12:13:38 +02:00
continue ;
2020-11-29 03:37:57 +01:00
}
}
2023-07-02 12:13:38 +02:00
if ( is_in_brief ) {
result . brief + = _process_doc_line ( doc_line , result . brief , space_prefix , state ) ;
} else {
result . description + = _process_doc_line ( doc_line , result . description , space_prefix , state ) ;
2022-07-17 22:04:22 +02:00
}
}
2023-03-17 22:37:52 +01:00
2023-07-02 12:13:38 +02:00
return result ;
2020-11-29 03:37:57 +01:00
}
# endif // TOOLS_ENABLED
2020-05-02 00:14:56 +02:00
GDScriptParser : : ParseRule * GDScriptParser : : get_rule ( GDScriptTokenizer : : Token : : Type p_token_type ) {
// Function table for expression parsing.
// clang-format destroys the alignment here, so turn off for the table.
/* clang-format off */
static ParseRule rules [ ] = {
2020-06-10 23:18:10 +02:00
// PREFIX INFIX PRECEDENCE (for infix)
2020-05-02 00:14:56 +02:00
{ nullptr , nullptr , PREC_NONE } , // EMPTY,
// Basic
{ nullptr , nullptr , PREC_NONE } , // ANNOTATION,
{ & GDScriptParser : : parse_identifier , nullptr , PREC_NONE } , // IDENTIFIER,
{ & GDScriptParser : : parse_literal , nullptr , PREC_NONE } , // LITERAL,
// Comparison
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_COMPARISON } , // LESS,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_COMPARISON } , // LESS_EQUAL,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_COMPARISON } , // GREATER,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_COMPARISON } , // GREATER_EQUAL,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_COMPARISON } , // EQUAL_EQUAL,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_COMPARISON } , // BANG_EQUAL,
// Logical
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_LOGIC_AND } , // AND,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_LOGIC_OR } , // OR,
2020-09-01 09:39:17 +02:00
{ & GDScriptParser : : parse_unary_operator , & GDScriptParser : : parse_binary_not_in_operator , PREC_CONTENT_TEST } , // NOT,
2020-05-02 00:14:56 +02:00
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_LOGIC_AND } , // AMPERSAND_AMPERSAND,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_LOGIC_OR } , // PIPE_PIPE,
{ & GDScriptParser : : parse_unary_operator , nullptr , PREC_NONE } , // BANG,
// Bitwise
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_BIT_AND } , // AMPERSAND,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_BIT_OR } , // PIPE,
{ & GDScriptParser : : parse_unary_operator , nullptr , PREC_NONE } , // TILDE,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_BIT_XOR } , // CARET,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_BIT_SHIFT } , // LESS_LESS,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_BIT_SHIFT } , // GREATER_GREATER,
// Math
2021-01-10 14:03:05 +01:00
{ & GDScriptParser : : parse_unary_operator , & GDScriptParser : : parse_binary_operator , PREC_ADDITION_SUBTRACTION } , // PLUS,
{ & GDScriptParser : : parse_unary_operator , & GDScriptParser : : parse_binary_operator , PREC_ADDITION_SUBTRACTION } , // MINUS,
2020-05-02 00:14:56 +02:00
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_FACTOR } , // STAR,
2022-05-12 09:02:03 +02:00
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_POWER } , // STAR_STAR,
2020-05-02 00:14:56 +02:00
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_FACTOR } , // SLASH,
2022-05-26 17:56:39 +02:00
{ & GDScriptParser : : parse_get_node , & GDScriptParser : : parse_binary_operator , PREC_FACTOR } , // PERCENT,
2020-05-02 00:14:56 +02:00
// Assignment
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // EQUAL,
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // PLUS_EQUAL,
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // MINUS_EQUAL,
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // STAR_EQUAL,
2022-03-07 18:25:21 +01:00
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // STAR_STAR_EQUAL,
2020-05-02 00:14:56 +02:00
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // SLASH_EQUAL,
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // PERCENT_EQUAL,
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // LESS_LESS_EQUAL,
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // GREATER_GREATER_EQUAL,
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // AMPERSAND_EQUAL,
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // PIPE_EQUAL,
{ nullptr , & GDScriptParser : : parse_assignment , PREC_ASSIGNMENT } , // CARET_EQUAL,
// Control flow
{ nullptr , & GDScriptParser : : parse_ternary_operator , PREC_TERNARY } , // IF,
{ nullptr , nullptr , PREC_NONE } , // ELIF,
{ nullptr , nullptr , PREC_NONE } , // ELSE,
{ nullptr , nullptr , PREC_NONE } , // FOR,
{ nullptr , nullptr , PREC_NONE } , // WHILE,
{ nullptr , nullptr , PREC_NONE } , // BREAK,
{ nullptr , nullptr , PREC_NONE } , // CONTINUE,
{ nullptr , nullptr , PREC_NONE } , // PASS,
{ nullptr , nullptr , PREC_NONE } , // RETURN,
{ nullptr , nullptr , PREC_NONE } , // MATCH,
2023-07-31 12:47:26 +02:00
{ nullptr , nullptr , PREC_NONE } , // WHEN,
2020-05-02 00:14:56 +02:00
// Keywords
{ nullptr , & GDScriptParser : : parse_cast , PREC_CAST } , // AS,
{ nullptr , nullptr , PREC_NONE } , // ASSERT,
{ & GDScriptParser : : parse_await , nullptr , PREC_NONE } , // AWAIT,
{ nullptr , nullptr , PREC_NONE } , // BREAKPOINT,
{ nullptr , nullptr , PREC_NONE } , // CLASS,
{ nullptr , nullptr , PREC_NONE } , // CLASS_NAME,
{ nullptr , nullptr , PREC_NONE } , // CONST,
{ nullptr , nullptr , PREC_NONE } , // ENUM,
{ nullptr , nullptr , PREC_NONE } , // EXTENDS,
2021-03-25 14:36:29 +01:00
{ & GDScriptParser : : parse_lambda , nullptr , PREC_NONE } , // FUNC,
2020-05-02 00:14:56 +02:00
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_CONTENT_TEST } , // IN,
2023-02-17 00:16:24 +01:00
{ nullptr , & GDScriptParser : : parse_type_test , PREC_TYPE_TEST } , // IS,
2020-05-02 00:14:56 +02:00
{ nullptr , nullptr , PREC_NONE } , // NAMESPACE,
{ & GDScriptParser : : parse_preload , nullptr , PREC_NONE } , // PRELOAD,
{ & GDScriptParser : : parse_self , nullptr , PREC_NONE } , // SELF,
{ nullptr , nullptr , PREC_NONE } , // SIGNAL,
{ nullptr , nullptr , PREC_NONE } , // STATIC,
{ & GDScriptParser : : parse_call , nullptr , PREC_NONE } , // SUPER,
2020-07-16 03:02:44 +02:00
{ nullptr , nullptr , PREC_NONE } , // TRAIT,
2020-05-02 00:14:56 +02:00
{ nullptr , nullptr , PREC_NONE } , // VAR,
{ nullptr , nullptr , PREC_NONE } , // VOID,
2021-09-21 19:38:14 +02:00
{ & GDScriptParser : : parse_yield , nullptr , PREC_NONE } , // YIELD,
2020-05-02 00:14:56 +02:00
// Punctuation
{ & GDScriptParser : : parse_array , & GDScriptParser : : parse_subscript , PREC_SUBSCRIPT } , // BRACKET_OPEN,
{ nullptr , nullptr , PREC_NONE } , // BRACKET_CLOSE,
{ & GDScriptParser : : parse_dictionary , nullptr , PREC_NONE } , // BRACE_OPEN,
{ nullptr , nullptr , PREC_NONE } , // BRACE_CLOSE,
{ & GDScriptParser : : parse_grouping , & GDScriptParser : : parse_call , PREC_CALL } , // PARENTHESIS_OPEN,
{ nullptr , nullptr , PREC_NONE } , // PARENTHESIS_CLOSE,
{ nullptr , nullptr , PREC_NONE } , // COMMA,
{ nullptr , nullptr , PREC_NONE } , // SEMICOLON,
{ nullptr , & GDScriptParser : : parse_attribute , PREC_ATTRIBUTE } , // PERIOD,
{ nullptr , nullptr , PREC_NONE } , // PERIOD_PERIOD,
{ nullptr , nullptr , PREC_NONE } , // COLON,
{ & GDScriptParser : : parse_get_node , nullptr , PREC_NONE } , // DOLLAR,
{ nullptr , nullptr , PREC_NONE } , // FORWARD_ARROW,
{ nullptr , nullptr , PREC_NONE } , // UNDERSCORE,
// Whitespace
{ nullptr , nullptr , PREC_NONE } , // NEWLINE,
{ nullptr , nullptr , PREC_NONE } , // INDENT,
{ nullptr , nullptr , PREC_NONE } , // DEDENT,
// Constants
{ & GDScriptParser : : parse_builtin_constant , nullptr , PREC_NONE } , // CONST_PI,
{ & GDScriptParser : : parse_builtin_constant , nullptr , PREC_NONE } , // CONST_TAU,
{ & GDScriptParser : : parse_builtin_constant , nullptr , PREC_NONE } , // CONST_INF,
{ & GDScriptParser : : parse_builtin_constant , nullptr , PREC_NONE } , // CONST_NAN,
// Error message improvement
{ nullptr , nullptr , PREC_NONE } , // VCS_CONFLICT_MARKER,
{ nullptr , nullptr , PREC_NONE } , // BACKTICK,
{ nullptr , & GDScriptParser : : parse_invalid_token , PREC_CAST } , // QUESTION_MARK,
// Special
{ nullptr , nullptr , PREC_NONE } , // ERROR,
{ nullptr , nullptr , PREC_NONE } , // TK_EOF,
} ;
/* clang-format on */
// Avoid desync.
static_assert ( sizeof ( rules ) / sizeof ( rules [ 0 ] ) = = GDScriptTokenizer : : Token : : TK_MAX , " Amount of parse rules don't match the amount of token types. " ) ;
2021-03-12 14:35:16 +01:00
// Let's assume this is never invalid, since nothing generates a TK_MAX.
2020-05-02 00:14:56 +02:00
return & rules [ p_token_type ] ;
}
bool GDScriptParser : : SuiteNode : : has_local ( const StringName & p_name ) const {
if ( locals_indices . has ( p_name ) ) {
2020-03-09 00:24:12 +01:00
return true ;
}
2020-05-02 00:14:56 +02:00
if ( parent_block ! = nullptr ) {
return parent_block - > has_local ( p_name ) ;
2020-03-09 00:24:12 +01:00
}
return false ;
}
2020-05-02 00:14:56 +02:00
const GDScriptParser : : SuiteNode : : Local & GDScriptParser : : SuiteNode : : get_local ( const StringName & p_name ) const {
if ( locals_indices . has ( p_name ) ) {
return locals [ locals_indices [ p_name ] ] ;
2015-08-30 16:50:10 +02:00
}
2020-05-02 00:14:56 +02:00
if ( parent_block ! = nullptr ) {
return parent_block - > get_local ( p_name ) ;
2015-09-02 05:56:51 +02:00
}
2020-05-02 00:14:56 +02:00
return empty ;
}
2015-09-02 05:56:51 +02:00
2023-10-05 12:50:26 +02:00
bool GDScriptParser : : AnnotationNode : : apply ( GDScriptParser * p_this , Node * p_target , ClassNode * p_class ) {
2023-02-02 15:57:22 +01:00
if ( is_applied ) {
return true ;
}
is_applied = true ;
2023-10-05 12:50:26 +02:00
return ( p_this - > * ( p_this - > valid_annotations [ name ] . apply ) ) ( this , p_target , p_class ) ;
2015-08-30 16:50:10 +02:00
}
2020-05-02 00:14:56 +02:00
bool GDScriptParser : : AnnotationNode : : applies_to ( uint32_t p_target_kinds ) const {
return ( info - > target_kind & p_target_kinds ) > 0 ;
2018-05-30 04:16:54 +02:00
}
2023-01-31 15:43:54 +01:00
bool GDScriptParser : : validate_annotation_arguments ( AnnotationNode * p_annotation ) {
2020-05-02 00:14:56 +02:00
ERR_FAIL_COND_V_MSG ( ! valid_annotations . has ( p_annotation - > name ) , false , vformat ( R " (Annotation " % s " not found to validate.) " , p_annotation - > name ) ) ;
2020-01-10 23:43:33 +01:00
2020-05-02 00:14:56 +02:00
const MethodInfo & info = valid_annotations [ p_annotation - > name ] . info ;
2018-06-05 18:50:21 +02:00
2020-05-02 00:14:56 +02:00
if ( ( ( info . flags & METHOD_FLAG_VARARG ) = = 0 ) & & p_annotation - > arguments . size ( ) > info . arguments . size ( ) ) {
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 ( ) ) ) ;
return false ;
2018-05-30 04:16:54 +02:00
}
2020-05-02 00:14:56 +02:00
if ( p_annotation - > arguments . size ( ) < info . arguments . size ( ) - info . default_arguments . size ( ) ) {
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 ( ) ) ) ;
return false ;
2018-05-30 04:16:54 +02:00
}
2024-02-28 15:23:11 +01:00
// Some annotations need to be resolved in the parser.
2024-01-29 21:00:26 +01:00
if ( p_annotation - > name = = SNAME ( " @icon " ) ) {
2023-01-31 15:43:54 +01:00
ExpressionNode * argument = p_annotation - > arguments [ 0 ] ;
if ( argument - > type ! = Node : : LITERAL ) {
2024-01-29 21:00:26 +01:00
push_error ( R " (Argument 1 of annotation " @ icon " must be a string literal.) " , argument ) ;
2023-01-31 15:43:54 +01:00
return false ;
}
Variant value = static_cast < LiteralNode * > ( argument ) - > value ;
if ( value . get_type ( ) ! = Variant : : STRING ) {
2024-01-29 21:00:26 +01:00
push_error ( R " (Argument 1 of annotation " @ icon " must be a string literal.) " , argument ) ;
2023-01-31 15:43:54 +01:00
return false ;
}
p_annotation - > resolved_arguments . push_back ( value ) ;
}
// For other annotations, see `GDScriptAnalyzer::resolve_annotation()`.
2020-05-02 00:14:56 +02:00
return true ;
2018-05-30 04:16:54 +02:00
}
2023-10-05 12:50:26 +02:00
bool GDScriptParser : : tool_annotation ( const AnnotationNode * p_annotation , Node * p_target , ClassNode * p_class ) {
2023-04-14 19:45:49 +02:00
# ifdef DEBUG_ENABLED
2024-01-28 21:51:39 +01:00
if ( _is_tool ) {
2023-04-14 19:45:49 +02:00
push_error ( R " ( " @ tool " annotation can only be used once.) " , p_annotation ) ;
return false ;
}
# endif // DEBUG_ENABLED
2024-01-28 21:51:39 +01:00
_is_tool = true ;
2020-05-02 00:14:56 +02:00
return true ;
}
2018-07-25 17:12:46 +02:00
2023-10-05 12:50:26 +02:00
bool GDScriptParser : : icon_annotation ( const AnnotationNode * p_annotation , Node * p_target , ClassNode * p_class ) {
ERR_FAIL_COND_V_MSG ( p_target - > type ! = Node : : CLASS , false , R " ( " @ icon " annotation can only be applied to classes.) " ) ;
2023-02-02 06:37:39 +01:00
ERR_FAIL_COND_V ( p_annotation - > resolved_arguments . is_empty ( ) , false ) ;
2023-08-24 12:49:20 +02:00
2023-10-05 12:50:26 +02:00
ClassNode * class_node = static_cast < ClassNode * > ( p_target ) ;
2023-08-24 12:49:20 +02:00
String path = p_annotation - > resolved_arguments [ 0 ] ;
2023-04-14 19:45:49 +02:00
# ifdef DEBUG_ENABLED
2023-10-05 12:50:26 +02:00
if ( ! class_node - > icon_path . is_empty ( ) ) {
2023-04-14 19:45:49 +02:00
push_error ( R " ( " @ icon " annotation can only be used once.) " , p_annotation ) ;
return false ;
}
2023-08-24 12:49:20 +02:00
if ( path . is_empty ( ) ) {
2023-04-14 19:45:49 +02:00
push_error ( R " ( " @ icon " annotation argument must contain the path to the icon.) " , p_annotation - > arguments [ 0 ] ) ;
return false ;
}
# endif // DEBUG_ENABLED
2023-08-24 12:49:20 +02:00
2023-10-05 12:50:26 +02:00
class_node - > icon_path = path ;
2023-08-24 12:49:20 +02:00
if ( path . is_empty ( ) | | path . is_absolute_path ( ) ) {
2023-10-05 12:50:26 +02:00
class_node - > simplified_icon_path = path . simplify_path ( ) ;
2023-08-24 12:49:20 +02:00
} else if ( path . is_relative_path ( ) ) {
2023-10-05 12:50:26 +02:00
class_node - > simplified_icon_path = script_path . get_base_dir ( ) . path_join ( path ) . simplify_path ( ) ;
2023-08-24 12:49:20 +02:00
} else {
2023-10-05 12:50:26 +02:00
class_node - > simplified_icon_path = path ;
2023-08-24 12:49:20 +02:00
}
2020-05-02 00:14:56 +02:00
return true ;
}
2018-05-30 04:16:54 +02:00
2023-10-05 12:50:26 +02:00
bool GDScriptParser : : onready_annotation ( const AnnotationNode * p_annotation , Node * p_target , ClassNode * p_class ) {
ERR_FAIL_COND_V_MSG ( p_target - > type ! = Node : : VARIABLE , false , R " ( " @ onready " annotation can only be applied to class variables.) " ) ;
2018-05-30 04:16:54 +02:00
2023-02-06 18:52:13 +01:00
if ( current_class & & ! ClassDB : : is_parent_class ( current_class - > get_datatype ( ) . native_type , SNAME ( " Node " ) ) ) {
2023-02-06 14:45:38 +01:00
push_error ( R " ( " @ onready " can only be used in classes that inherit " Node " .) " , p_annotation ) ;
2024-04-13 01:13:25 +02:00
return false ;
2023-02-06 14:45:38 +01:00
}
2023-10-05 12:50:26 +02:00
VariableNode * variable = static_cast < VariableNode * > ( p_target ) ;
2023-05-16 12:03:53 +02:00
if ( variable - > is_static ) {
push_error ( R " ( " @ onready " annotation cannot be applied to a static variable.) " , p_annotation ) ;
return false ;
}
2020-05-02 00:14:56 +02:00
if ( variable - > onready ) {
2023-05-16 12:03:53 +02:00
push_error ( R " ( " @ onready " annotation can only be used once per variable.) " , p_annotation ) ;
2020-05-02 00:14:56 +02:00
return false ;
2018-05-30 04:16:54 +02:00
}
2020-05-02 00:14:56 +02:00
variable - > onready = true ;
current_class - > onready_used = true ;
return true ;
2018-05-30 04:16:54 +02:00
}
2023-10-07 12:32:08 +02:00
static String _get_annotation_error_string ( const StringName & p_annotation_name , const Vector < Variant : : Type > & p_expected_types , const GDScriptParser : : DataType & p_provided_type ) {
Vector < String > types ;
for ( int i = 0 ; i < p_expected_types . size ( ) ; i + + ) {
const Variant : : Type & type = p_expected_types [ i ] ;
types . push_back ( Variant : : get_type_name ( type ) ) ;
types . push_back ( " Array[ " + Variant : : get_type_name ( type ) + " ] " ) ;
switch ( type ) {
case Variant : : INT :
types . push_back ( " PackedByteArray " ) ;
types . push_back ( " PackedInt32Array " ) ;
types . push_back ( " PackedInt64Array " ) ;
break ;
case Variant : : FLOAT :
types . push_back ( " PackedFloat32Array " ) ;
types . push_back ( " PackedFloat64Array " ) ;
break ;
case Variant : : STRING :
types . push_back ( " PackedStringArray " ) ;
break ;
case Variant : : VECTOR2 :
types . push_back ( " PackedVector2Array " ) ;
break ;
case Variant : : VECTOR3 :
types . push_back ( " PackedVector3Array " ) ;
break ;
case Variant : : COLOR :
types . push_back ( " PackedColorArray " ) ;
break ;
2024-04-08 16:51:34 +02:00
case Variant : : VECTOR4 :
types . push_back ( " PackedVector4Array " ) ;
break ;
2023-10-07 12:32:08 +02:00
default :
break ;
}
}
String string ;
if ( types . size ( ) = = 1 ) {
string = types [ 0 ] . quote ( ) ;
} else if ( types . size ( ) = = 2 ) {
string = types [ 0 ] . quote ( ) + " or " + types [ 1 ] . quote ( ) ;
} else if ( types . size ( ) > = 3 ) {
string = types [ 0 ] . quote ( ) ;
for ( int i = 1 ; i < types . size ( ) - 1 ; i + + ) {
string + = " , " + types [ i ] . quote ( ) ;
}
string + = " , or " + types [ types . size ( ) - 1 ] . quote ( ) ;
}
return vformat ( R " ( " % s " annotation requires a variable of type %s, but type " % s " was given instead.) " , p_annotation_name, string, p_provided_type.to_string()) ;
}
2024-06-20 12:05:29 +02:00
static StringName _find_narrowest_native_or_global_class ( const GDScriptParser : : DataType & p_type ) {
switch ( p_type . kind ) {
case GDScriptParser : : DataType : : NATIVE : {
if ( p_type . is_meta_type ) {
return Object : : get_class_static ( ) ; // `GDScriptNativeClass` is not an exposed class.
}
return p_type . native_type ;
} break ;
case GDScriptParser : : DataType : : SCRIPT : {
Ref < Script > script ;
if ( p_type . script_type . is_valid ( ) ) {
script = p_type . script_type ;
} else {
script = ResourceLoader : : load ( p_type . script_path , SNAME ( " Script " ) ) ;
}
if ( p_type . is_meta_type ) {
return script . is_valid ( ) ? script - > get_class ( ) : Script : : get_class_static ( ) ;
}
if ( script . is_null ( ) ) {
return p_type . native_type ;
}
if ( script - > get_global_name ( ) ! = StringName ( ) ) {
return script - > get_global_name ( ) ;
}
Ref < Script > base_script = script - > get_base_script ( ) ;
if ( base_script . is_null ( ) ) {
return script - > get_instance_base_type ( ) ;
}
GDScriptParser : : DataType base_type ;
base_type . kind = GDScriptParser : : DataType : : SCRIPT ;
base_type . builtin_type = Variant : : OBJECT ;
base_type . native_type = base_script - > get_instance_base_type ( ) ;
base_type . script_type = base_script ;
base_type . script_path = base_script - > get_path ( ) ;
return _find_narrowest_native_or_global_class ( base_type ) ;
} break ;
case GDScriptParser : : DataType : : CLASS : {
if ( p_type . is_meta_type ) {
return GDScript : : get_class_static ( ) ;
}
if ( p_type . class_type = = nullptr ) {
return p_type . native_type ;
}
if ( p_type . class_type - > get_global_name ( ) ! = StringName ( ) ) {
return p_type . class_type - > get_global_name ( ) ;
}
return _find_narrowest_native_or_global_class ( p_type . class_type - > base_type ) ;
} break ;
default : {
ERR_FAIL_V ( StringName ( ) ) ;
} break ;
}
}
2020-05-02 00:14:56 +02:00
template < PropertyHint t_hint , Variant : : Type t_type >
2023-10-05 12:50:26 +02:00
bool GDScriptParser : : export_annotations ( const AnnotationNode * p_annotation , Node * p_target , ClassNode * p_class ) {
ERR_FAIL_COND_V_MSG ( p_target - > type ! = Node : : VARIABLE , false , vformat ( R " ( " % s " annotation can only be applied to variables.) " , p_annotation - > name ) ) ;
ERR_FAIL_NULL_V ( p_class , false ) ;
2018-05-30 04:16:54 +02:00
2023-10-05 12:50:26 +02:00
VariableNode * variable = static_cast < VariableNode * > ( p_target ) ;
2023-05-16 12:03:53 +02:00
if ( variable - > is_static ) {
push_error ( vformat ( R " (Annotation " % s " cannot be applied to a static variable.) " , p_annotation - > name ) , p_annotation ) ;
return false ;
}
2020-05-02 00:14:56 +02:00
if ( variable - > exported ) {
push_error ( vformat ( R " (Annotation " % s " cannot be used with another " @ export " annotation.) " , p_annotation - > name ) , p_annotation ) ;
return false ;
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
variable - > exported = true ;
2021-03-17 14:57:30 +01:00
2020-05-02 00:14:56 +02:00
variable - > export_info . type = t_type ;
variable - > export_info . hint = t_hint ;
2018-05-30 04:16:54 +02:00
2020-05-02 00:14:56 +02:00
String hint_string ;
for ( int i = 0 ; i < p_annotation - > resolved_arguments . size ( ) ; i + + ) {
2023-02-01 08:28:20 +01:00
String arg_string = String ( p_annotation - > resolved_arguments [ i ] ) ;
if ( p_annotation - > name ! = SNAME ( " @export_placeholder " ) ) {
if ( arg_string . is_empty ( ) ) {
push_error ( vformat ( R " (Argument %d of annotation " % s " is empty.) " , i + 1 , p_annotation - > name ) , p_annotation - > arguments [ i ] ) ;
return false ;
}
if ( arg_string . contains ( " , " ) ) {
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 ] ) ;
return false ;
}
2023-01-29 13:07:24 +01:00
}
2023-02-01 08:28:20 +01:00
2023-07-26 17:26:48 +02:00
// WARNING: Do not merge with the previous `if` because there `!=`, not `==`!
2023-02-01 08:28:20 +01:00
if ( p_annotation - > name = = SNAME ( " @export_flags " ) ) {
const int64_t max_flags = 32 ;
Vector < String > t = arg_string . split ( " : " , true , 1 ) ;
if ( t [ 0 ] . is_empty ( ) ) {
push_error ( vformat ( R " (Invalid argument %d of annotation " @ export_flags " : Expected flag name.) " , i + 1 ) , p_annotation - > arguments [ i ] ) ;
return false ;
}
if ( t . size ( ) = = 2 ) {
if ( t [ 1 ] . is_empty ( ) ) {
push_error ( vformat ( R " (Invalid argument %d of annotation " @ export_flags " : Expected flag value.) " , i + 1 ) , p_annotation - > arguments [ i ] ) ;
return false ;
}
if ( ! t [ 1 ] . is_valid_int ( ) ) {
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 ] ) ;
return false ;
}
int64_t value = t [ 1 ] . to_int ( ) ;
if ( value < 1 | | value > = ( 1LL < < max_flags ) ) {
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 ] ) ;
return false ;
}
} else if ( i > = max_flags ) {
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 ] ) ;
return false ;
}
2023-07-26 17:26:48 +02:00
} else if ( p_annotation - > name = = SNAME ( " @export_node_path " ) ) {
String native_class = arg_string ;
if ( ScriptServer : : is_global_class ( arg_string ) ) {
native_class = ScriptServer : : get_global_class_native_base ( arg_string ) ;
}
if ( ! ClassDB : : class_exists ( native_class ) ) {
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 ] ) ;
return false ;
} else if ( ! ClassDB : : is_parent_class ( native_class , SNAME ( " Node " ) ) ) {
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 ] ) ;
return false ;
}
2023-02-01 08:28:20 +01:00
}
2020-05-02 00:14:56 +02:00
if ( i > 0 ) {
hint_string + = " , " ;
}
2023-02-01 08:28:20 +01:00
hint_string + = arg_string ;
2020-05-02 00:14:56 +02:00
}
variable - > export_info . hint_string = hint_string ;
2018-05-30 04:16:54 +02:00
Fix various typos
Found via `codespell -q 3 -S ./thirdparty,*.po,./DONORS.md -L ackward,ang,ans,ba,beng,cas,childs,childrens,dof,doubleclick,expct,fave,findn,gird,hist,inh,inout,leapyear,lod,nd,numer,ois,ony,paket,ro,seeked,sinc,switchs,te,uint,varn,vew`
2022-01-02 19:47:52 +01:00
// This is called after the analyzer is done finding the type, so this should be set here.
2021-03-18 14:17:42 +01:00
DataType export_type = variable - > get_datatype ( ) ;
2023-10-07 12:32:08 +02:00
// Use initializer type if specified type is `Variant`.
if ( export_type . is_variant ( ) & & variable - > initializer ! = nullptr & & variable - > initializer - > datatype . is_set ( ) ) {
export_type = variable - > initializer - > get_datatype ( ) ;
export_type . type_source = DataType : : INFERRED ;
}
const Variant : : Type original_export_type_builtin = export_type . builtin_type ;
// Process array and packed array annotations on the element type.
bool is_array = false ;
if ( export_type . builtin_type = = Variant : : ARRAY & & export_type . has_container_element_type ( 0 ) ) {
is_array = true ;
export_type = export_type . get_container_element_type ( 0 ) ;
} else if ( export_type . is_typed_container_type ( ) ) {
is_array = true ;
export_type = export_type . get_typed_container_type ( ) ;
export_type . type_source = variable - > datatype . type_source ;
}
2023-12-19 18:56:30 +01:00
bool use_default_variable_type_check = true ;
2021-03-17 14:57:30 +01:00
2022-11-24 21:24:46 +01:00
if ( p_annotation - > name = = SNAME ( " @export_range " ) ) {
if ( export_type . builtin_type = = Variant : : INT ) {
variable - > export_info . type = Variant : : INT ;
}
2023-07-26 17:26:48 +02:00
} else if ( p_annotation - > name = = SNAME ( " @export_multiline " ) ) {
2023-10-07 12:32:08 +02:00
use_default_variable_type_check = false ;
2022-12-24 23:46:57 +01:00
2023-10-07 12:32:08 +02:00
if ( export_type . builtin_type ! = Variant : : STRING & & export_type . builtin_type ! = Variant : : DICTIONARY ) {
Vector < Variant : : Type > expected_types = { Variant : : STRING , Variant : : DICTIONARY } ;
push_error ( _get_annotation_error_string ( p_annotation - > name , expected_types , variable - > get_datatype ( ) ) , p_annotation ) ;
return false ;
}
2022-12-24 23:46:57 +01:00
2023-10-07 12:32:08 +02:00
if ( export_type . builtin_type = = Variant : : DICTIONARY ) {
2022-12-24 23:46:57 +01:00
variable - > export_info . type = Variant : : DICTIONARY ;
}
2023-12-19 18:56:30 +01:00
} else if ( p_annotation - > name = = SNAME ( " @export " ) ) {
use_default_variable_type_check = false ;
2022-11-24 21:24:46 +01:00
2021-03-17 14:57:30 +01:00
if ( variable - > datatype_specifier = = nullptr & & variable - > initializer = = nullptr ) {
push_error ( R " (Cannot use simple " @ export " annotation with variable without type or initializer, since type can't be inferred.) " , p_annotation ) ;
return false ;
}
if ( export_type . is_variant ( ) | | export_type . has_no_type ( ) ) {
push_error ( R " (Cannot use simple " @ export " annotation because the type of the initialized value can't be inferred.) " , p_annotation ) ;
return false ;
}
switch ( export_type . kind ) {
case GDScriptParser : : DataType : : BUILTIN :
variable - > export_info . type = export_type . builtin_type ;
variable - > export_info . hint = PROPERTY_HINT_NONE ;
2024-04-15 21:10:47 +02:00
variable - > export_info . hint_string = String ( ) ;
2021-03-17 14:57:30 +01:00
break ;
case GDScriptParser : : DataType : : NATIVE :
2024-06-20 12:05:29 +02:00
case GDScriptParser : : DataType : : SCRIPT :
2024-05-04 19:41:01 +02:00
case GDScriptParser : : DataType : : CLASS : {
2024-06-20 12:05:29 +02:00
const StringName class_name = _find_narrowest_native_or_global_class ( export_type ) ;
2024-05-04 19:41:01 +02:00
if ( ClassDB : : is_parent_class ( export_type . native_type , SNAME ( " Resource " ) ) ) {
2022-04-25 06:33:18 +02:00
variable - > export_info . type = Variant : : OBJECT ;
variable - > export_info . hint = PROPERTY_HINT_RESOURCE_TYPE ;
variable - > export_info . hint_string = class_name ;
2024-05-04 19:41:01 +02:00
} else if ( ClassDB : : is_parent_class ( export_type . native_type , SNAME ( " Node " ) ) ) {
variable - > export_info . type = Variant : : OBJECT ;
variable - > export_info . hint = PROPERTY_HINT_NODE_TYPE ;
variable - > export_info . hint_string = class_name ;
} else {
push_error ( R " (Export type can only be built-in, a resource, a node, or an enum.) " , p_annotation ) ;
return false ;
2022-04-25 06:33:18 +02:00
}
} break ;
2021-03-17 18:58:05 +01:00
case GDScriptParser : : DataType : : ENUM : {
2023-09-19 19:35:45 +02:00
if ( export_type . is_meta_type ) {
variable - > export_info . type = Variant : : DICTIONARY ;
} else {
variable - > export_info . type = Variant : : INT ;
variable - > export_info . hint = PROPERTY_HINT_ENUM ;
String enum_hint_string ;
bool first = true ;
for ( const KeyValue < StringName , int64_t > & E : export_type . enum_values ) {
if ( ! first ) {
enum_hint_string + = " , " ;
} else {
first = false ;
}
enum_hint_string + = E . key . operator String ( ) . capitalize ( ) . xml_escape ( ) ;
enum_hint_string + = " : " ;
enum_hint_string + = String : : num_int64 ( E . value ) . xml_escape ( ) ;
2021-03-17 18:58:05 +01:00
}
2023-09-19 19:35:45 +02:00
variable - > export_info . hint_string = enum_hint_string ;
variable - > export_info . usage | = PROPERTY_USAGE_CLASS_IS_ENUM ;
variable - > export_info . class_name = String ( export_type . native_type ) . replace ( " :: " , " . " ) ;
}
2021-03-17 18:58:05 +01:00
} break ;
2021-03-17 14:57:30 +01:00
default :
2023-10-07 12:32:08 +02:00
push_error ( R " (Export type can only be built-in, a resource, a node, or an enum.) " , p_annotation ) ;
2023-10-05 12:50:26 +02:00
return false ;
}
if ( variable - > export_info . hint = = PROPERTY_HINT_NODE_TYPE & & ! ClassDB : : is_parent_class ( p_class - > base_type . native_type , SNAME ( " Node " ) ) ) {
2023-10-07 12:32:08 +02:00
push_error ( vformat ( R " (Node export is only supported in Node-derived classes, but the current class inherits " % s " .) " , p_class - > base_type . to_string ( ) ) , p_annotation ) ;
2023-10-05 12:50:26 +02:00
return false ;
2021-03-17 14:57:30 +01:00
}
2023-01-29 13:07:24 +01:00
} else if ( p_annotation - > name = = SNAME ( " @export_enum " ) ) {
2023-12-19 18:56:30 +01:00
use_default_variable_type_check = false ;
2023-01-29 13:07:24 +01:00
Variant : : Type enum_type = Variant : : INT ;
if ( export_type . kind = = DataType : : BUILTIN & & export_type . builtin_type = = Variant : : STRING ) {
enum_type = Variant : : STRING ;
}
variable - > export_info . type = enum_type ;
if ( ! export_type . is_variant ( ) & & ( export_type . kind ! = DataType : : BUILTIN | | export_type . builtin_type ! = enum_type ) ) {
2023-10-07 12:32:08 +02:00
Vector < Variant : : Type > expected_types = { Variant : : INT , Variant : : STRING } ;
push_error ( _get_annotation_error_string ( p_annotation - > name , expected_types , variable - > get_datatype ( ) ) , p_annotation ) ;
2023-01-29 13:07:24 +01:00
return false ;
}
2023-12-19 18:56:30 +01:00
}
if ( use_default_variable_type_check ) {
2021-03-17 14:57:30 +01:00
// Validate variable type with export.
if ( ! export_type . is_variant ( ) & & ( export_type . kind ! = DataType : : BUILTIN | | export_type . builtin_type ! = t_type ) ) {
// Allow float/int conversion.
if ( ( t_type ! = Variant : : FLOAT | | export_type . builtin_type ! = Variant : : INT ) & & ( t_type ! = Variant : : INT | | export_type . builtin_type ! = Variant : : FLOAT ) ) {
2023-10-07 12:32:08 +02:00
Vector < Variant : : Type > expected_types = { t_type } ;
push_error ( _get_annotation_error_string ( p_annotation - > name , expected_types , variable - > get_datatype ( ) ) , p_annotation ) ;
2021-03-17 14:57:30 +01:00
return false ;
}
}
}
2023-10-07 12:32:08 +02:00
if ( is_array ) {
String hint_prefix = itos ( variable - > export_info . type ) ;
if ( variable - > export_info . hint ) {
hint_prefix + = " / " + itos ( variable - > export_info . hint ) ;
}
2024-04-15 21:10:47 +02:00
variable - > export_info . type = original_export_type_builtin ;
2023-10-07 12:32:08 +02:00
variable - > export_info . hint = PROPERTY_HINT_TYPE_STRING ;
variable - > export_info . hint_string = hint_prefix + " : " + variable - > export_info . hint_string ;
2024-04-15 21:10:47 +02:00
variable - > export_info . usage = PROPERTY_USAGE_DEFAULT ;
variable - > export_info . class_name = StringName ( ) ;
}
return true ;
}
// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values,
// or handle array exports in a special way, so they are implemented as separate methods.
bool GDScriptParser : : export_storage_annotation ( const AnnotationNode * p_annotation , Node * p_node , ClassNode * p_class ) {
ERR_FAIL_COND_V_MSG ( p_node - > type ! = Node : : VARIABLE , false , vformat ( R " ( " % s " annotation can only be applied to variables.) " , p_annotation - > name ) ) ;
VariableNode * variable = static_cast < VariableNode * > ( p_node ) ;
if ( variable - > is_static ) {
push_error ( vformat ( R " (Annotation " % s " cannot be applied to a static variable.) " , p_annotation - > name ) , p_annotation ) ;
return false ;
}
if ( variable - > exported ) {
push_error ( vformat ( R " (Annotation " % s " cannot be used with another " @ export " annotation.) " , p_annotation - > name ) , p_annotation ) ;
return false ;
2023-10-07 12:32:08 +02:00
}
2024-04-15 21:10:47 +02:00
variable - > exported = true ;
// Save the info because the compiler uses export info for overwriting member info.
variable - > export_info = variable - > get_datatype ( ) . to_property_info ( variable - > identifier - > name ) ;
variable - > export_info . usage | = PROPERTY_USAGE_STORAGE ;
2020-05-02 00:14:56 +02:00
return true ;
}
2018-05-30 04:16:54 +02:00
2023-02-08 20:15:48 +01:00
bool GDScriptParser : : export_custom_annotation ( const AnnotationNode * p_annotation , Node * p_node , ClassNode * p_class ) {
ERR_FAIL_COND_V_MSG ( p_node - > type ! = Node : : VARIABLE , false , vformat ( R " ( " % s " annotation can only be applied to variables.) " , p_annotation - > name ) ) ;
ERR_FAIL_COND_V_MSG ( p_annotation - > resolved_arguments . size ( ) < 2 , false , R " (Annotation " @ export_custom " requires 2 arguments.) " ) ;
VariableNode * variable = static_cast < VariableNode * > ( p_node ) ;
2024-04-15 21:10:47 +02:00
if ( variable - > is_static ) {
push_error ( vformat ( R " (Annotation " % s " cannot be applied to a static variable.) " , p_annotation - > name ) , p_annotation ) ;
return false ;
}
2023-02-08 20:15:48 +01:00
if ( variable - > exported ) {
push_error ( vformat ( R " (Annotation " % s " cannot be used with another " @ export " annotation.) " , p_annotation - > name ) , p_annotation ) ;
return false ;
}
variable - > exported = true ;
DataType export_type = variable - > get_datatype ( ) ;
variable - > export_info . type = export_type . builtin_type ;
variable - > export_info . hint = static_cast < PropertyHint > ( p_annotation - > resolved_arguments [ 0 ] . operator int64_t ( ) ) ;
variable - > export_info . hint_string = p_annotation - > resolved_arguments [ 1 ] ;
if ( p_annotation - > resolved_arguments . size ( ) > = 3 ) {
variable - > export_info . usage = p_annotation - > resolved_arguments [ 2 ] . operator int64_t ( ) ;
}
return true ;
}
2022-07-03 21:30:08 +02:00
template < PropertyUsageFlags t_usage >
2023-10-05 12:50:26 +02:00
bool GDScriptParser : : export_group_annotations ( const AnnotationNode * p_annotation , Node * p_target , ClassNode * p_class ) {
2022-07-03 21:30:08 +02:00
AnnotationNode * annotation = const_cast < AnnotationNode * > ( p_annotation ) ;
2023-02-02 06:37:39 +01:00
if ( annotation - > resolved_arguments . is_empty ( ) ) {
return false ;
}
2022-07-03 21:30:08 +02:00
annotation - > export_info . name = annotation - > resolved_arguments [ 0 ] ;
switch ( t_usage ) {
case PROPERTY_USAGE_CATEGORY : {
annotation - > export_info . usage = t_usage ;
} break ;
case PROPERTY_USAGE_GROUP : {
annotation - > export_info . usage = t_usage ;
if ( annotation - > resolved_arguments . size ( ) = = 2 ) {
annotation - > export_info . hint_string = annotation - > resolved_arguments [ 1 ] ;
}
} break ;
case PROPERTY_USAGE_SUBGROUP : {
annotation - > export_info . usage = t_usage ;
if ( annotation - > resolved_arguments . size ( ) = = 2 ) {
annotation - > export_info . hint_string = annotation - > resolved_arguments [ 1 ] ;
}
} break ;
}
return true ;
}
2023-10-05 12:50:26 +02:00
bool GDScriptParser : : warning_annotations ( const AnnotationNode * p_annotation , Node * p_target , ClassNode * p_class ) {
2024-02-28 15:23:11 +01:00
# ifndef DEBUG_ENABLED
// Only available in debug builds.
return true ;
# else // DEBUG_ENABLED
if ( is_ignoring_warnings ) {
return true ; // We already ignore all warnings, let's optimize it.
}
2022-01-04 13:32:43 +01:00
bool has_error = false ;
for ( const Variant & warning_name : p_annotation - > resolved_arguments ) {
2024-02-28 15:23:11 +01:00
GDScriptWarning : : Code warning_code = GDScriptWarning : : get_code_from_name ( String ( warning_name ) . to_upper ( ) ) ;
if ( warning_code = = GDScriptWarning : : WARNING_MAX ) {
2022-01-04 13:32:43 +01:00
push_error ( vformat ( R " (Invalid warning name: " % s " .) " , warning_name ) , p_annotation ) ;
has_error = true ;
} else {
2024-02-28 15:23:11 +01:00
int start_line = p_annotation - > start_line ;
int end_line = p_target - > end_line ;
switch ( p_target - > type ) {
# define SIMPLE_CASE(m_type, m_class, m_property) \
case m_type : { \
m_class * node = static_cast < m_class * > ( p_target ) ; \
if ( node - > m_property = = nullptr ) { \
end_line = node - > start_line ; \
} else { \
end_line = node - > m_property - > end_line ; \
} \
} break ;
// Can contain properties (set/get).
SIMPLE_CASE ( Node : : VARIABLE , VariableNode , initializer )
// Contain bodies.
SIMPLE_CASE ( Node : : FOR , ForNode , list )
SIMPLE_CASE ( Node : : IF , IfNode , condition )
SIMPLE_CASE ( Node : : MATCH , MatchNode , test )
SIMPLE_CASE ( Node : : WHILE , WhileNode , condition )
# undef SIMPLE_CASE
case Node : : CLASS : {
end_line = p_target - > start_line ;
for ( const AnnotationNode * annotation : p_target - > annotations ) {
start_line = MIN ( start_line , annotation - > start_line ) ;
end_line = MAX ( end_line , annotation - > end_line ) ;
}
} break ;
case Node : : FUNCTION : {
// `@warning_ignore` on function has a controversial feature that is used in tests.
// It's better not to remove it for now, while there is no way to mass-ignore warnings.
} break ;
case Node : : MATCH_BRANCH : {
MatchBranchNode * branch = static_cast < MatchBranchNode * > ( p_target ) ;
end_line = branch - > start_line ;
for ( int i = 0 ; i < branch - > patterns . size ( ) ; i + + ) {
end_line = MAX ( end_line , branch - > patterns [ i ] - > end_line ) ;
}
} break ;
default : {
} break ;
}
end_line = MAX ( start_line , end_line ) ; // Prevent infinite loop.
for ( int line = start_line ; line < = end_line ; line + + ) {
warning_ignored_lines [ warning_code ] . insert ( line ) ;
}
2022-01-04 13:32:43 +01:00
}
}
return ! has_error ;
# endif // DEBUG_ENABLED
2020-05-02 00:14:56 +02:00
}
2023-10-05 12:50:26 +02:00
bool GDScriptParser : : rpc_annotation ( const AnnotationNode * p_annotation , Node * p_target , ClassNode * p_class ) {
ERR_FAIL_COND_V_MSG ( p_target - > type ! = Node : : FUNCTION , false , vformat ( R " ( " % s " annotation can only be applied to functions.) " , p_annotation - > name ) ) ;
2020-05-02 00:14:56 +02:00
2023-10-05 12:50:26 +02:00
FunctionNode * function = static_cast < FunctionNode * > ( p_target ) ;
2022-07-12 23:12:42 +02:00
if ( function - > rpc_config . get_type ( ) ! = Variant : : NIL ) {
push_error ( R " (RPC annotations can only be used once per function.) " , p_annotation ) ;
return false ;
}
Dictionary rpc_config ;
rpc_config [ " rpc_mode " ] = MultiplayerAPI : : RPC_MODE_AUTHORITY ;
2023-02-02 06:37:39 +01:00
if ( ! p_annotation - > resolved_arguments . is_empty ( ) ) {
2023-01-28 22:21:27 +01:00
unsigned char locality_args = 0 ;
unsigned char permission_args = 0 ;
unsigned char transfer_mode_args = 0 ;
2023-06-13 20:33:48 +02:00
for ( int i = 0 ; i < p_annotation - > resolved_arguments . size ( ) ; i + + ) {
if ( i = = 3 ) {
rpc_config [ " channel " ] = p_annotation - > resolved_arguments [ i ] . operator int ( ) ;
continue ;
}
2023-01-28 22:21:27 +01:00
String arg = p_annotation - > resolved_arguments [ i ] . operator String ( ) ;
if ( arg = = " call_local " ) {
locality_args + + ;
2022-07-12 23:12:42 +02:00
rpc_config [ " call_local " ] = true ;
2023-01-28 22:21:27 +01:00
} else if ( arg = = " call_remote " ) {
locality_args + + ;
2022-07-12 23:12:42 +02:00
rpc_config [ " call_local " ] = false ;
2023-01-28 22:21:27 +01:00
} else if ( arg = = " any_peer " ) {
permission_args + + ;
rpc_config [ " rpc_mode " ] = MultiplayerAPI : : RPC_MODE_ANY_PEER ;
} else if ( arg = = " authority " ) {
permission_args + + ;
rpc_config [ " rpc_mode " ] = MultiplayerAPI : : RPC_MODE_AUTHORITY ;
} else if ( arg = = " reliable " ) {
transfer_mode_args + + ;
2022-07-12 23:12:42 +02:00
rpc_config [ " transfer_mode " ] = MultiplayerPeer : : TRANSFER_MODE_RELIABLE ;
2023-01-28 22:21:27 +01:00
} else if ( arg = = " unreliable " ) {
transfer_mode_args + + ;
2022-07-12 23:12:42 +02:00
rpc_config [ " transfer_mode " ] = MultiplayerPeer : : TRANSFER_MODE_UNRELIABLE ;
2023-01-28 22:21:27 +01:00
} else if ( arg = = " unreliable_ordered " ) {
transfer_mode_args + + ;
2022-07-12 23:12:42 +02:00
rpc_config [ " transfer_mode " ] = MultiplayerPeer : : TRANSFER_MODE_UNRELIABLE_ORDERED ;
2021-06-24 10:28:15 +02:00
} else {
2023-01-28 22:21:27 +01:00
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 ) ;
2021-06-24 10:28:15 +02:00
}
2020-05-02 00:14:56 +02:00
}
2023-01-28 22:21:27 +01:00
if ( locality_args > 1 ) {
push_error ( R " (Invalid RPC config. The locality ( " call_local " / " call_remote " ) must be specified no more than once.) " , p_annotation ) ;
} else if ( permission_args > 1 ) {
push_error ( R " (Invalid RPC config. The permission ( " any_peer " / " authority " ) must be specified no more than once.) " , p_annotation ) ;
} else if ( transfer_mode_args > 1 ) {
push_error ( R " (Invalid RPC config. The transfer mode ( " reliable " / " unreliable " / " unreliable_ordered " ) must be specified no more than once.) " , p_annotation ) ;
}
2021-06-24 10:28:15 +02:00
}
2022-07-12 23:12:42 +02:00
function - > rpc_config = rpc_config ;
2020-05-02 00:14:56 +02:00
return true ;
}
2023-10-05 12:50:26 +02:00
bool GDScriptParser : : static_unload_annotation ( const AnnotationNode * p_annotation , Node * p_target , ClassNode * p_class ) {
2023-04-19 16:10:35 +02:00
ERR_FAIL_COND_V_MSG ( p_target - > type ! = Node : : CLASS , false , vformat ( R " ( " % s " annotation can only be applied to classes.) " , p_annotation - > name ) ) ;
2023-10-05 12:50:26 +02:00
ClassNode * class_node = static_cast < ClassNode * > ( p_target ) ;
if ( class_node - > annotated_static_unload ) {
2023-04-19 16:10:35 +02:00
push_error ( vformat ( R " ( " % s " annotation can only be used once per script.) " , p_annotation - > name ) , p_annotation ) ;
return false ;
}
2023-10-05 12:50:26 +02:00
class_node - > annotated_static_unload = true ;
2023-04-19 16:10:35 +02:00
return true ;
}
2020-06-10 23:18:10 +02:00
GDScriptParser : : DataType GDScriptParser : : SuiteNode : : Local : : get_datatype ( ) const {
switch ( type ) {
case CONSTANT :
return constant - > get_datatype ( ) ;
case VARIABLE :
return variable - > get_datatype ( ) ;
case PARAMETER :
return parameter - > get_datatype ( ) ;
case FOR_VARIABLE :
case PATTERN_BIND :
return bind - > get_datatype ( ) ;
case UNDEFINED :
return DataType ( ) ;
}
return DataType ( ) ;
}
String GDScriptParser : : SuiteNode : : Local : : get_name ( ) const {
switch ( type ) {
case SuiteNode : : Local : : PARAMETER :
2022-09-29 11:53:28 +02:00
return " parameter " ;
2020-06-10 23:18:10 +02:00
case SuiteNode : : Local : : CONSTANT :
2022-09-29 11:53:28 +02:00
return " constant " ;
2020-06-10 23:18:10 +02:00
case SuiteNode : : Local : : VARIABLE :
2022-09-29 11:53:28 +02:00
return " variable " ;
2020-06-10 23:18:10 +02:00
case SuiteNode : : Local : : FOR_VARIABLE :
2022-09-29 11:53:28 +02:00
return " for loop iterator " ;
2020-06-10 23:18:10 +02:00
case SuiteNode : : Local : : PATTERN_BIND :
2023-08-24 18:01:31 +02:00
return " pattern bind " ;
2020-06-10 23:18:10 +02:00
case SuiteNode : : Local : : UNDEFINED :
2022-09-29 11:53:28 +02:00
return " <undefined> " ;
default :
return String ( ) ;
2020-06-10 23:18:10 +02:00
}
}
String GDScriptParser : : DataType : : to_string ( ) const {
switch ( kind ) {
case VARIANT :
return " Variant " ;
case BUILTIN :
if ( builtin_type = = Variant : : NIL ) {
return " null " ;
}
2023-09-14 20:31:07 +02:00
if ( builtin_type = = Variant : : ARRAY & & has_container_element_type ( 0 ) ) {
return vformat ( " Array[%s] " , container_element_types [ 0 ] . to_string ( ) ) ;
2021-03-09 16:32:35 +01:00
}
2020-06-10 23:18:10 +02:00
return Variant : : get_type_name ( builtin_type ) ;
case NATIVE :
if ( is_meta_type ) {
return GDScriptNativeClass : : get_class_static ( ) ;
}
return native_type . operator String ( ) ;
case CLASS :
if ( class_type - > identifier ! = nullptr ) {
return class_type - > identifier - > name . operator String ( ) ;
}
2020-06-12 02:49:58 +02:00
return class_type - > fqcn ;
2020-06-10 23:18:10 +02:00
case SCRIPT : {
if ( is_meta_type ) {
2023-04-11 18:36:28 +02:00
return script_type ! = nullptr ? script_type - > get_class_name ( ) . operator String ( ) : " " ;
2020-06-10 23:18:10 +02:00
}
2022-09-23 17:13:57 +02:00
String name = script_type ! = nullptr ? script_type - > get_name ( ) : " " ;
2020-12-15 13:04:21 +01:00
if ( ! name . is_empty ( ) ) {
2020-06-10 23:18:10 +02:00
return name ;
}
2020-07-16 03:02:44 +02:00
name = script_path ;
2020-12-15 13:04:21 +01:00
if ( ! name . is_empty ( ) ) {
2020-06-10 23:18:10 +02:00
return name ;
}
return native_type . operator String ( ) ;
}
2022-12-04 04:02:03 +01:00
case ENUM : {
// native_type contains either the native class defining the enum
// or the fully qualified class name of the script defining the enum
return String ( native_type ) . get_file ( ) ; // Remove path, keep filename
}
2022-12-11 03:57:35 +01:00
case RESOLVING :
2020-06-10 23:18:10 +02:00
case UNRESOLVED :
return " <unresolved type> " ;
}
2022-12-11 03:57:35 +01:00
ERR_FAIL_V_MSG ( " <unresolved type> " , " Kind set outside the enum range. " ) ;
2020-06-10 23:18:10 +02:00
}
2023-08-28 18:20:10 +02:00
PropertyInfo GDScriptParser : : DataType : : to_property_info ( const String & p_name ) const {
PropertyInfo result ;
result . name = p_name ;
result . usage = PROPERTY_USAGE_NONE ;
if ( ! is_hard_type ( ) ) {
result . usage | = PROPERTY_USAGE_NIL_IS_VARIANT ;
return result ;
}
switch ( kind ) {
case BUILTIN :
result . type = builtin_type ;
2023-09-14 20:31:07 +02:00
if ( builtin_type = = Variant : : ARRAY & & has_container_element_type ( 0 ) ) {
const DataType elem_type = get_container_element_type ( 0 ) ;
switch ( elem_type . kind ) {
2023-08-28 18:20:10 +02:00
case BUILTIN :
result . hint = PROPERTY_HINT_ARRAY_TYPE ;
2023-09-14 20:31:07 +02:00
result . hint_string = Variant : : get_type_name ( elem_type . builtin_type ) ;
2023-08-28 18:20:10 +02:00
break ;
case NATIVE :
result . hint = PROPERTY_HINT_ARRAY_TYPE ;
2023-09-14 20:31:07 +02:00
result . hint_string = elem_type . native_type ;
2023-08-28 18:20:10 +02:00
break ;
case SCRIPT :
result . hint = PROPERTY_HINT_ARRAY_TYPE ;
2023-09-14 20:31:07 +02:00
if ( elem_type . script_type . is_valid ( ) & & elem_type . script_type - > get_global_name ( ) ! = StringName ( ) ) {
result . hint_string = elem_type . script_type - > get_global_name ( ) ;
2023-08-28 18:20:10 +02:00
} else {
2023-09-14 20:31:07 +02:00
result . hint_string = elem_type . native_type ;
2023-08-28 18:20:10 +02:00
}
break ;
case CLASS :
result . hint = PROPERTY_HINT_ARRAY_TYPE ;
2023-09-14 20:31:07 +02:00
if ( elem_type . class_type ! = nullptr & & elem_type . class_type - > get_global_name ( ) ! = StringName ( ) ) {
result . hint_string = elem_type . class_type - > get_global_name ( ) ;
2023-08-28 18:20:10 +02:00
} else {
2023-09-14 20:31:07 +02:00
result . hint_string = elem_type . native_type ;
2023-08-28 18:20:10 +02:00
}
break ;
case ENUM :
result . hint = PROPERTY_HINT_ARRAY_TYPE ;
2023-09-14 20:31:07 +02:00
result . hint_string = String ( elem_type . native_type ) . replace ( " :: " , " . " ) ;
2023-08-28 18:20:10 +02:00
break ;
case VARIANT :
case RESOLVING :
case UNRESOLVED :
break ;
}
}
break ;
case NATIVE :
result . type = Variant : : OBJECT ;
if ( is_meta_type ) {
result . class_name = GDScriptNativeClass : : get_class_static ( ) ;
} else {
result . class_name = native_type ;
}
break ;
case SCRIPT :
result . type = Variant : : OBJECT ;
if ( is_meta_type ) {
result . class_name = script_type . is_valid ( ) ? script_type - > get_class ( ) : Script : : get_class_static ( ) ;
} else if ( script_type . is_valid ( ) & & script_type - > get_global_name ( ) ! = StringName ( ) ) {
result . class_name = script_type - > get_global_name ( ) ;
} else {
result . class_name = native_type ;
}
break ;
case CLASS :
result . type = Variant : : OBJECT ;
if ( is_meta_type ) {
result . class_name = GDScript : : get_class_static ( ) ;
} else if ( class_type ! = nullptr & & class_type - > get_global_name ( ) ! = StringName ( ) ) {
result . class_name = class_type - > get_global_name ( ) ;
} else {
result . class_name = native_type ;
}
break ;
case ENUM :
if ( is_meta_type ) {
result . type = Variant : : DICTIONARY ;
} else {
result . type = Variant : : INT ;
result . usage | = PROPERTY_USAGE_CLASS_IS_ENUM ;
result . class_name = String ( native_type ) . replace ( " :: " , " . " ) ;
}
break ;
case VARIANT :
case RESOLVING :
case UNRESOLVED :
result . usage | = PROPERTY_USAGE_NIL_IS_VARIANT ;
break ;
}
return result ;
}
2021-08-14 05:44:22 +02:00
static Variant : : Type _variant_type_to_typed_array_element_type ( Variant : : Type p_type ) {
switch ( p_type ) {
case Variant : : PACKED_BYTE_ARRAY :
case Variant : : PACKED_INT32_ARRAY :
case Variant : : PACKED_INT64_ARRAY :
return Variant : : INT ;
case Variant : : PACKED_FLOAT32_ARRAY :
case Variant : : PACKED_FLOAT64_ARRAY :
return Variant : : FLOAT ;
case Variant : : PACKED_STRING_ARRAY :
return Variant : : STRING ;
case Variant : : PACKED_VECTOR2_ARRAY :
return Variant : : VECTOR2 ;
case Variant : : PACKED_VECTOR3_ARRAY :
return Variant : : VECTOR3 ;
case Variant : : PACKED_COLOR_ARRAY :
return Variant : : COLOR ;
2024-04-08 16:51:34 +02:00
case Variant : : PACKED_VECTOR4_ARRAY :
return Variant : : VECTOR4 ;
2021-08-14 05:44:22 +02:00
default :
return Variant : : NIL ;
}
}
bool GDScriptParser : : DataType : : is_typed_container_type ( ) const {
return kind = = GDScriptParser : : DataType : : BUILTIN & & _variant_type_to_typed_array_element_type ( builtin_type ) ! = Variant : : NIL ;
}
GDScriptParser : : DataType GDScriptParser : : DataType : : get_typed_container_type ( ) const {
GDScriptParser : : DataType type ;
type . kind = GDScriptParser : : DataType : : BUILTIN ;
type . builtin_type = _variant_type_to_typed_array_element_type ( builtin_type ) ;
return type ;
}
2022-07-11 20:31:15 +02:00
void GDScriptParser : : complete_extents ( Node * p_node ) {
while ( ! nodes_in_progress . is_empty ( ) & & nodes_in_progress . back ( ) - > get ( ) ! = p_node ) {
ERR_PRINT ( " Parser bug: Mismatch in extents tracking stack. " ) ;
nodes_in_progress . pop_back ( ) ;
}
if ( nodes_in_progress . is_empty ( ) ) {
ERR_PRINT ( " Parser bug: Extents tracking stack is empty. " ) ;
} else {
nodes_in_progress . pop_back ( ) ;
}
}
void GDScriptParser : : update_extents ( Node * p_node ) {
p_node - > end_line = previous . end_line ;
p_node - > end_column = previous . end_column ;
p_node - > leftmost_column = MIN ( p_node - > leftmost_column , previous . leftmost_column ) ;
p_node - > rightmost_column = MAX ( p_node - > rightmost_column , previous . rightmost_column ) ;
}
void GDScriptParser : : reset_extents ( Node * p_node , GDScriptTokenizer : : Token p_token ) {
p_node - > start_line = p_token . start_line ;
p_node - > end_line = p_token . end_line ;
p_node - > start_column = p_token . start_column ;
p_node - > end_column = p_token . end_column ;
p_node - > leftmost_column = p_token . leftmost_column ;
p_node - > rightmost_column = p_token . rightmost_column ;
}
void GDScriptParser : : reset_extents ( Node * p_node , Node * p_from ) {
if ( p_from = = nullptr ) {
return ;
}
p_node - > start_line = p_from - > start_line ;
p_node - > end_line = p_from - > end_line ;
p_node - > start_column = p_from - > start_column ;
p_node - > end_column = p_from - > end_column ;
p_node - > leftmost_column = p_from - > leftmost_column ;
p_node - > rightmost_column = p_from - > rightmost_column ;
}
2020-05-02 00:14:56 +02:00
/*---------- PRETTY PRINT FOR DEBUG ----------*/
2018-07-01 18:17:40 +02:00
# ifdef DEBUG_ENABLED
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : increase_indent ( ) {
indent_level + + ;
indent = " " ;
for ( int i = 0 ; i < indent_level * 4 ; i + + ) {
if ( i % 4 = = 0 ) {
indent + = " | " ;
} else {
indent + = " " ;
2018-07-01 18:17:40 +02:00
}
}
2018-05-30 04:16:54 +02:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : decrease_indent ( ) {
indent_level - - ;
indent = " " ;
for ( int i = 0 ; i < indent_level * 4 ; i + + ) {
if ( i % 4 = = 0 ) {
indent + = " | " ;
} else {
indent + = " " ;
}
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : push_line ( const String & p_line ) {
2020-12-15 13:04:21 +01:00
if ( ! p_line . is_empty ( ) ) {
2020-05-02 00:14:56 +02:00
push_text ( p_line ) ;
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
printed + = " \n " ;
pending_indent = true ;
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : push_text ( const String & p_text ) {
if ( pending_indent ) {
printed + = indent ;
pending_indent = false ;
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
printed + = p_text ;
}
2021-07-24 15:46:25 +02:00
void GDScriptParser : : TreePrinter : : print_annotation ( const AnnotationNode * p_annotation ) {
2020-05-02 00:14:56 +02:00
push_text ( p_annotation - > name ) ;
push_text ( " ( " ) ;
for ( int i = 0 ; i < p_annotation - > arguments . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " , " ) ;
}
print_expression ( p_annotation - > arguments [ i ] ) ;
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
push_line ( " ) " ) ;
}
void GDScriptParser : : TreePrinter : : print_array ( ArrayNode * p_array ) {
push_text ( " [ " ) ;
for ( int i = 0 ; i < p_array - > elements . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " , " ) ;
}
print_expression ( p_array - > elements [ i ] ) ;
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
push_text ( " ] " ) ;
}
2018-07-01 18:17:40 +02:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_assert ( AssertNode * p_assert ) {
push_text ( " Assert ( " ) ;
print_expression ( p_assert - > condition ) ;
push_line ( " ) " ) ;
}
2018-07-01 18:17:40 +02:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_assignment ( AssignmentNode * p_assignment ) {
switch ( p_assignment - > assignee - > type ) {
case Node : : IDENTIFIER :
print_identifier ( static_cast < IdentifierNode * > ( p_assignment - > assignee ) ) ;
2018-07-01 18:17:40 +02:00
break ;
2020-05-02 00:14:56 +02:00
case Node : : SUBSCRIPT :
print_subscript ( static_cast < SubscriptNode * > ( p_assignment - > assignee ) ) ;
break ;
default :
break ; // Unreachable.
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
push_text ( " " ) ;
switch ( p_assignment - > operation ) {
case AssignmentNode : : OP_ADDITION :
push_text ( " + " ) ;
break ;
case AssignmentNode : : OP_SUBTRACTION :
push_text ( " - " ) ;
break ;
case AssignmentNode : : OP_MULTIPLICATION :
push_text ( " * " ) ;
break ;
case AssignmentNode : : OP_DIVISION :
push_text ( " / " ) ;
break ;
case AssignmentNode : : OP_MODULO :
push_text ( " % " ) ;
break ;
2022-03-07 18:25:21 +01:00
case AssignmentNode : : OP_POWER :
push_text ( " ** " ) ;
break ;
2020-05-02 00:14:56 +02:00
case AssignmentNode : : OP_BIT_SHIFT_LEFT :
push_text ( " << " ) ;
break ;
case AssignmentNode : : OP_BIT_SHIFT_RIGHT :
push_text ( " >> " ) ;
break ;
case AssignmentNode : : OP_BIT_AND :
push_text ( " & " ) ;
break ;
case AssignmentNode : : OP_BIT_OR :
push_text ( " | " ) ;
break ;
case AssignmentNode : : OP_BIT_XOR :
push_text ( " ^ " ) ;
break ;
case AssignmentNode : : OP_NONE :
break ;
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
push_text ( " = " ) ;
print_expression ( p_assignment - > assigned_value ) ;
push_line ( ) ;
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_await ( AwaitNode * p_await ) {
push_text ( " Await " ) ;
print_expression ( p_await - > to_await ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_binary_op ( BinaryOpNode * p_binary_op ) {
// Surround in parenthesis for disambiguation.
push_text ( " ( " ) ;
print_expression ( p_binary_op - > left_operand ) ;
switch ( p_binary_op - > operation ) {
case BinaryOpNode : : OP_ADDITION :
push_text ( " + " ) ;
break ;
case BinaryOpNode : : OP_SUBTRACTION :
push_text ( " - " ) ;
break ;
case BinaryOpNode : : OP_MULTIPLICATION :
push_text ( " * " ) ;
break ;
case BinaryOpNode : : OP_DIVISION :
push_text ( " / " ) ;
break ;
case BinaryOpNode : : OP_MODULO :
push_text ( " % " ) ;
break ;
2022-03-07 18:25:21 +01:00
case BinaryOpNode : : OP_POWER :
push_text ( " ** " ) ;
break ;
2020-05-02 00:14:56 +02:00
case BinaryOpNode : : OP_BIT_LEFT_SHIFT :
push_text ( " << " ) ;
break ;
case BinaryOpNode : : OP_BIT_RIGHT_SHIFT :
push_text ( " >> " ) ;
break ;
case BinaryOpNode : : OP_BIT_AND :
push_text ( " & " ) ;
break ;
case BinaryOpNode : : OP_BIT_OR :
push_text ( " | " ) ;
break ;
case BinaryOpNode : : OP_BIT_XOR :
push_text ( " ^ " ) ;
break ;
case BinaryOpNode : : OP_LOGIC_AND :
push_text ( " AND " ) ;
break ;
case BinaryOpNode : : OP_LOGIC_OR :
push_text ( " OR " ) ;
break ;
case BinaryOpNode : : OP_CONTENT_TEST :
push_text ( " IN " ) ;
break ;
case BinaryOpNode : : OP_COMP_EQUAL :
push_text ( " == " ) ;
break ;
case BinaryOpNode : : OP_COMP_NOT_EQUAL :
push_text ( " != " ) ;
break ;
case BinaryOpNode : : OP_COMP_LESS :
push_text ( " < " ) ;
break ;
case BinaryOpNode : : OP_COMP_LESS_EQUAL :
push_text ( " <= " ) ;
break ;
case BinaryOpNode : : OP_COMP_GREATER :
push_text ( " > " ) ;
break ;
case BinaryOpNode : : OP_COMP_GREATER_EQUAL :
push_text ( " >= " ) ;
break ;
}
print_expression ( p_binary_op - > right_operand ) ;
// Surround in parenthesis for disambiguation.
push_text ( " ) " ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_call ( CallNode * p_call ) {
if ( p_call - > is_super ) {
push_text ( " super " ) ;
if ( p_call - > callee ! = nullptr ) {
push_text ( " . " ) ;
print_expression ( p_call - > callee ) ;
}
} else {
print_expression ( p_call - > callee ) ;
}
push_text ( " ( " ) ;
for ( int i = 0 ; i < p_call - > arguments . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " , " ) ;
}
print_expression ( p_call - > arguments [ i ] ) ;
}
push_text ( " ) " ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_cast ( CastNode * p_cast ) {
print_expression ( p_cast - > operand ) ;
push_text ( " AS " ) ;
print_type ( p_cast - > cast_type ) ;
2019-06-14 16:38:54 +02:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_class ( ClassNode * p_class ) {
push_text ( " Class " ) ;
if ( p_class - > identifier = = nullptr ) {
push_text ( " <unnamed> " ) ;
} else {
print_identifier ( p_class - > identifier ) ;
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
if ( p_class - > extends_used ) {
bool first = true ;
push_text ( " Extends " ) ;
2020-12-15 13:04:21 +01:00
if ( ! p_class - > extends_path . is_empty ( ) ) {
2020-05-02 00:14:56 +02:00
push_text ( vformat ( R " ( " % s " ) " , p_class - > extends_path ) ) ;
first = false ;
}
for ( int i = 0 ; i < p_class - > extends . size ( ) ; i + + ) {
if ( ! first ) {
push_text ( " . " ) ;
} else {
first = false ;
}
2023-03-08 21:06:29 +01:00
push_text ( p_class - > extends [ i ] - > name ) ;
2020-05-02 00:14:56 +02:00
}
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
push_line ( " : " ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
increase_indent ( ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
for ( int i = 0 ; i < p_class - > members . size ( ) ; i + + ) {
const ClassNode : : Member & m = p_class - > members [ i ] ;
2020-03-28 09:12:19 +01:00
2020-05-02 00:14:56 +02:00
switch ( m . type ) {
case ClassNode : : Member : : CLASS :
print_class ( m . m_class ) ;
break ;
case ClassNode : : Member : : VARIABLE :
print_variable ( m . variable ) ;
break ;
case ClassNode : : Member : : CONSTANT :
print_constant ( m . constant ) ;
break ;
case ClassNode : : Member : : SIGNAL :
print_signal ( m . signal ) ;
break ;
case ClassNode : : Member : : FUNCTION :
print_function ( m . function ) ;
break ;
case ClassNode : : Member : : ENUM :
print_enum ( m . m_enum ) ;
break ;
case ClassNode : : Member : : ENUM_VALUE :
break ; // Nothing. Will be printed by enum.
2022-07-03 21:30:08 +02:00
case ClassNode : : Member : : GROUP :
break ; // Nothing. Groups are only used by inspector.
2020-05-02 00:14:56 +02:00
case ClassNode : : Member : : UNDEFINED :
push_line ( " <unknown member> " ) ;
break ;
}
2018-05-30 04:16:52 +02:00
}
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
decrease_indent ( ) ;
}
2019-03-03 20:36:42 +01:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_constant ( ConstantNode * p_constant ) {
push_text ( " Constant " ) ;
print_identifier ( p_constant - > identifier ) ;
2018-05-30 04:16:52 +02:00
2020-05-02 00:14:56 +02:00
increase_indent ( ) ;
push_line ( ) ;
push_text ( " = " ) ;
if ( p_constant - > initializer = = nullptr ) {
push_text ( " <missing value> " ) ;
} else {
print_expression ( p_constant - > initializer ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
decrease_indent ( ) ;
push_line ( ) ;
}
2018-05-30 04:16:52 +02:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_dictionary ( DictionaryNode * p_dictionary ) {
push_line ( " { " ) ;
increase_indent ( ) ;
for ( int i = 0 ; i < p_dictionary - > elements . size ( ) ; i + + ) {
print_expression ( p_dictionary - > elements [ i ] . key ) ;
if ( p_dictionary - > style = = DictionaryNode : : PYTHON_DICT ) {
push_text ( " : " ) ;
} else {
push_text ( " = " ) ;
}
print_expression ( p_dictionary - > elements [ i ] . value ) ;
push_line ( " , " ) ;
}
decrease_indent ( ) ;
push_text ( " } " ) ;
}
2020-01-08 16:22:41 +01:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_expression ( ExpressionNode * p_expression ) {
2021-03-25 14:36:29 +01:00
if ( p_expression = = nullptr ) {
push_text ( " <invalid expression> " ) ;
return ;
}
2020-05-02 00:14:56 +02:00
switch ( p_expression - > type ) {
case Node : : ARRAY :
print_array ( static_cast < ArrayNode * > ( p_expression ) ) ;
break ;
case Node : : ASSIGNMENT :
print_assignment ( static_cast < AssignmentNode * > ( p_expression ) ) ;
break ;
case Node : : AWAIT :
print_await ( static_cast < AwaitNode * > ( p_expression ) ) ;
break ;
case Node : : BINARY_OPERATOR :
print_binary_op ( static_cast < BinaryOpNode * > ( p_expression ) ) ;
break ;
case Node : : CALL :
print_call ( static_cast < CallNode * > ( p_expression ) ) ;
break ;
case Node : : CAST :
print_cast ( static_cast < CastNode * > ( p_expression ) ) ;
break ;
case Node : : DICTIONARY :
print_dictionary ( static_cast < DictionaryNode * > ( p_expression ) ) ;
break ;
case Node : : GET_NODE :
print_get_node ( static_cast < GetNodeNode * > ( p_expression ) ) ;
break ;
case Node : : IDENTIFIER :
print_identifier ( static_cast < IdentifierNode * > ( p_expression ) ) ;
break ;
2021-03-25 14:36:29 +01:00
case Node : : LAMBDA :
print_lambda ( static_cast < LambdaNode * > ( p_expression ) ) ;
break ;
2020-05-02 00:14:56 +02:00
case Node : : LITERAL :
print_literal ( static_cast < LiteralNode * > ( p_expression ) ) ;
break ;
case Node : : PRELOAD :
print_preload ( static_cast < PreloadNode * > ( p_expression ) ) ;
break ;
case Node : : SELF :
print_self ( static_cast < SelfNode * > ( p_expression ) ) ;
break ;
case Node : : SUBSCRIPT :
print_subscript ( static_cast < SubscriptNode * > ( p_expression ) ) ;
break ;
case Node : : TERNARY_OPERATOR :
print_ternary_op ( static_cast < TernaryOpNode * > ( p_expression ) ) ;
break ;
2023-02-17 00:16:24 +01:00
case Node : : TYPE_TEST :
print_type_test ( static_cast < TypeTestNode * > ( p_expression ) ) ;
break ;
2020-05-02 00:14:56 +02:00
case Node : : UNARY_OPERATOR :
print_unary_op ( static_cast < UnaryOpNode * > ( p_expression ) ) ;
break ;
default :
push_text ( vformat ( " <unknown expression %d> " , p_expression - > type ) ) ;
break ;
2020-05-14 16:41:43 +02:00
}
2020-05-02 00:14:56 +02:00
}
2018-05-30 04:16:54 +02:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_enum ( EnumNode * p_enum ) {
push_text ( " Enum " ) ;
if ( p_enum - > identifier ! = nullptr ) {
print_identifier ( p_enum - > identifier ) ;
} else {
push_text ( " <unnamed> " ) ;
}
2018-05-30 04:16:54 +02:00
2020-05-02 00:14:56 +02:00
push_line ( " { " ) ;
increase_indent ( ) ;
for ( int i = 0 ; i < p_enum - > values . size ( ) ; i + + ) {
const EnumNode : : Value & item = p_enum - > values [ i ] ;
print_identifier ( item . identifier ) ;
push_text ( " = " ) ;
push_text ( itos ( item . value ) ) ;
push_line ( " , " ) ;
2018-05-30 04:16:54 +02:00
}
2020-05-02 00:14:56 +02:00
decrease_indent ( ) ;
push_line ( " } " ) ;
}
2018-05-30 04:16:54 +02:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_for ( ForNode * p_for ) {
push_text ( " For " ) ;
print_identifier ( p_for - > variable ) ;
push_text ( " IN " ) ;
print_expression ( p_for - > list ) ;
push_line ( " : " ) ;
2018-05-30 04:16:54 +02:00
2020-05-02 00:14:56 +02:00
increase_indent ( ) ;
2020-03-28 09:12:19 +01:00
2020-05-02 00:14:56 +02:00
print_suite ( p_for - > loop ) ;
2018-05-30 04:16:54 +02:00
2020-05-02 00:14:56 +02:00
decrease_indent ( ) ;
}
2019-01-23 21:45:33 +01:00
2021-03-25 14:36:29 +01:00
void GDScriptParser : : TreePrinter : : print_function ( FunctionNode * p_function , const String & p_context ) {
2021-07-24 15:46:25 +02:00
for ( const AnnotationNode * E : p_function - > annotations ) {
2021-07-16 05:45:57 +02:00
print_annotation ( E ) ;
2020-05-02 00:14:56 +02:00
}
2024-01-22 15:31:55 +01:00
if ( p_function - > is_static ) {
push_text ( " Static " ) ;
}
2021-03-25 14:36:29 +01:00
push_text ( p_context ) ;
push_text ( " " ) ;
if ( p_function - > identifier ) {
print_identifier ( p_function - > identifier ) ;
} else {
push_text ( " <anonymous> " ) ;
}
2020-05-02 00:14:56 +02:00
push_text ( " ( " ) ;
for ( int i = 0 ; i < p_function - > parameters . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " , " ) ;
2018-07-01 18:17:40 +02:00
}
2020-05-02 00:14:56 +02:00
print_parameter ( p_function - > parameters [ i ] ) ;
}
push_line ( " ) : " ) ;
increase_indent ( ) ;
print_suite ( p_function - > body ) ;
decrease_indent ( ) ;
}
void GDScriptParser : : TreePrinter : : print_get_node ( GetNodeNode * p_get_node ) {
2022-05-26 17:56:39 +02:00
if ( p_get_node - > use_dollar ) {
push_text ( " $ " ) ;
2018-07-01 18:17:40 +02:00
}
2022-05-26 17:56:39 +02:00
push_text ( p_get_node - > full_path ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_identifier ( IdentifierNode * p_identifier ) {
2022-01-03 01:47:18 +01:00
if ( p_identifier ! = nullptr ) {
push_text ( p_identifier - > name ) ;
} else {
push_text ( " <invalid identifier> " ) ;
}
2014-02-25 13:31:47 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_if ( IfNode * p_if , bool p_is_elif ) {
if ( p_is_elif ) {
push_text ( " Elif " ) ;
} else {
push_text ( " If " ) ;
}
print_expression ( p_if - > condition ) ;
push_line ( " : " ) ;
2014-12-17 02:31:57 +01:00
2020-05-02 00:14:56 +02:00
increase_indent ( ) ;
print_suite ( p_if - > true_block ) ;
decrease_indent ( ) ;
2014-02-25 13:31:47 +01:00
2020-05-02 00:14:56 +02:00
// FIXME: Properly detect "elif" blocks.
if ( p_if - > false_block ! = nullptr ) {
push_line ( " Else : " ) ;
increase_indent ( ) ;
print_suite ( p_if - > false_block ) ;
decrease_indent ( ) ;
}
2014-02-25 13:31:47 +01:00
}
2021-03-25 14:36:29 +01:00
void GDScriptParser : : TreePrinter : : print_lambda ( LambdaNode * p_lambda ) {
print_function ( p_lambda - > function , " Lambda " ) ;
2021-03-26 13:03:16 +01:00
push_text ( " | captures [ " ) ;
2021-03-28 16:03:13 +02:00
for ( int i = 0 ; i < p_lambda - > captures . size ( ) ; i + + ) {
if ( i > 0 ) {
2021-03-26 13:03:16 +01:00
push_text ( " , " ) ;
}
2021-03-28 16:03:13 +02:00
push_text ( p_lambda - > captures [ i ] - > name . operator String ( ) ) ;
2021-03-26 13:03:16 +01:00
}
push_line ( " ] " ) ;
2021-03-25 14:36:29 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_literal ( LiteralNode * p_literal ) {
// Prefix for string types.
switch ( p_literal - > value . get_type ( ) ) {
case Variant : : NODE_PATH :
push_text ( " ^ \" " ) ;
break ;
case Variant : : STRING :
push_text ( " \" " ) ;
break ;
case Variant : : STRING_NAME :
push_text ( " & \" " ) ;
break ;
default :
break ;
}
push_text ( p_literal - > value ) ;
// Suffix for string types.
switch ( p_literal - > value . get_type ( ) ) {
case Variant : : NODE_PATH :
case Variant : : STRING :
case Variant : : STRING_NAME :
push_text ( " \" " ) ;
break ;
default :
break ;
}
2016-01-23 19:36:03 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_match ( MatchNode * p_match ) {
push_text ( " Match " ) ;
print_expression ( p_match - > test ) ;
push_line ( " : " ) ;
increase_indent ( ) ;
for ( int i = 0 ; i < p_match - > branches . size ( ) ; i + + ) {
print_match_branch ( p_match - > branches [ i ] ) ;
}
decrease_indent ( ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_match_branch ( MatchBranchNode * p_match_branch ) {
for ( int i = 0 ; i < p_match_branch - > patterns . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " , " ) ;
}
print_match_pattern ( p_match_branch - > patterns [ i ] ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
push_line ( " : " ) ;
2014-02-10 02:10:30 +01:00
2020-05-02 00:14:56 +02:00
increase_indent ( ) ;
print_suite ( p_match_branch - > block ) ;
decrease_indent ( ) ;
}
2014-12-17 03:46:55 +01:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_match_pattern ( PatternNode * p_match_pattern ) {
switch ( p_match_pattern - > pattern_type ) {
case PatternNode : : PT_LITERAL :
print_literal ( p_match_pattern - > literal ) ;
break ;
case PatternNode : : PT_WILDCARD :
push_text ( " _ " ) ;
break ;
case PatternNode : : PT_REST :
push_text ( " .. " ) ;
break ;
case PatternNode : : PT_BIND :
push_text ( " Var " ) ;
print_identifier ( p_match_pattern - > bind ) ;
break ;
case PatternNode : : PT_EXPRESSION :
print_expression ( p_match_pattern - > expression ) ;
break ;
case PatternNode : : PT_ARRAY :
push_text ( " [ " ) ;
for ( int i = 0 ; i < p_match_pattern - > array . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " , " ) ;
}
print_match_pattern ( p_match_pattern - > array [ i ] ) ;
}
push_text ( " ] " ) ;
break ;
case PatternNode : : PT_DICTIONARY :
push_text ( " { " ) ;
for ( int i = 0 ; i < p_match_pattern - > dictionary . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " , " ) ;
}
if ( p_match_pattern - > dictionary [ i ] . key ! = nullptr ) {
// Key can be null for rest pattern.
print_expression ( p_match_pattern - > dictionary [ i ] . key ) ;
push_text ( " : " ) ;
}
print_match_pattern ( p_match_pattern - > dictionary [ i ] . value_pattern ) ;
}
push_text ( " } " ) ;
break ;
}
}
2015-08-30 16:50:10 +02:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_parameter ( ParameterNode * p_parameter ) {
print_identifier ( p_parameter - > identifier ) ;
if ( p_parameter - > datatype_specifier ! = nullptr ) {
push_text ( " : " ) ;
print_type ( p_parameter - > datatype_specifier ) ;
}
2022-12-22 21:43:36 +01:00
if ( p_parameter - > initializer ! = nullptr ) {
2020-05-02 00:14:56 +02:00
push_text ( " = " ) ;
2022-12-22 21:43:36 +01:00
print_expression ( p_parameter - > initializer ) ;
2020-05-02 00:14:56 +02:00
}
}
2014-12-17 02:31:57 +01:00
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_preload ( PreloadNode * p_preload ) {
push_text ( R " (Preload ( " ) " );
push_text ( p_preload - > resolved_path ) ;
push_text ( R " ( " ) " );
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_return ( ReturnNode * p_return ) {
push_text ( " Return " ) ;
if ( p_return - > return_value ! = nullptr ) {
push_text ( " " ) ;
print_expression ( p_return - > return_value ) ;
}
push_line ( ) ;
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_self ( SelfNode * p_self ) {
push_text ( " Self( " ) ;
if ( p_self - > current_class - > identifier ! = nullptr ) {
print_identifier ( p_self - > current_class - > identifier ) ;
} else {
push_text ( " <main class> " ) ;
}
push_text ( " ) " ) ;
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_signal ( SignalNode * p_signal ) {
push_text ( " Signal " ) ;
print_identifier ( p_signal - > identifier ) ;
push_text ( " ( " ) ;
for ( int i = 0 ; i < p_signal - > parameters . size ( ) ; i + + ) {
print_parameter ( p_signal - > parameters [ i ] ) ;
}
push_line ( " ) " ) ;
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_subscript ( SubscriptNode * p_subscript ) {
print_expression ( p_subscript - > base ) ;
if ( p_subscript - > is_attribute ) {
push_text ( " . " ) ;
print_identifier ( p_subscript - > attribute ) ;
} else {
push_text ( " [ " ) ;
print_expression ( p_subscript - > index ) ;
push_text ( " ] " ) ;
}
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_statement ( Node * p_statement ) {
switch ( p_statement - > type ) {
case Node : : ASSERT :
print_assert ( static_cast < AssertNode * > ( p_statement ) ) ;
break ;
case Node : : VARIABLE :
print_variable ( static_cast < VariableNode * > ( p_statement ) ) ;
break ;
case Node : : CONSTANT :
print_constant ( static_cast < ConstantNode * > ( p_statement ) ) ;
break ;
case Node : : IF :
print_if ( static_cast < IfNode * > ( p_statement ) ) ;
break ;
case Node : : FOR :
print_for ( static_cast < ForNode * > ( p_statement ) ) ;
break ;
case Node : : WHILE :
print_while ( static_cast < WhileNode * > ( p_statement ) ) ;
break ;
case Node : : MATCH :
print_match ( static_cast < MatchNode * > ( p_statement ) ) ;
break ;
case Node : : RETURN :
print_return ( static_cast < ReturnNode * > ( p_statement ) ) ;
break ;
case Node : : BREAK :
push_line ( " Break " ) ;
break ;
case Node : : CONTINUE :
push_line ( " Continue " ) ;
break ;
case Node : : PASS :
push_line ( " Pass " ) ;
break ;
case Node : : BREAKPOINT :
push_line ( " Breakpoint " ) ;
break ;
case Node : : ASSIGNMENT :
print_assignment ( static_cast < AssignmentNode * > ( p_statement ) ) ;
break ;
default :
if ( p_statement - > is_expression ( ) ) {
print_expression ( static_cast < ExpressionNode * > ( p_statement ) ) ;
push_line ( ) ;
} else {
push_line ( vformat ( " <unknown statement %d> " , p_statement - > type ) ) ;
}
break ;
}
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_suite ( SuiteNode * p_suite ) {
for ( int i = 0 ; i < p_suite - > statements . size ( ) ; i + + ) {
print_statement ( p_suite - > statements [ i ] ) ;
}
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_ternary_op ( TernaryOpNode * p_ternary_op ) {
// Surround in parenthesis for disambiguation.
push_text ( " ( " ) ;
print_expression ( p_ternary_op - > true_expr ) ;
push_text ( " ) IF ( " ) ;
print_expression ( p_ternary_op - > condition ) ;
push_text ( " ) ELSE ( " ) ;
print_expression ( p_ternary_op - > false_expr ) ;
push_text ( " ) " ) ;
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_type ( TypeNode * p_type ) {
2020-12-15 13:04:21 +01:00
if ( p_type - > type_chain . is_empty ( ) ) {
2020-05-02 00:14:56 +02:00
push_text ( " Void " ) ;
2020-06-10 23:18:10 +02:00
} else {
for ( int i = 0 ; i < p_type - > type_chain . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " . " ) ;
}
print_identifier ( p_type - > type_chain [ i ] ) ;
}
2020-05-02 00:14:56 +02:00
}
2014-12-17 02:31:57 +01:00
}
2023-02-17 00:16:24 +01:00
void GDScriptParser : : TreePrinter : : print_type_test ( TypeTestNode * p_test ) {
print_expression ( p_test - > operand ) ;
push_text ( " IS " ) ;
print_type ( p_test - > test_type ) ;
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_unary_op ( UnaryOpNode * p_unary_op ) {
// Surround in parenthesis for disambiguation.
push_text ( " ( " ) ;
switch ( p_unary_op - > operation ) {
case UnaryOpNode : : OP_POSITIVE :
push_text ( " + " ) ;
break ;
case UnaryOpNode : : OP_NEGATIVE :
push_text ( " - " ) ;
break ;
case UnaryOpNode : : OP_LOGIC_NOT :
push_text ( " NOT " ) ;
break ;
case UnaryOpNode : : OP_COMPLEMENT :
push_text ( " ~ " ) ;
break ;
}
print_expression ( p_unary_op - > operand ) ;
// Surround in parenthesis for disambiguation.
push_text ( " ) " ) ;
2014-12-17 02:31:57 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_variable ( VariableNode * p_variable ) {
2021-07-24 15:46:25 +02:00
for ( const AnnotationNode * E : p_variable - > annotations ) {
2021-07-16 05:45:57 +02:00
print_annotation ( E ) ;
2020-05-02 00:14:56 +02:00
}
2024-01-22 15:31:55 +01:00
if ( p_variable - > is_static ) {
push_text ( " Static " ) ;
}
2020-05-02 00:14:56 +02:00
push_text ( " Variable " ) ;
print_identifier ( p_variable - > identifier ) ;
2020-06-01 21:41:05 +02:00
push_text ( " : " ) ;
if ( p_variable - > datatype_specifier ! = nullptr ) {
print_type ( p_variable - > datatype_specifier ) ;
} else if ( p_variable - > infer_datatype ) {
push_text ( " <inferred type> " ) ;
} else {
push_text ( " Variant " ) ;
}
2020-05-02 00:14:56 +02:00
increase_indent ( ) ;
push_line ( ) ;
push_text ( " = " ) ;
if ( p_variable - > initializer = = nullptr ) {
push_text ( " <default value> " ) ;
} else {
print_expression ( p_variable - > initializer ) ;
}
2020-06-01 21:41:05 +02:00
push_line ( ) ;
if ( p_variable - > property ! = VariableNode : : PROP_NONE ) {
if ( p_variable - > getter ! = nullptr ) {
push_text ( " Get " ) ;
if ( p_variable - > property = = VariableNode : : PROP_INLINE ) {
push_line ( " : " ) ;
increase_indent ( ) ;
2021-09-06 07:04:43 +02:00
print_suite ( p_variable - > getter - > body ) ;
2020-06-01 21:41:05 +02:00
decrease_indent ( ) ;
} else {
push_line ( " = " ) ;
increase_indent ( ) ;
print_identifier ( p_variable - > getter_pointer ) ;
push_line ( ) ;
decrease_indent ( ) ;
}
}
if ( p_variable - > setter ! = nullptr ) {
push_text ( " Set ( " ) ;
if ( p_variable - > property = = VariableNode : : PROP_INLINE ) {
if ( p_variable - > setter_parameter ! = nullptr ) {
print_identifier ( p_variable - > setter_parameter ) ;
} else {
push_text ( " <missing> " ) ;
}
push_line ( " ): " ) ;
increase_indent ( ) ;
2021-09-06 07:04:43 +02:00
print_suite ( p_variable - > setter - > body ) ;
2020-06-01 21:41:05 +02:00
decrease_indent ( ) ;
} else {
push_line ( " = " ) ;
increase_indent ( ) ;
print_identifier ( p_variable - > setter_pointer ) ;
push_line ( ) ;
decrease_indent ( ) ;
}
}
}
2020-05-02 00:14:56 +02:00
decrease_indent ( ) ;
push_line ( ) ;
2016-09-12 15:52:29 +02:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_while ( WhileNode * p_while ) {
push_text ( " While " ) ;
print_expression ( p_while - > condition ) ;
push_line ( " : " ) ;
increase_indent ( ) ;
print_suite ( p_while - > loop ) ;
decrease_indent ( ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
void GDScriptParser : : TreePrinter : : print_tree ( const GDScriptParser & p_parser ) {
2023-09-28 11:40:18 +02:00
ERR_FAIL_NULL_MSG ( p_parser . get_tree ( ) , " Parse the code before printing the parse tree. " ) ;
2020-05-02 00:14:56 +02:00
if ( p_parser . is_tool ( ) ) {
push_line ( " @tool " ) ;
}
2020-12-15 13:04:21 +01:00
if ( ! p_parser . get_tree ( ) - > icon_path . is_empty ( ) ) {
2020-05-02 00:14:56 +02:00
push_text ( R " (@icon ( " ) " );
push_text ( p_parser . get_tree ( ) - > icon_path ) ;
push_line ( " \" ) " ) ;
}
print_class ( p_parser . get_tree ( ) ) ;
2021-10-07 15:40:48 +02:00
print_line ( String ( printed ) ) ;
2014-02-10 02:10:30 +01:00
}
2020-05-02 00:14:56 +02:00
# endif // DEBUG_ENABLED