2014-02-10 01:10:30 +00:00
/*************************************************************************/
2017-11-16 17:38:18 +00:00
/* gdscript_parser.cpp */
2014-02-10 01:10:30 +00:00
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
2017-08-27 12:16:55 +00:00
/* https://godotengine.org */
2014-02-10 01:10:30 +00:00
/*************************************************************************/
2021-01-01 19:13:46 +00:00
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
2014-02-10 01:10:30 +00:00
/* */
/* 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-04 23:50:27 +00:00
2017-11-16 17:38:18 +00:00
# include "gdscript_parser.h"
2020-11-07 22:33:38 +00:00
# include "core/config/project_settings.h"
2021-06-11 12:51:48 +00:00
# include "core/io/file_access.h"
2018-09-11 16:13:45 +00:00
# include "core/io/resource_loader.h"
2020-05-01 22:14:56 +00:00
# include "core/math/math_defs.h"
2020-06-10 21:18:10 +00:00
# include "gdscript.h"
2020-05-01 22:14:56 +00:00
# ifdef DEBUG_ENABLED
# include "core/os/os.h"
2020-11-07 22:33:38 +00:00
# include "core/string/string_builder.h"
2020-05-01 22:14:56 +00:00
# endif // DEBUG_ENABLED
2020-07-06 15:24:24 +00:00
# ifdef TOOLS_ENABLED
# include "editor/editor_settings.h"
# endif // TOOLS_ENABLED
2020-05-01 22:14:56 +00:00
static HashMap < StringName , Variant : : Type > builtin_types ;
Variant : : Type GDScriptParser : : get_builtin_type ( const StringName & p_type ) {
2020-12-15 12:04:21 +00:00
if ( builtin_types . is_empty ( ) ) {
2020-05-01 22:14:56 +00:00
builtin_types [ " bool " ] = Variant : : BOOL ;
builtin_types [ " int " ] = Variant : : INT ;
builtin_types [ " float " ] = Variant : : FLOAT ;
builtin_types [ " String " ] = Variant : : STRING ;
builtin_types [ " Vector2 " ] = Variant : : VECTOR2 ;
builtin_types [ " Vector2i " ] = Variant : : VECTOR2I ;
builtin_types [ " Rect2 " ] = Variant : : RECT2 ;
builtin_types [ " Rect2i " ] = Variant : : RECT2I ;
builtin_types [ " Transform2D " ] = Variant : : TRANSFORM2D ;
builtin_types [ " Vector3 " ] = Variant : : VECTOR3 ;
builtin_types [ " Vector3i " ] = Variant : : VECTOR3I ;
builtin_types [ " AABB " ] = Variant : : AABB ;
builtin_types [ " Plane " ] = Variant : : PLANE ;
2021-01-20 07:02:02 +00:00
builtin_types [ " Quaternion " ] = Variant : : QUATERNION ;
2020-05-01 22:14:56 +00:00
builtin_types [ " Basis " ] = Variant : : BASIS ;
2021-06-15 13:04:50 +00:00
builtin_types [ " Transform3D " ] = Variant : : TRANSFORM3D ;
2020-05-01 22:14:56 +00:00
builtin_types [ " Color " ] = Variant : : COLOR ;
2020-11-09 13:53:05 +00:00
builtin_types [ " RID " ] = Variant : : RID ;
2020-05-01 22:14:56 +00:00
builtin_types [ " Object " ] = Variant : : OBJECT ;
builtin_types [ " StringName " ] = Variant : : STRING_NAME ;
builtin_types [ " NodePath " ] = Variant : : NODE_PATH ;
builtin_types [ " Dictionary " ] = Variant : : DICTIONARY ;
builtin_types [ " Callable " ] = Variant : : CALLABLE ;
builtin_types [ " Signal " ] = Variant : : SIGNAL ;
builtin_types [ " Array " ] = Variant : : ARRAY ;
builtin_types [ " PackedByteArray " ] = Variant : : PACKED_BYTE_ARRAY ;
builtin_types [ " PackedInt32Array " ] = Variant : : PACKED_INT32_ARRAY ;
builtin_types [ " PackedInt64Array " ] = Variant : : PACKED_INT64_ARRAY ;
builtin_types [ " PackedFloat32Array " ] = Variant : : PACKED_FLOAT32_ARRAY ;
builtin_types [ " PackedFloat64Array " ] = Variant : : PACKED_FLOAT64_ARRAY ;
builtin_types [ " PackedStringArray " ] = Variant : : PACKED_STRING_ARRAY ;
builtin_types [ " PackedVector2Array " ] = Variant : : PACKED_VECTOR2_ARRAY ;
builtin_types [ " PackedVector3Array " ] = Variant : : PACKED_VECTOR3_ARRAY ;
builtin_types [ " PackedColorArray " ] = Variant : : PACKED_COLOR_ARRAY ;
// NIL is not here, hence the -1.
if ( builtin_types . size ( ) ! = Variant : : VARIANT_MAX - 1 ) {
ERR_PRINT ( " Outdated parser: amount of built-in types don't match the amount of types in Variant. " ) ;
}
}
if ( builtin_types . has ( p_type ) ) {
return builtin_types [ p_type ] ;
}
return Variant : : VARIANT_MAX ;
}
2020-08-01 19:00:26 +00:00
void GDScriptParser : : cleanup ( ) {
builtin_types . clear ( ) ;
}
2020-07-06 15:24:24 +00:00
void GDScriptParser : : get_annotation_list ( List < MethodInfo > * r_annotations ) const {
List < StringName > keys ;
valid_annotations . get_key_list ( & keys ) ;
2021-07-16 03:45:57 +00:00
for ( const StringName & E : keys ) {
r_annotations - > push_back ( valid_annotations [ E ] . info ) ;
2020-07-06 15:24:24 +00:00
}
}
2020-05-01 22:14:56 +00:00
GDScriptParser : : GDScriptParser ( ) {
// Register valid annotations.
// TODO: Should this be static?
register_annotation ( MethodInfo ( " @tool " ) , AnnotationInfo : : SCRIPT , & GDScriptParser : : tool_annotation ) ;
register_annotation ( MethodInfo ( " @icon " , { Variant : : STRING , " icon_path " } ) , AnnotationInfo : : SCRIPT , & GDScriptParser : : icon_annotation ) ;
register_annotation ( MethodInfo ( " @onready " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : onready_annotation ) ;
// Export annotations.
2021-03-17 13:57:30 +00:00
register_annotation ( MethodInfo ( " @export " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_NONE , Variant : : NIL > ) ;
2020-05-01 22:14:56 +00:00
register_annotation ( MethodInfo ( " @export_enum " , { Variant : : STRING , " names " } ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_ENUM , Variant : : INT > , 0 , true ) ;
register_annotation ( MethodInfo ( " @export_file " , { Variant : : STRING , " filter " } ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_FILE , Variant : : STRING > , 1 , true ) ;
register_annotation ( MethodInfo ( " @export_dir " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_DIR , Variant : : STRING > ) ;
register_annotation ( MethodInfo ( " @export_global_file " , { Variant : : STRING , " filter " } ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_GLOBAL_FILE , Variant : : STRING > , 1 , 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 " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_PLACEHOLDER_TEXT , Variant : : STRING > ) ;
register_annotation ( MethodInfo ( " @export_range " , { Variant : : FLOAT , " min " } , { Variant : : FLOAT , " max " } , { Variant : : FLOAT , " step " } , { Variant : : STRING , " slider1 " } , { Variant : : STRING , " slider2 " } ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_RANGE , Variant : : FLOAT > , 3 ) ;
register_annotation ( MethodInfo ( " @export_exp_easing " , { Variant : : STRING , " hint1 " } , { Variant : : STRING , " hint2 " } ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_EXP_EASING , Variant : : FLOAT > , 2 ) ;
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 " , { Variant : : STRING , " type " } ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_NODE_PATH_VALID_TYPES , Variant : : NODE_PATH > , 1 , true ) ;
register_annotation ( MethodInfo ( " @export_flags " , { Variant : : STRING , " names " } ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_FLAGS , Variant : : INT > , 0 , 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 > ) ;
2021-03-08 19:56:33 +00:00
register_annotation ( MethodInfo ( " @export_flags_2d_navigation " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_LAYERS_2D_NAVIGATION , Variant : : INT > ) ;
2020-05-01 22:14:56 +00:00
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 > ) ;
2021-03-08 19:56:33 +00:00
register_annotation ( MethodInfo ( " @export_flags_3d_navigation " ) , AnnotationInfo : : VARIABLE , & GDScriptParser : : export_annotations < PROPERTY_HINT_LAYERS_3D_NAVIGATION , Variant : : INT > ) ;
2020-05-01 22:14:56 +00:00
// Networking.
2021-09-03 17:40:47 +00:00
register_annotation ( MethodInfo ( " @rpc " , { Variant : : STRING , " mode " } , { Variant : : STRING , " sync " } , { Variant : : STRING , " transfer_mode " } , { Variant : : INT , " transfer_channel " } ) , AnnotationInfo : : FUNCTION , & GDScriptParser : : network_annotations < Multiplayer : : RPC_MODE_AUTHORITY > , 4 , true ) ;
2020-05-01 22:14:56 +00:00
// TODO: Warning annotations.
}
GDScriptParser : : ~ GDScriptParser ( ) {
clear ( ) ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
void GDScriptParser : : clear ( ) {
while ( list ! = nullptr ) {
Node * element = list ;
list = list - > next ;
memdelete ( element ) ;
2020-05-14 14:41:43 +00:00
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
head = nullptr ;
list = nullptr ;
_is_tool = false ;
for_completion = false ;
errors . clear ( ) ;
multiline_stack . clear ( ) ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00: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 ) {
errors . push_back ( { p_message , current . start_line , current . start_column } ) ;
} else {
errors . push_back ( { p_message , p_origin - > start_line , p_origin - > leftmost_column } ) ;
}
}
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-06-11 22:31:28 +00:00
void GDScriptParser : : push_warning ( const Node * p_source , GDScriptWarning : : Code p_code , const String & p_symbol1 , const String & p_symbol2 , const String & p_symbol3 , const String & p_symbol4 ) {
2020-11-13 14:09:52 +00:00
ERR_FAIL_COND ( p_source = = nullptr ) ;
2020-06-11 22:31:28 +00:00
Vector < String > symbols ;
2020-12-15 12:04:21 +00:00
if ( ! p_symbol1 . is_empty ( ) ) {
2020-06-11 22:31:28 +00:00
symbols . push_back ( p_symbol1 ) ;
}
2020-12-15 12:04:21 +00:00
if ( ! p_symbol2 . is_empty ( ) ) {
2020-06-11 22:31:28 +00:00
symbols . push_back ( p_symbol2 ) ;
}
2020-12-15 12:04:21 +00:00
if ( ! p_symbol3 . is_empty ( ) ) {
2020-06-11 22:31:28 +00:00
symbols . push_back ( p_symbol3 ) ;
}
2020-12-15 12:04:21 +00:00
if ( ! p_symbol4 . is_empty ( ) ) {
2020-06-11 22:31:28 +00:00
symbols . push_back ( p_symbol4 ) ;
}
push_warning ( p_source , p_code , symbols ) ;
}
void GDScriptParser : : push_warning ( const Node * p_source , GDScriptWarning : : Code p_code , const Vector < String > & p_symbols ) {
2020-11-13 14:09:52 +00:00
ERR_FAIL_COND ( p_source = = nullptr ) ;
2020-06-11 22:31:28 +00:00
if ( is_ignoring_warnings ) {
return ;
}
if ( GLOBAL_GET ( " debug/gdscript/warnings/exclude_addons " ) . booleanize ( ) & & script_path . begins_with ( " res://addons/ " ) ) {
return ;
}
String warn_name = GDScriptWarning : : get_name_from_code ( ( GDScriptWarning : : Code ) p_code ) . to_lower ( ) ;
if ( ignored_warnings . has ( warn_name ) ) {
return ;
}
if ( ! GLOBAL_GET ( " debug/gdscript/warnings/ " + warn_name ) ) {
return ;
}
GDScriptWarning warning ;
warning . code = p_code ;
warning . symbols = p_symbols ;
warning . start_line = p_source - > start_line ;
warning . end_line = p_source - > end_line ;
warning . leftmost_column = p_source - > leftmost_column ;
warning . rightmost_column = p_source - > rightmost_column ;
List < GDScriptWarning > : : Element * before = nullptr ;
2021-07-16 03:45:57 +00:00
for ( List < GDScriptWarning > : : Element * E = warnings . front ( ) ; E ; E = E - > next ( ) ) {
2020-06-11 22:31:28 +00:00
if ( E - > get ( ) . start_line > warning . start_line ) {
break ;
}
before = E ;
}
if ( before ) {
warnings . insert_after ( before , warning ) ;
} else {
warnings . push_front ( warning ) ;
}
}
2020-07-16 01:02:44 +00:00
# endif
2020-06-11 22:31:28 +00:00
2020-07-06 15:24:24 +00: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 ;
}
if ( previous . cursor_place ! = GDScriptTokenizer : : CURSOR_MIDDLE & & previous . cursor_place ! = GDScriptTokenizer : : CURSOR_END & & current . cursor_place = = GDScriptTokenizer : : CURSOR_NONE ) {
return ;
}
CompletionContext context ;
context . type = p_type ;
context . current_class = current_class ;
context . current_function = current_function ;
context . current_suite = current_suite ;
context . current_line = tokenizer . get_cursor_line ( ) ;
context . current_argument = p_argument ;
context . node = p_node ;
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 ;
}
if ( previous . cursor_place ! = GDScriptTokenizer : : CURSOR_MIDDLE & & previous . cursor_place ! = GDScriptTokenizer : : CURSOR_END & & current . cursor_place = = GDScriptTokenizer : : CURSOR_NONE ) {
return ;
}
CompletionContext context ;
context . type = p_type ;
context . current_class = current_class ;
context . current_function = current_function ;
context . current_suite = current_suite ;
context . current_line = tokenizer . get_cursor_line ( ) ;
context . builtin_type = p_builtin_type ;
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 ) ;
if ( previous . cursor_place = = GDScriptTokenizer : : CURSOR_MIDDLE | | previous . cursor_place = = GDScriptTokenizer : : CURSOR_END | | current . cursor_place = = GDScriptTokenizer : : CURSOR_BEGINNING ) {
completion_call = call ;
}
}
void GDScriptParser : : pop_completion_call ( ) {
if ( ! for_completion ) {
return ;
}
2020-12-15 12:04:21 +00:00
ERR_FAIL_COND_MSG ( completion_call_stack . is_empty ( ) , " Trying to pop empty completion call stack " ) ;
2020-07-06 15:24:24 +00: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 12:04:21 +00:00
ERR_FAIL_COND_MSG ( completion_call_stack . is_empty ( ) , " Trying to set argument on empty completion call stack " ) ;
2020-07-06 15:24:24 +00:00
completion_call_stack . back ( ) - > get ( ) . argument = p_argument ;
}
2020-05-01 22:14:56 +00:00
Error GDScriptParser : : parse ( const String & p_source_code , const String & p_script_path , bool p_for_completion ) {
clear ( ) ;
2020-07-06 15:24:24 +00:00
String source = p_source_code ;
int cursor_line = - 1 ;
int cursor_column = - 1 ;
for_completion = p_for_completion ;
int tab_size = 4 ;
# ifdef TOOLS_ENABLED
if ( EditorSettings : : get_singleton ( ) ) {
2021-08-15 17:14:46 +00:00
tab_size = EditorSettings : : get_singleton ( ) - > get_setting ( " text_editor/behavior/indent/size " ) ;
2020-07-06 15:24:24 +00: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 10:43:20 +00:00
if ( line [ j ] = = char32_t ( 0xFFFF ) ) {
2020-07-06 15:24:24 +00: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 ( ) ) ;
}
tokenizer . set_source_code ( source ) ;
tokenizer . set_cursor_position ( cursor_line , cursor_column ) ;
2020-06-12 00:49:58 +00:00
script_path = p_script_path ;
2020-05-01 22:14:56 +00:00
current = tokenizer . scan ( ) ;
2021-09-11 18:38:15 +00: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 ) ;
}
2020-06-01 19:41:05 +00:00
current = tokenizer . scan ( ) ;
}
2020-05-01 22:14:56 +00:00
2021-09-11 18:38:15 +00: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-01 22:14:56 +00:00
push_multiline ( false ) ; // Keep one for the whole parsing.
parse_program ( ) ;
pop_multiline ( ) ;
2018-07-01 16:17:40 +00:00
2020-05-01 22:14:56 +00:00
# ifdef DEBUG_ENABLED
if ( multiline_stack . size ( ) > 0 ) {
ERR_PRINT ( " Parser bug: Imbalanced multiline stack. " ) ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
# endif
2014-02-10 01:10:30 +00:00
2020-12-15 12:04:21 +00:00
if ( errors . is_empty ( ) ) {
2020-05-01 22:14:56 +00:00
return OK ;
} else {
return ERR_PARSE_ERROR ;
}
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
GDScriptTokenizer : : Token GDScriptParser : : advance ( ) {
2021-03-25 13:36:29 +00:00
lambda_ended = false ; // Empty marker since we're past the end in any case.
2020-05-01 22:14:56 +00: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 19:40:47 +00:00
}
2020-12-15 12:04:21 +00:00
if ( for_completion & & ! completion_call_stack . is_empty ( ) ) {
2020-07-06 15:24:24 +00:00
if ( completion_call . call = = nullptr & & tokenizer . is_past_cursor ( ) ) {
completion_call = completion_call_stack . back ( ) - > get ( ) ;
passed_cursor = true ;
}
}
2020-05-01 22:14:56 +00:00
previous = current ;
current = tokenizer . scan ( ) ;
while ( current . type = = GDScriptTokenizer : : Token : : ERROR ) {
push_error ( current . literal ) ;
current = tokenizer . scan ( ) ;
}
return previous ;
2020-03-06 19:40:47 +00:00
}
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : match ( GDScriptTokenizer : : Token : : Type p_token_type ) {
if ( ! check ( p_token_type ) ) {
2014-02-10 01:10:30 +00:00
return false ;
}
2020-05-01 22:14:56 +00:00
advance ( ) ;
return true ;
}
2014-02-10 01:10:30 +00:00
2021-03-25 13:36:29 +00:00
bool GDScriptParser : : check ( GDScriptTokenizer : : Token : : Type p_token_type ) const {
2020-05-01 22:14:56 +00:00
if ( p_token_type = = GDScriptTokenizer : : Token : : IDENTIFIER ) {
return current . is_identifier ( ) ;
2018-09-16 21:29:17 +00:00
}
2020-05-01 22:14:56 +00:00
return current . type = = p_token_type ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : consume ( GDScriptTokenizer : : Token : : Type p_token_type , const String & p_error_message ) {
if ( match ( p_token_type ) ) {
2015-12-29 14:41:37 +00:00
return true ;
2020-05-01 22:14:56 +00:00
}
push_error ( p_error_message ) ;
return false ;
}
2014-02-10 01:10:30 +00:00
2021-03-25 13:36:29 +00:00
bool GDScriptParser : : is_at_end ( ) const {
2020-05-01 22:14:56 +00:00
return check ( GDScriptTokenizer : : Token : : TK_EOF ) ;
}
2019-10-13 19:48:18 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
advance ( ) ;
2014-02-10 01:10:30 +00:00
}
}
2020-05-01 22:14:56 +00:00
void GDScriptParser : : push_multiline ( bool p_state ) {
multiline_stack . push_back ( p_state ) ;
tokenizer . set_multiline_mode ( p_state ) ;
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 ) {
current = tokenizer . scan ( ) ; // Don't call advance() here, as we don't want to change the previous token.
}
}
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
void GDScriptParser : : pop_multiline ( ) {
ERR_FAIL_COND_MSG ( multiline_stack . size ( ) = = 0 , " Parser bug: trying to pop from multiline stack without available value. " ) ;
multiline_stack . pop_back ( ) ;
tokenizer . set_multiline_mode ( multiline_stack . size ( ) > 0 ? multiline_stack . back ( ) - > get ( ) : false ) ;
}
2021-03-25 13:36:29 +00:00
bool GDScriptParser : : is_statement_end_token ( ) const {
2020-08-18 00:30:39 +00:00
return check ( GDScriptTokenizer : : Token : : NEWLINE ) | | check ( GDScriptTokenizer : : Token : : SEMICOLON ) | | check ( GDScriptTokenizer : : Token : : TK_EOF ) ;
2020-05-01 22:14:56 +00:00
}
2014-02-10 01:10:30 +00:00
2021-03-25 13:36:29 +00:00
bool GDScriptParser : : is_statement_end ( ) const {
return lambda_ended | | in_lambda | | is_statement_end_token ( ) ;
}
2020-05-01 22:14:56 +00:00
void GDScriptParser : : end_statement ( const String & p_context ) {
bool found = false ;
2020-08-18 00:30:39 +00:00
while ( is_statement_end ( ) & & ! is_at_end ( ) ) {
2020-05-01 22:14:56 +00:00
// Remove sequential newlines/semicolons.
2021-03-25 13:36:29 +00: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-01 22:14:56 +00:00
found = true ;
}
2020-08-18 00:30:39 +00:00
if ( ! found & & ! is_at_end ( ) ) {
2020-05-01 22:14:56 +00:00
push_error ( vformat ( R " (Expected end of statement after %s, found " % s " instead.) " , p_context , current . get_name ( ) ) ) ;
}
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
void GDScriptParser : : parse_program ( ) {
head = alloc_node < ClassNode > ( ) ;
current_class = head ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : ANNOTATION ) ) {
// Check for @tool annotation.
AnnotationNode * annotation = parse_annotation ( AnnotationInfo : : SCRIPT | AnnotationInfo : : CLASS_LEVEL ) ;
2020-08-18 00:02:49 +00:00
if ( annotation ! = nullptr ) {
if ( annotation - > name = = " @tool " ) {
// TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
_is_tool = true ;
if ( previous . type ! = GDScriptTokenizer : : Token : : NEWLINE ) {
push_error ( R " (Expected newline after " @ tool " annotation.) " ) ;
}
// @tool annotation has no specific target.
annotation - > apply ( this , nullptr ) ;
} else {
annotation_stack . push_back ( annotation ) ;
2014-02-10 01:10:30 +00:00
}
}
}
2020-05-01 22:14:56 +00:00
for ( bool should_break = false ; ! should_break ; ) {
// Order here doesn't matter, but there should be only one of each at most.
switch ( current . type ) {
case GDScriptTokenizer : : Token : : CLASS_NAME :
2020-12-15 12:04:21 +00:00
if ( ! annotation_stack . is_empty ( ) ) {
2020-05-01 22:14:56 +00:00
push_error ( R " ( " class_name " should be used before annotations.) " ) ;
}
advance ( ) ;
if ( head - > identifier ! = nullptr ) {
push_error ( R " ( " class_name " can only be used once.) " ) ;
} else {
parse_class_name ( ) ;
}
break ;
case GDScriptTokenizer : : Token : : EXTENDS :
2020-12-15 12:04:21 +00:00
if ( ! annotation_stack . is_empty ( ) ) {
2020-05-01 22:14:56 +00:00
push_error ( R " ( " extends " should be used before annotations.) " ) ;
}
advance ( ) ;
if ( head - > extends_used ) {
push_error ( R " ( " extends " can only be used once.) " ) ;
} else {
parse_extends ( ) ;
2020-07-16 01:02:44 +00:00
end_statement ( " superclass " ) ;
2020-05-01 22:14:56 +00:00
}
break ;
default :
should_break = true ;
break ;
}
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00:00
if ( panic_mode ) {
synchronize ( ) ;
2014-12-17 01:31:57 +00:00
}
2020-05-01 22:14:56 +00:00
}
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : ANNOTATION ) ) {
// Check for @icon annotation.
AnnotationNode * annotation = parse_annotation ( AnnotationInfo : : SCRIPT | AnnotationInfo : : CLASS_LEVEL ) ;
if ( annotation ! = nullptr ) {
if ( annotation - > name = = " @icon " ) {
if ( previous . type ! = GDScriptTokenizer : : Token : : NEWLINE ) {
push_error ( R " (Expected newline after " @ icon " annotation.) " ) ;
}
annotation - > apply ( this , head ) ;
} else {
annotation_stack . push_back ( annotation ) ;
}
2016-09-12 13:52:29 +00:00
}
2014-12-17 01:31:57 +00:00
}
2021-09-21 17:13:23 +00:00
parse_class_body ( true ) ;
2020-05-01 22:14:56 +00:00
2020-11-29 02:37:57 +00:00
# ifdef TOOLS_ENABLED
2021-08-09 20:13:42 +00:00
for ( const KeyValue < int , GDScriptTokenizer : : CommentData > & E : tokenizer . get_comments ( ) ) {
if ( E . value . new_line & & E . value . comment . begins_with ( " ## " ) ) {
class_doc_line = MIN ( class_doc_line , E . key ) ;
2020-11-29 02:37:57 +00:00
}
}
if ( has_comment ( class_doc_line ) ) {
get_class_doc_comment ( class_doc_line , head - > doc_brief_description , head - > doc_description , head - > doc_tutorials , false ) ;
}
# endif // TOOLS_ENABLED
2020-05-01 22:14:56 +00:00
if ( ! check ( GDScriptTokenizer : : Token : : TK_EOF ) ) {
push_error ( " Expected end of file. " ) ;
}
clear_unused_annotations ( ) ;
2014-12-17 01:31:57 +00:00
}
2020-05-01 22:14:56 +00:00
GDScriptParser : : ClassNode * GDScriptParser : : parse_class ( ) {
ClassNode * n_class = alloc_node < ClassNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
ClassNode * previous_class = current_class ;
current_class = n_class ;
n_class - > outer = previous_class ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected identifier for the class name after " class " . ) " ) ) {
n_class - > identifier = parse_identifier ( ) ;
}
2014-02-10 01:10:30 +00:00
2020-07-16 01:02:44 +00:00
if ( match ( GDScriptTokenizer : : Token : : EXTENDS ) ) {
2020-05-01 22:14:56 +00:00
parse_extends ( ) ;
}
2016-07-22 12:22:34 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after class declaration.) " ) ;
2014-02-10 01:10:30 +00:00
2021-09-21 17:13:23 +00:00
bool multiline = match ( GDScriptTokenizer : : Token : : NEWLINE ) ;
if ( multiline & & ! consume ( GDScriptTokenizer : : Token : : INDENT , R " (Expected indented block after class declaration.) " ) ) {
2020-05-01 22:14:56 +00:00
current_class = previous_class ;
return n_class ;
}
2014-04-05 21:50:09 +00:00
2020-08-31 13:27:11 +00: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 17:13:23 +00:00
parse_class_body ( multiline ) ;
2019-09-02 11:46:38 +00:00
2021-09-21 17:13:23 +00:00
if ( multiline ) {
consume ( GDScriptTokenizer : : Token : : DEDENT , R " (Missing unindent at the end of the class body.) " ) ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
current_class = previous_class ;
return n_class ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 ( ) ;
}
2017-01-08 05:04:53 +00:00
2020-05-01 22:14:56 +00:00
// TODO: Move this to annotation
if ( match ( GDScriptTokenizer : : Token : : COMMA ) ) {
// Icon path.
if ( consume ( GDScriptTokenizer : : Token : : LITERAL , R " (Expected class icon path string after " , " .) " ) ) {
if ( previous . literal . get_type ( ) ! = Variant : : STRING ) {
push_error ( vformat ( R " (Only strings can be used for the class icon path, found " % s " instead.) " , Variant : : get_type_name ( previous . literal . get_type ( ) ) ) ) ;
}
current_class - > icon_path = previous . literal ;
}
}
2017-01-08 05:04:53 +00:00
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : EXTENDS ) ) {
// Allow extends on the same line.
parse_extends ( ) ;
2020-07-16 01:02:44 +00:00
end_statement ( " superclass " ) ;
2020-05-01 22:14:56 +00:00
} else {
end_statement ( " class_name statement " ) ;
}
}
2017-01-08 05:04:53 +00:00
2020-05-01 22:14:56 +00:00
void GDScriptParser : : parse_extends ( ) {
current_class - > extends_used = true ;
2017-01-08 05:04:53 +00:00
2020-07-06 15:24:24 +00:00
int chain_index = 0 ;
2020-05-01 22:14:56 +00: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 17:28:34 +00:00
2020-05-01 22:14:56 +00:00
if ( ! match ( GDScriptTokenizer : : Token : : PERIOD ) ) {
return ;
}
}
2017-01-08 05:04:53 +00:00
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_INHERIT_TYPE , current_class , chain_index + + ) ;
2020-05-01 22:14:56 +00:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected superclass name after " extends " .) " ) ) {
return ;
}
current_class - > extends . push_back ( previous . literal ) ;
2017-01-08 05:04:53 +00:00
2020-05-01 22:14:56 +00:00
while ( match ( GDScriptTokenizer : : Token : : PERIOD ) ) {
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_INHERIT_TYPE , current_class , chain_index + + ) ;
2020-05-01 22:14:56 +00:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected superclass name after " . " .) " ) ) {
return ;
}
current_class - > extends . push_back ( previous . literal ) ;
}
}
2017-02-16 13:29:18 +00:00
2020-05-01 22:14:56 +00:00
template < class T >
void GDScriptParser : : parse_class_member ( T * ( GDScriptParser : : * p_parse_function ) ( ) , AnnotationInfo : : TargetKind p_target , const String & p_member_kind ) {
advance ( ) ;
T * member = ( this - > * p_parse_function ) ( ) ;
if ( member = = nullptr ) {
return ;
}
2020-11-29 02:37:57 +00:00
# ifdef TOOLS_ENABLED
int doc_comment_line = member - > start_line - 1 ;
# endif // TOOLS_ENABLED
2020-05-01 22:14:56 +00:00
// Consume annotations.
2020-12-15 12:04:21 +00:00
while ( ! annotation_stack . is_empty ( ) ) {
2020-05-01 22:14:56 +00:00
AnnotationNode * last_annotation = annotation_stack . back ( ) - > get ( ) ;
if ( last_annotation - > applies_to ( p_target ) ) {
member - > annotations . push_front ( last_annotation ) ;
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 ( ) ;
return ;
}
2020-11-29 02:37:57 +00:00
# ifdef TOOLS_ENABLED
if ( last_annotation - > start_line = = doc_comment_line ) {
doc_comment_line - - ;
}
# endif // TOOLS_ENABLED
2020-05-01 22:14:56 +00:00
}
2020-11-29 02:37:57 +00:00
# ifdef TOOLS_ENABLED
// Consume doc comments.
2020-11-29 03:42:06 +00:00
class_doc_line = MIN ( class_doc_line , doc_comment_line - 1 ) ;
2020-11-29 02:37:57 +00:00
if ( has_comment ( doc_comment_line ) ) {
if constexpr ( std : : is_same_v < T , ClassNode > ) {
get_class_doc_comment ( doc_comment_line , member - > doc_brief_description , member - > doc_description , member - > doc_tutorials , true ) ;
} else {
member - > doc_description = get_doc_comment ( doc_comment_line ) ;
}
}
# endif // TOOLS_ENABLED
2020-05-01 22:14:56 +00:00
if ( member - > identifier ! = nullptr ) {
// Enums may be unnamed.
// TODO: Consider names in outer scope too, for constants and classes (and static functions?)
if ( current_class - > members_indices . has ( member - > identifier - > name ) ) {
2020-07-16 01:02:44 +00:00
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 ) ;
2020-05-01 22:14:56 +00:00
} else {
current_class - > add_member ( member ) ;
}
}
}
2017-02-16 13:29:18 +00:00
2021-09-21 17:13:23 +00:00
void GDScriptParser : : parse_class_body ( bool p_is_multiline ) {
2020-05-01 22:14:56 +00:00
bool class_end = false ;
while ( ! class_end & & ! is_at_end ( ) ) {
switch ( current . type ) {
case GDScriptTokenizer : : Token : : VAR :
parse_class_member ( & GDScriptParser : : parse_variable , AnnotationInfo : : VARIABLE , " variable " ) ;
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 : : STATIC :
case GDScriptTokenizer : : Token : : FUNC :
parse_class_member ( & GDScriptParser : : parse_function , AnnotationInfo : : FUNCTION , " function " ) ;
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 ;
case GDScriptTokenizer : : Token : : ANNOTATION : {
advance ( ) ;
AnnotationNode * annotation = parse_annotation ( AnnotationInfo : : CLASS_LEVEL ) ;
if ( annotation ! = nullptr ) {
annotation_stack . push_back ( annotation ) ;
2017-02-16 13:29:18 +00:00
}
2020-05-01 22:14:56 +00:00
break ;
2016-01-24 22:45:11 +00:00
}
2020-05-01 22:14:56 +00:00
case GDScriptTokenizer : : Token : : PASS :
2020-07-16 01:02:44 +00:00
advance ( ) ;
2020-05-01 22:14:56 +00:00
end_statement ( R " ( " pass " ) " ) ;
break ;
case GDScriptTokenizer : : Token : : DEDENT :
class_end = true ;
break ;
default :
push_error ( vformat ( R " (Unexpected " % s " in class body.) " , current . get_name ( ) ) ) ;
advance ( ) ;
break ;
}
if ( panic_mode ) {
synchronize ( ) ;
}
2021-09-21 17:13:23 +00:00
if ( ! p_is_multiline ) {
class_end = true ;
}
2020-05-01 22:14:56 +00:00
}
}
2017-02-16 13:29:18 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : VariableNode * GDScriptParser : : parse_variable ( ) {
2020-06-01 19:41:05 +00:00
return parse_variable ( true ) ;
}
GDScriptParser : : VariableNode * GDScriptParser : : parse_variable ( bool p_allow_property ) {
2020-05-01 22:14:56 +00:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected variable name after " var " .) " ) ) {
return nullptr ;
}
2017-02-16 13:29:18 +00:00
2020-05-01 22:14:56 +00:00
VariableNode * variable = alloc_node < VariableNode > ( ) ;
variable - > identifier = parse_identifier ( ) ;
2021-03-24 17:08:34 +00:00
variable - > export_info . name = variable - > identifier - > name ;
2014-12-07 05:04:20 +00:00
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : COLON ) ) {
2020-06-01 19:41:05 +00:00
if ( check ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
if ( p_allow_property ) {
advance ( ) ;
return parse_property ( variable , true ) ;
} else {
push_error ( R " (Expected type after " : " ) " ) ;
return nullptr ;
}
} else if ( check ( ( GDScriptTokenizer : : Token : : EQUAL ) ) ) {
2020-05-01 22:14:56 +00:00
// Infer type.
variable - > infer_datatype = true ;
} else {
2020-07-06 15:24:24 +00: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 19:41:05 +00:00
}
}
2020-05-01 22:14:56 +00:00
// Parse type.
variable - > datatype_specifier = parse_type ( ) ;
}
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : EQUAL ) ) {
// Initializer.
variable - > initializer = parse_expression ( false ) ;
2021-03-17 14:09:57 +00:00
if ( variable - > initializer = = nullptr ) {
push_error ( R " (Expected expression for variable initial value after " = " .) " ) ;
}
2020-06-11 22:31:28 +00:00
variable - > assignments + + ;
2020-05-01 22:14:56 +00:00
}
2018-05-30 02:16:54 +00:00
2020-06-01 19:41:05 +00: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 ) ;
}
}
2020-05-01 22:14:56 +00:00
end_statement ( " variable declaration " ) ;
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
return variable ;
}
2014-02-10 01:10:30 +00:00
2020-06-01 19:41:05 +00: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 " : " .) " ) ) {
return nullptr ;
}
}
2020-07-06 15:24:24 +00:00
VariableNode * property = p_variable ;
make_completion_context ( COMPLETION_PROPERTY_DECLARATION , property ) ;
2020-06-01 19:41:05 +00:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected " get " or " set " for property declaration.) " ) ) {
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 + + ) {
if ( function - > name = = " set " ) {
if ( setter_used ) {
push_error ( R " (Properties can only have one setter.) " ) ;
} else {
parse_property_setter ( property ) ;
setter_used = true ;
}
} else if ( function - > name = = " get " ) {
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 ( ) ;
}
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 ) {
case VariableNode : : PROP_INLINE :
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected " ( " after " set " .) " ) ;
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected parameter name after " ( " .) " ) ) {
p_variable - > setter_parameter = parse_identifier ( ) ;
}
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected " ) " after parameter name.)* " ) ;
consume ( GDScriptTokenizer : : Token : : COLON , R " *(Expected " : " after " ) " .)* " ) ;
p_variable - > setter = parse_suite ( " setter definition " ) ;
break ;
case VariableNode : : PROP_SETGET :
consume ( GDScriptTokenizer : : Token : : EQUAL , R " (Expected " = " after " set " ) " ) ;
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_PROPERTY_METHOD , p_variable ) ;
2020-06-01 19:41:05 +00: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 ) {
case VariableNode : : PROP_INLINE :
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " get " .) " ) ;
p_variable - > getter = parse_suite ( " getter definition " ) ;
break ;
case VariableNode : : PROP_SETGET :
consume ( GDScriptTokenizer : : Token : : EQUAL , R " (Expected " = " after " get " ) " ) ;
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_PROPERTY_METHOD , p_variable ) ;
2020-06-01 19:41:05 +00: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.
}
}
2020-05-01 22:14:56 +00:00
GDScriptParser : : ConstantNode * GDScriptParser : : parse_constant ( ) {
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected constant name after " const " .) " ) ) {
return nullptr ;
}
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
ConstantNode * constant = alloc_node < ConstantNode > ( ) ;
constant - > identifier = parse_identifier ( ) ;
2014-09-15 14:33:30 +00:00
2020-05-01 22:14:56 +00: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 14:33:30 +00:00
2020-05-01 22:14:56 +00:00
if ( consume ( GDScriptTokenizer : : Token : : EQUAL , R " (Expected initializer after constant name.) " ) ) {
// Initializer.
constant - > initializer = parse_expression ( false ) ;
2014-09-15 14:33:30 +00:00
2020-05-01 22:14:56 +00:00
if ( constant - > initializer = = nullptr ) {
push_error ( R " (Expected initializer expression for constant.) " ) ;
return nullptr ;
}
2020-12-30 17:57:46 +00:00
} else {
return nullptr ;
2020-05-01 22:14:56 +00:00
}
2014-09-15 14:33:30 +00:00
2020-05-01 22:14:56 +00:00
end_statement ( " constant declaration " ) ;
2016-10-03 18:18:21 +00:00
2020-05-01 22:14:56 +00:00
return constant ;
}
2016-10-03 18:18:21 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ParameterNode * GDScriptParser : : parse_parameter ( ) {
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected parameter name.) " ) ) {
return nullptr ;
}
2014-09-15 14:33:30 +00:00
2020-05-01 22:14:56 +00:00
ParameterNode * parameter = alloc_node < ParameterNode > ( ) ;
parameter - > identifier = parse_identifier ( ) ;
2014-09-15 14:33:30 +00:00
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : COLON ) ) {
if ( check ( ( GDScriptTokenizer : : Token : : EQUAL ) ) ) {
// Infer type.
parameter - > infer_datatype = true ;
} else {
// Parse type.
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_TYPE_NAME , parameter ) ;
2020-05-01 22:14:56 +00:00
parameter - > datatype_specifier = parse_type ( ) ;
}
}
2016-08-07 01:11:03 +00:00
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : EQUAL ) ) {
// Default value.
parameter - > default_value = parse_expression ( false ) ;
}
2014-09-15 14:33:30 +00:00
2020-05-01 22:14:56 +00:00
return parameter ;
}
2014-09-15 14:33:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : SignalNode * GDScriptParser : : parse_signal ( ) {
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected signal name after " signal " .) " ) ) {
return nullptr ;
}
2016-10-03 18:18:21 +00:00
2020-05-01 22:14:56 +00:00
SignalNode * signal = alloc_node < SignalNode > ( ) ;
signal - > identifier = parse_identifier ( ) ;
2014-09-15 14:33:30 +00:00
2021-09-21 16:40:39 +00:00
if ( check ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN ) ) {
push_multiline ( true ) ;
advance ( ) ;
2020-08-19 14:32:48 +00:00
do {
if ( check ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE ) ) {
// Allow for trailing comma.
break ;
}
2020-05-01 22:14:56 +00:00
ParameterNode * parameter = parse_parameter ( ) ;
if ( parameter = = nullptr ) {
2020-08-19 14:32:48 +00:00
push_error ( " Expected signal parameter name. " ) ;
2020-05-01 22:14:56 +00:00
break ;
2014-09-15 14:33:30 +00:00
}
2020-05-01 22:14:56 +00:00
if ( parameter - > default_value ! = nullptr ) {
push_error ( R " (Signal parameters cannot have a default value.) " ) ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-08-19 14:32:48 +00:00
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) & & ! is_at_end ( ) ) ;
2021-09-21 16:40:39 +00:00
pop_multiline ( ) ;
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected closing " ) " after signal parameters.)* " ) ;
}
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00:00
end_statement ( " signal declaration " ) ;
2018-01-18 21:03:34 +00:00
2020-05-01 22:14:56 +00:00
return signal ;
}
2018-01-18 21:03:34 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : EnumNode * GDScriptParser : : parse_enum ( ) {
EnumNode * enum_node = alloc_node < EnumNode > ( ) ;
bool named = false ;
2018-01-18 21:03:34 +00:00
2020-05-01 22:14:56 +00:00
if ( check ( GDScriptTokenizer : : Token : : IDENTIFIER ) ) {
advance ( ) ;
enum_node - > identifier = parse_identifier ( ) ;
named = true ;
}
2018-01-18 21:03:34 +00:00
2020-05-01 22:14:56 +00:00
push_multiline ( true ) ;
consume ( GDScriptTokenizer : : Token : : BRACE_OPEN , vformat ( R " (Expected " { " after %s.) " , named ? " enum name " : R " ( " enum " ) " ) ) ;
2014-02-10 01:10:30 +00:00
2020-08-19 14:14:16 +00:00
HashMap < StringName , int > elements ;
2020-05-01 22:14:56 +00:00
do {
if ( check ( GDScriptTokenizer : : Token : : BRACE_CLOSE ) ) {
break ; // Allow trailing comma.
}
2020-09-18 11:35:51 +00:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected identifier for enum key.) " ) ) {
2020-05-01 22:14:56 +00:00
EnumNode : : Value item ;
item . identifier = parse_identifier ( ) ;
2020-08-18 20:44:20 +00:00
item . parent_enum = enum_node ;
item . line = previous . start_line ;
item . leftmost_column = previous . leftmost_column ;
2020-11-29 02:37:57 +00:00
item . rightmost_column = previous . rightmost_column ;
2020-05-01 22:14:56 +00:00
2020-08-19 14:14:16 +00: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 ) {
2020-05-01 22:14:56 +00:00
// TODO: Abstract this recursive member check.
ClassNode * parent = current_class ;
while ( parent ! = nullptr ) {
if ( parent - > members_indices . has ( item . identifier - > name ) ) {
push_error ( vformat ( R " (Name " % s " is already used as a class %s.) " , item . identifier - > name , parent - > get_member ( item . identifier - > name ) . get_type_name ( ) ) ) ;
break ;
2018-09-10 13:31:36 +00:00
}
2020-05-01 22:14:56 +00:00
parent = parent - > outer ;
2018-01-18 21:03:34 +00:00
}
}
2014-12-17 01:31:57 +00:00
2020-08-19 14:14:16 +00:00
elements [ item . identifier - > name ] = item . line ;
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : EQUAL ) ) {
2020-08-18 20:44:20 +00:00
ExpressionNode * value = parse_expression ( false ) ;
if ( value = = nullptr ) {
push_error ( R " (Expected expression value after " = " .) " ) ;
2017-11-17 04:42:24 +00:00
}
2020-08-18 20:44:20 +00:00
item . custom_value = value ;
2020-05-01 22:14:56 +00:00
}
2020-08-18 20:44:20 +00:00
item . index = enum_node - > values . size ( ) ;
2020-05-01 22:14:56 +00:00
enum_node - > values . push_back ( item ) ;
2020-06-12 00:49:58 +00:00
if ( ! named ) {
2020-05-01 22:14:56 +00:00
// Add as member of current class.
current_class - > add_member ( item ) ;
}
}
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : BRACE_CLOSE , R " (Expected closing " } " for enum.) " ) ;
2014-02-10 01:10:30 +00:00
2020-11-29 02:37:57 +00:00
# ifdef TOOLS_ENABLED
2021-05-20 10:07:26 +00:00
// Enum values documentation.
2020-11-29 02:37:57 +00:00
for ( int i = 0 ; i < enum_node - > values . size ( ) ; i + + ) {
if ( i = = enum_node - > values . size ( ) - 1 ) {
// If close bracket is same line as last value.
if ( enum_node - > values [ i ] . line ! = previous . start_line & & has_comment ( enum_node - > values [ i ] . line ) ) {
if ( named ) {
enum_node - > values . write [ i ] . doc_description = get_doc_comment ( enum_node - > values [ i ] . line , true ) ;
} else {
current_class - > set_enum_value_doc ( enum_node - > values [ i ] . identifier - > name , get_doc_comment ( enum_node - > values [ i ] . line , true ) ) ;
}
}
} else {
// If two values are same line.
if ( enum_node - > values [ i ] . line ! = enum_node - > values [ i + 1 ] . line & & has_comment ( enum_node - > values [ i ] . line ) ) {
if ( named ) {
enum_node - > values . write [ i ] . doc_description = get_doc_comment ( enum_node - > values [ i ] . line , true ) ;
} else {
current_class - > set_enum_value_doc ( enum_node - > values [ i ] . identifier - > name , get_doc_comment ( enum_node - > values [ i ] . line , true ) ) ;
}
}
}
}
# endif // TOOLS_ENABLED
2020-05-01 22:14:56 +00:00
end_statement ( " enum " ) ;
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00:00
return enum_node ;
}
2014-02-10 01:10:30 +00:00
2021-03-25 13:36:29 +00:00
void GDScriptParser : : parse_function_signature ( FunctionNode * p_function , SuiteNode * p_body , const String & p_type ) {
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-05-01 22:14:56 +00:00
if ( parameter - > default_value ! = nullptr ) {
default_used = true ;
} else {
if ( default_used ) {
push_error ( " Cannot have a mandatory parameters after optional parameters. " ) ;
continue ;
2020-05-14 14:41:43 +00:00
}
2017-11-17 04:42:24 +00:00
}
2021-03-25 13:36:29 +00: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-01 22:14:56 +00:00
} else {
2021-03-25 13:36:29 +00:00
p_function - > parameters_indices [ parameter - > identifier - > name ] = p_function - > parameters . size ( ) ;
p_function - > parameters . push_back ( parameter ) ;
2021-03-26 12:03:16 +00:00
p_body - > add_local ( parameter , current_function ) ;
2014-12-17 01:31:57 +00:00
}
2020-05-01 22:14:56 +00:00
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
}
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00:00
pop_multiline ( ) ;
2021-03-25 13:36:29 +00:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , vformat ( R " *(Expected closing " ) " after %s parameters.)* " , p_type ) ) ;
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : FORWARD_ARROW ) ) {
2021-03-25 13:36:29 +00: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 17:08:52 +00:00
push_error ( R " (Expected return type or " void " after " - > " .) " ) ;
}
2020-05-01 22:14:56 +00:00
}
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00: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 13:36:29 +00:00
consume ( GDScriptTokenizer : : Token : : COLON , vformat ( R " (Expected " : " after %s declaration.) " , p_type ) ) ;
}
GDScriptParser : : FunctionNode * GDScriptParser : : parse_function ( ) {
bool _static = false ;
if ( previous . type = = GDScriptTokenizer : : Token : : STATIC ) {
// TODO: Improve message if user uses "static" with "var" or "const"
if ( ! consume ( GDScriptTokenizer : : Token : : FUNC , R " (Expected " func " after " static " .) " ) ) {
return nullptr ;
}
_static = true ;
}
FunctionNode * function = alloc_node < FunctionNode > ( ) ;
make_completion_context ( COMPLETION_OVERRIDE_METHOD , function ) ;
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected function name after " func " .) " ) ) {
return nullptr ;
}
FunctionNode * previous_function = current_function ;
current_function = function ;
function - > identifier = parse_identifier ( ) ;
function - > is_static = _static ;
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 16:38:35 +00:00
2020-06-10 21:18:10 +00:00
current_suite = previous_suite ;
function - > body = parse_suite ( " function declaration " , body ) ;
2019-01-17 21:17:06 +00:00
2020-05-01 22:14:56 +00:00
current_function = previous_function ;
return function ;
}
2019-03-05 21:19:02 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : AnnotationNode * GDScriptParser : : parse_annotation ( uint32_t p_valid_targets ) {
AnnotationNode * annotation = alloc_node < AnnotationNode > ( ) ;
2016-06-30 01:17:55 +00:00
2020-05-01 22:14:56 +00:00
annotation - > name = previous . literal ;
2014-02-10 01:10:30 +00:00
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_ANNOTATION , annotation ) ;
2020-05-01 22:14:56 +00:00
bool valid = true ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 ) ) {
push_error ( vformat ( R " (Annotation " % s " is not allowed in this level.) " , annotation - > name ) ) ;
valid = false ;
}
if ( match ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN ) ) {
// Arguments.
2020-07-06 15:24:24 +00:00
push_completion_call ( annotation ) ;
make_completion_context ( COMPLETION_ANNOTATION_ARGUMENTS , annotation , 0 , true ) ;
2020-05-01 22:14:56 +00:00
if ( ! check ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE ) & & ! is_at_end ( ) ) {
2020-07-16 01:02:44 +00:00
int argument_index = 0 ;
2020-05-01 22:14:56 +00:00
do {
2020-07-16 01:02:44 +00:00
make_completion_context ( COMPLETION_ANNOTATION_ARGUMENTS , annotation , argument_index , true ) ;
set_last_completion_call_arg ( argument_index + + ) ;
2020-05-01 22:14:56 +00:00
ExpressionNode * argument = parse_expression ( false ) ;
if ( argument = = nullptr ) {
valid = false ;
continue ;
2019-04-09 15:08:36 +00:00
}
2020-05-01 22:14:56 +00:00
annotation - > arguments . push_back ( argument ) ;
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected " ) " after annotation arguments.)* " ) ;
}
2020-07-06 15:24:24 +00:00
pop_completion_call ( ) ;
2020-05-01 22:14:56 +00:00
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
match ( GDScriptTokenizer : : Token : : NEWLINE ) ; // Newline after annotation is optional.
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( valid ) {
valid = validate_annotation_arguments ( annotation ) ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return valid ? annotation : nullptr ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
void GDScriptParser : : clear_unused_annotations ( ) {
2021-07-24 13:46:25 +00:00
for ( const AnnotationNode * annotation : annotation_stack ) {
2020-07-06 15:24:24 +00:00
push_error ( vformat ( R " (Annotation " % s " does not precedes a valid target, so it will have no effect.) " , annotation - > name ) , annotation ) ;
2020-05-01 22:14:56 +00:00
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
annotation_stack . clear ( ) ;
}
bool GDScriptParser : : register_annotation ( const MethodInfo & p_info , uint32_t p_target_kinds , AnnotationAction p_apply , int p_optional_arguments , bool p_is_vararg ) {
ERR_FAIL_COND_V_MSG ( valid_annotations . has ( p_info . name ) , false , vformat ( R " (Annotation " % s " already registered.) " , p_info . name ) ) ;
2018-08-26 16:31:23 +00:00
2020-05-01 22:14:56 +00:00
AnnotationInfo new_annotation ;
new_annotation . info = p_info ;
new_annotation . info . default_arguments . resize ( p_optional_arguments ) ;
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 16:31:23 +00:00
2020-05-01 22:14:56 +00:00
valid_annotations [ p_info . name ] = new_annotation ;
return true ;
}
2018-08-26 16:31:23 +00:00
2021-03-25 13:36:29 +00:00
GDScriptParser : : SuiteNode * GDScriptParser : : parse_suite ( const String & p_context , SuiteNode * p_suite , bool p_for_lambda ) {
2020-06-10 21:18:10 +00:00
SuiteNode * suite = p_suite ! = nullptr ? p_suite : alloc_node < SuiteNode > ( ) ;
2020-05-01 22:14:56 +00:00
suite - > parent_block = current_suite ;
2021-03-26 12:03:16 +00:00
suite - > parent_function = current_function ;
2020-05-01 22:14:56 +00:00
current_suite = suite ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
bool multiline = false ;
2014-02-10 01:10:30 +00:00
2021-03-25 13:36:29 +00:00
if ( match ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
2020-05-01 22:14:56 +00:00
multiline = true ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( multiline ) {
if ( ! consume ( GDScriptTokenizer : : Token : : INDENT , vformat ( R " (Expected indented block after %s.) " , p_context ) ) ) {
current_suite = suite - > parent_block ;
return suite ;
}
}
2021-03-25 13:36:29 +00:00
int error_count = 0 ;
2020-05-01 22:14:56 +00:00
do {
2021-09-21 17:13:23 +00:00
if ( ! multiline & & previous . type = = GDScriptTokenizer : : Token : : SEMICOLON & & check ( GDScriptTokenizer : : Token : : NEWLINE ) ) {
break ;
}
2020-05-01 22:14:56 +00:00
Node * statement = parse_statement ( ) ;
if ( statement = = nullptr ) {
2021-03-25 13:36:29 +00:00
if ( error_count + + > 100 ) {
push_error ( " Too many statement errors. " , suite ) ;
break ;
}
2020-05-01 22:14:56 +00: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 ) {
2020-06-10 21:18:10 +00:00
push_error ( vformat ( R " (There is already a %s named " % s " declared in this scope.) " , local . get_name ( ) , variable - > identifier - > name ) ) ;
2020-05-01 22:14:56 +00:00
}
2021-03-26 12:03:16 +00:00
current_suite - > add_local ( variable , current_function ) ;
2020-05-01 22:14:56 +00: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 14:41:43 +00:00
}
2020-05-01 22:14:56 +00:00
push_error ( vformat ( R " (There is already a %s named " % s " declared in this scope.) " , name , constant - > identifier - > name ) ) ;
2014-02-10 01:10:30 +00:00
}
2021-03-26 12:03:16 +00:00
current_suite - > add_local ( constant , current_function ) ;
2020-05-01 22:14:56 +00:00
break ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
default :
break ;
}
2014-02-10 01:10:30 +00:00
2021-09-21 17:13:23 +00:00
} while ( ( multiline | | previous . type = = GDScriptTokenizer : : Token : : SEMICOLON ) & & ! check ( GDScriptTokenizer : : Token : : DEDENT ) & & ! lambda_ended & & ! is_at_end ( ) ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( multiline ) {
2021-03-25 13:36:29 +00: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-01 22:14:56 +00:00
}
2014-02-10 01:10:30 +00:00
2021-03-25 13:36:29 +00:00
if ( p_for_lambda ) {
lambda_ended = true ;
}
2020-05-01 22:14:56 +00:00
current_suite = suite - > parent_block ;
return suite ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : Node * GDScriptParser : : parse_statement ( ) {
Node * result = nullptr ;
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-06-11 22:31:28 +00:00
bool unreachable = current_suite - > has_return & & ! current_suite - > has_unreachable_code ;
2020-07-16 01:02:44 +00:00
# endif
2020-06-11 22:31:28 +00:00
2020-05-01 22:14:56 +00:00
switch ( current . type ) {
case GDScriptTokenizer : : Token : : PASS :
advance ( ) ;
result = alloc_node < PassNode > ( ) ;
end_statement ( R " ( " pass " ) " ) ;
break ;
case GDScriptTokenizer : : Token : : VAR :
advance ( ) ;
result = parse_variable ( ) ;
break ;
case GDScriptTokenizer : : Token : : CONST :
advance ( ) ;
result = parse_constant ( ) ;
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 ( ) ) {
2020-08-26 19:08:19 +00:00
if ( current_function & & current_function - > identifier - > name = = GDScriptLanguage : : get_singleton ( ) - > strings . _init ) {
2020-07-16 01:02:44 +00:00
push_error ( R " (Constructor cannot return a value.) " ) ;
}
2020-05-01 22:14:56 +00:00
n_return - > return_value = parse_expression ( false ) ;
2021-03-25 13:36:29 +00: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-01 22:14:56 +00:00
}
result = n_return ;
2020-06-11 22:31:28 +00:00
current_suite - > has_return = true ;
2020-05-01 22:14:56 +00:00
end_statement ( " return statement " ) ;
break ;
}
case GDScriptTokenizer : : Token : : BREAKPOINT :
advance ( ) ;
result = alloc_node < BreakpointNode > ( ) ;
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 13:36:29 +00:00
bool has_ended_lambda = false ;
2020-06-01 19:41:05 +00:00
if ( expression = = nullptr ) {
2021-03-25 13:36:29 +00: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 {
push_error ( vformat ( R " (Expected statement, found " % s " instead.) " , previous . get_name ( ) ) ) ;
}
2020-06-01 19:41:05 +00:00
}
2020-05-01 22:14:56 +00:00
end_statement ( " expression " ) ;
2021-03-25 13:36:29 +00:00
lambda_ended = lambda_ended | | has_ended_lambda ;
2020-05-01 22:14:56 +00:00
result = expression ;
2020-06-11 22:31:28 +00:00
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-06-11 22:31:28 +00:00
if ( expression ! = nullptr ) {
switch ( expression - > type ) {
case Node : : CALL :
case Node : : ASSIGNMENT :
case Node : : AWAIT :
// Fine.
break ;
default :
push_warning ( expression , GDScriptWarning : : STANDALONE_EXPRESSION ) ;
}
}
2020-07-16 01:02:44 +00:00
# endif
2020-05-01 22:14:56 +00:00
break ;
}
}
2014-02-10 01:10:30 +00:00
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-11-13 14:09:52 +00:00
if ( unreachable & & result ! = nullptr ) {
2020-06-11 22:31:28 +00:00
current_suite - > has_unreachable_code = true ;
2020-10-31 01:21:50 +00:00
if ( current_function ) {
2021-03-25 13:36:29 +00:00
push_warning ( result , GDScriptWarning : : UNREACHABLE_CODE , current_function - > identifier ? current_function - > identifier - > name : " <anonymous lambda> " ) ;
2020-10-31 01:21:50 +00:00
} else {
// TODO: Properties setters and getters with unreachable code are not being warned
}
2020-06-11 22:31:28 +00:00
}
2020-07-16 01:02:44 +00:00
# endif
2020-06-11 22:31:28 +00:00
2020-05-01 22:14:56 +00:00
if ( panic_mode ) {
synchronize ( ) ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return result ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : AssertNode * GDScriptParser : : parse_assert ( ) {
// TODO: Add assert message.
AssertNode * assert = alloc_node < AssertNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected " ( " after " assert " .) " ) ;
assert - > condition = parse_expression ( false ) ;
if ( assert - > condition = = nullptr ) {
push_error ( " Expected expression to assert. " ) ;
return nullptr ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : COMMA ) ) {
// Error message.
2020-12-27 05:57:50 +00:00
assert - > message = parse_expression ( false ) ;
if ( assert - > message = = nullptr ) {
push_error ( R " (Expected error message for assert after " , " .) " ) ;
2020-05-01 22:14:56 +00:00
return nullptr ;
}
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected " ) " after assert expression.)* " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
end_statement ( R " ( " assert " ) " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return assert ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : BreakNode * GDScriptParser : : parse_break ( ) {
if ( ! can_break ) {
push_error ( R " (Cannot use " break " outside of a loop.) " ) ;
}
end_statement ( R " ( " break " ) " ) ;
return alloc_node < BreakNode > ( ) ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ContinueNode * GDScriptParser : : parse_continue ( ) {
if ( ! can_continue ) {
push_error ( R " (Cannot use " continue " outside of a loop or pattern matching block.) " ) ;
}
2020-06-11 22:31:28 +00:00
current_suite - > has_continue = true ;
2020-05-01 22:14:56 +00:00
end_statement ( R " ( " continue " ) " ) ;
2020-08-07 17:51:56 +00:00
ContinueNode * cont = alloc_node < ContinueNode > ( ) ;
cont - > is_for_match = is_continue_match ;
return cont ;
2020-05-01 22:14:56 +00:00
}
2016-11-26 12:40:13 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ForNode * GDScriptParser : : parse_for ( ) {
ForNode * n_for = alloc_node < ForNode > ( ) ;
2016-11-26 12:40:13 +00:00
2020-05-01 22:14:56 +00:00
if ( consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected loop variable name after " for " .) " ) ) {
n_for - > variable = parse_identifier ( ) ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : IN , R " (Expected " in " after " for " variable name.) " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
n_for - > list = parse_expression ( false ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " for " condition.) " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
// Save break/continue state.
bool could_break = can_break ;
bool could_continue = can_continue ;
2020-08-07 17:51:56 +00:00
bool was_continue_match = is_continue_match ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
// Allow break/continue.
can_break = true ;
can_continue = true ;
2020-08-07 17:51:56 +00:00
is_continue_match = false ;
2014-02-10 01:10:30 +00:00
2020-06-10 21:18:10 +00:00
SuiteNode * suite = alloc_node < SuiteNode > ( ) ;
2020-07-16 01:02:44 +00:00
if ( n_for - > variable ) {
2021-03-26 12:03:16 +00:00
suite - > add_local ( SuiteNode : : Local ( n_for - > variable , current_function ) ) ;
2020-07-16 01:02:44 +00:00
}
2020-07-06 15:24:24 +00:00
suite - > parent_for = n_for ;
2020-06-10 21:18:10 +00:00
n_for - > loop = parse_suite ( R " ( " for " block) " , suite ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
// Reset break/continue state.
can_break = could_break ;
can_continue = could_continue ;
2020-08-07 17:51:56 +00:00
is_continue_match = was_continue_match ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return n_for ;
}
2018-08-26 16:31:23 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : IfNode * GDScriptParser : : parse_if ( const String & p_token ) {
IfNode * n_if = alloc_node < IfNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : COLON , vformat ( R " (Expected " : " after " % s " condition.) " , p_token ) ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
n_if - > true_block = parse_suite ( vformat ( R " ( " % s " block) " , p_token ) ) ;
2020-07-06 15:24:24 +00:00
n_if - > true_block - > parent_if = n_if ;
2014-02-10 01:10:30 +00:00
2020-06-11 22:31:28 +00:00
if ( n_if - > true_block - > has_continue ) {
current_suite - > has_continue = true ;
}
2020-05-01 22:14:56 +00:00
if ( match ( GDScriptTokenizer : : Token : : ELIF ) ) {
IfNode * elif = parse_if ( " elif " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
SuiteNode * else_block = alloc_node < SuiteNode > ( ) ;
else_block - > statements . push_back ( elif ) ;
n_if - > false_block = else_block ;
} else if ( match ( GDScriptTokenizer : : Token : : ELSE ) ) {
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " else " .) " ) ;
n_if - > false_block = parse_suite ( R " ( " else " block) " ) ;
}
2014-02-10 01:10:30 +00:00
2020-06-11 22:31:28 +00: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-01 22:14:56 +00:00
return n_if ;
}
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : MatchNode * GDScriptParser : : parse_match ( ) {
MatchNode * match = alloc_node < MatchNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
match - > test = parse_expression ( false ) ;
if ( match - > test = = nullptr ) {
push_error ( R " (Expected expression to test after " match " .) " ) ;
}
2019-07-03 14:28:50 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " match " expression.) " ) ;
consume ( GDScriptTokenizer : : Token : : NEWLINE , R " (Expected a newline after " match " statement.) " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( ! consume ( GDScriptTokenizer : : Token : : INDENT , R " (Expected an indented block after " match " statement.) " ) ) {
return match ;
}
2014-12-17 01:31:57 +00:00
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-06-11 22:31:28 +00:00
bool all_have_return = true ;
bool have_wildcard = false ;
bool wildcard_has_return = false ;
bool have_wildcard_without_continue = false ;
2020-07-16 01:02:44 +00:00
# endif
2020-06-11 22:31:28 +00:00
2020-05-01 22:14:56 +00:00
while ( ! check ( GDScriptTokenizer : : Token : : DEDENT ) & & ! is_at_end ( ) ) {
MatchBranchNode * branch = parse_match_branch ( ) ;
if ( branch = = nullptr ) {
2021-08-24 19:27:17 +00:00
advance ( ) ;
2020-05-01 22:14:56 +00:00
continue ;
}
2020-06-11 22:31:28 +00:00
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
if ( have_wildcard_without_continue ) {
2020-06-11 22:31:28 +00:00
push_warning ( branch - > patterns [ 0 ] , GDScriptWarning : : UNREACHABLE_PATTERN ) ;
}
if ( branch - > has_wildcard ) {
have_wildcard = true ;
if ( branch - > block - > has_return ) {
wildcard_has_return = true ;
}
if ( ! branch - > block - > has_continue ) {
have_wildcard_without_continue = true ;
}
}
if ( ! branch - > block - > has_return ) {
all_have_return = false ;
}
2020-07-16 01:02:44 +00:00
# endif
2020-05-01 22:14:56 +00:00
match - > branches . push_back ( branch ) ;
}
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : DEDENT , R " (Expected an indented block after " match " statement.) " ) ;
2014-12-17 01:31:57 +00:00
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-06-11 22:31:28 +00:00
if ( wildcard_has_return | | ( all_have_return & & have_wildcard ) ) {
current_suite - > has_return = true ;
}
2020-07-16 01:02:44 +00:00
# endif
2020-06-11 22:31:28 +00:00
2020-05-01 22:14:56 +00:00
return match ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : MatchBranchNode * GDScriptParser : : parse_match_branch ( ) {
MatchBranchNode * branch = alloc_node < MatchBranchNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
bool has_bind = false ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
do {
PatternNode * pattern = parse_match_pattern ( ) ;
if ( pattern = = nullptr ) {
continue ;
}
if ( pattern - > pattern_type = = PatternNode : : PT_BIND ) {
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-11 22:31:28 +00:00
} else if ( pattern - > pattern_type = = PatternNode : : PT_BIND | | pattern - > pattern_type = = PatternNode : : PT_WILDCARD ) {
branch - > has_wildcard = true ;
2020-05-01 22:14:56 +00:00
}
branch - > patterns . push_back ( pattern ) ;
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
2014-02-10 01:10:30 +00:00
2020-12-15 12:04:21 +00:00
if ( branch - > patterns . is_empty ( ) ) {
2020-05-01 22:14:56 +00:00
push_error ( R " (No pattern found for " match " branch.) " ) ;
}
2014-02-10 01:10:30 +00:00
2021-08-25 13:42:48 +00:00
if ( ! consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " match " patterns.) " ) ) {
2021-08-24 19:27:17 +00:00
return nullptr ;
2021-08-25 13:42:48 +00:00
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
// Save continue state.
bool could_continue = can_continue ;
2020-08-07 17:51:56 +00:00
bool was_continue_match = is_continue_match ;
2020-05-01 22:14:56 +00:00
// Allow continue for match.
can_continue = true ;
2020-08-07 17:51:56 +00:00
is_continue_match = true ;
2014-02-10 01:10:30 +00:00
2020-06-10 21:18:10 +00:00
SuiteNode * suite = alloc_node < SuiteNode > ( ) ;
if ( branch - > patterns . size ( ) > 0 ) {
List < StringName > binds ;
branch - > patterns [ 0 ] - > binds . get_key_list ( & binds ) ;
2021-07-24 13:46:25 +00:00
for ( const StringName & E : binds ) {
2021-07-16 03:45:57 +00:00
SuiteNode : : Local local ( branch - > patterns [ 0 ] - > binds [ E ] , current_function ) ;
2020-06-10 21:18:10 +00:00
suite - > add_local ( local ) ;
}
}
branch - > block = parse_suite ( " match pattern block " , suite ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
// Restore continue state.
can_continue = could_continue ;
2020-08-07 17:51:56 +00:00
is_continue_match = was_continue_match ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return branch ;
}
2014-02-10 01:10:30 +00:00
2020-06-10 21:18:10 +00:00
GDScriptParser : : PatternNode * GDScriptParser : : parse_match_pattern ( PatternNode * p_root_pattern ) {
2020-05-01 22:14:56 +00:00
PatternNode * pattern = alloc_node < PatternNode > ( ) ;
2018-05-30 02:16:51 +00:00
2020-05-01 22:14:56 +00:00
switch ( current . type ) {
2020-06-10 21:18:10 +00:00
case GDScriptTokenizer : : Token : : VAR : {
2020-05-01 22:14:56 +00:00
// Bind.
advance ( ) ;
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected bind name after " var " .) " ) ) {
2020-04-01 23:20:12 +00:00
return nullptr ;
2018-05-30 02:16:51 +00:00
}
2020-05-01 22:14:56 +00:00
pattern - > pattern_type = PatternNode : : PT_BIND ;
pattern - > bind = parse_identifier ( ) ;
2020-06-10 21:18:10 +00: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 ) ) ;
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 ) ) ;
return nullptr ;
}
root_pattern - > binds [ pattern - > bind - > name ] = pattern - > bind ;
} break ;
2020-05-01 22:14:56 +00: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.
advance ( ) ;
pattern - > pattern_type = PatternNode : : PT_ARRAY ;
if ( ! check ( GDScriptTokenizer : : Token : : BRACKET_CLOSE ) ) {
do {
2020-06-10 21:18:10 +00:00
PatternNode * sub_pattern = parse_match_pattern ( p_root_pattern ! = nullptr ? p_root_pattern : pattern ) ;
2020-05-01 22:14:56 +00:00
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 ) ) ;
}
consume ( GDScriptTokenizer : : Token : : BRACKET_CLOSE , R " (Expected " ] " to close the array pattern.) " ) ;
break ;
2018-05-30 02:16:51 +00:00
}
2020-05-01 22:14:56 +00:00
case GDScriptTokenizer : : Token : : BRACE_OPEN : {
// Dictionary.
advance ( ) ;
pattern - > pattern_type = PatternNode : : PT_DICTIONARY ;
2021-08-24 19:27:17 +00: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 > ( ) ;
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-01 22:14:56 +00:00
if ( pattern - > rest_used ) {
push_error ( R " (The " . . " pattern must be the last element in the pattern dictionary.) " ) ;
2021-08-24 19:27:17 +00:00
} else if ( sub_pattern - > pattern_type = = PatternNode : : PT_REST ) {
push_error ( R " (The " . . " pattern cannot be used as a value.) " ) ;
2020-05-01 22:14:56 +00:00
} else {
2021-08-24 19:27:17 +00:00
pattern - > dictionary . push_back ( { key , sub_pattern } ) ;
2020-05-01 22:14:56 +00:00
}
} else {
2021-08-24 19:27:17 +00:00
// Key match only.
pattern - > dictionary . push_back ( { key , nullptr } ) ;
2020-05-01 22:14:56 +00:00
}
2021-08-24 19:27:17 +00:00
}
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : BRACE_CLOSE , R " (Expected " } " to close the dictionary pattern.) " ) ;
break ;
}
default : {
// Expression.
ExpressionNode * expression = parse_expression ( false ) ;
if ( expression = = nullptr ) {
push_error ( R " (Expected expression for match pattern.) " ) ;
2021-08-24 19:27:17 +00:00
return nullptr ;
2020-05-01 22:14:56 +00:00
} else {
2021-08-24 19:27:17 +00:00
if ( expression - > type = = GDScriptParser : : Node : : LITERAL ) {
pattern - > pattern_type = PatternNode : : PT_LITERAL ;
} else {
pattern - > pattern_type = PatternNode : : PT_EXPRESSION ;
}
2020-05-01 22:14:56 +00:00
pattern - > expression = expression ;
2014-04-05 21:50:09 +00:00
}
2020-05-01 22:14:56 +00:00
break ;
2014-04-05 21:50:09 +00:00
}
2020-05-01 22:14:56 +00:00
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return pattern ;
}
2014-02-10 01:10:30 +00:00
2020-06-10 21:18:10 +00: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-01 22:14:56 +00:00
GDScriptParser : : WhileNode * GDScriptParser : : parse_while ( ) {
WhileNode * n_while = alloc_node < WhileNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
n_while - > condition = parse_expression ( false ) ;
if ( n_while - > condition = = nullptr ) {
push_error ( R " (Expected conditional expression after " while " .) " ) ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : COLON , R " (Expected " : " after " while " condition.) " ) ;
2017-08-24 03:06:56 +00:00
2020-05-01 22:14:56 +00:00
// Save break/continue state.
bool could_break = can_break ;
bool could_continue = can_continue ;
2020-08-07 17:51:56 +00:00
bool was_continue_match = is_continue_match ;
2017-03-05 15:44:50 +00:00
2020-05-01 22:14:56 +00:00
// Allow break/continue.
can_break = true ;
can_continue = true ;
2020-08-07 17:51:56 +00:00
is_continue_match = false ;
2017-08-24 03:06:56 +00:00
2020-05-01 22:14:56 +00:00
n_while - > loop = parse_suite ( R " ( " while " block) " ) ;
// Reset break/continue state.
can_break = could_break ;
can_continue = could_continue ;
2020-08-07 17:51:56 +00:00
is_continue_match = was_continue_match ;
2020-05-01 22:14:56 +00:00
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 01:02:44 +00:00
// Completion can appear whenever an expression is expected.
make_completion_context ( COMPLETION_IDENTIFIER , nullptr ) ;
2021-03-25 13:36:29 +00:00
GDScriptTokenizer : : Token token = current ;
2020-05-01 22:14:56 +00:00
ParseFunction prefix_rule = get_rule ( token . type ) - > prefix ;
if ( prefix_rule = = nullptr ) {
// Expected expression. Let the caller give the proper error message.
return nullptr ;
}
2021-03-25 13:36:29 +00:00
advance ( ) ; // Only consume the token if there's a valid rule.
2020-05-01 22:14:56 +00:00
ExpressionNode * previous_operand = ( this - > * prefix_rule ) ( nullptr , p_can_assign ) ;
while ( p_precedence < = get_rule ( current . type ) - > precedence ) {
if ( p_stop_on_assign & & current . type = = GDScriptTokenizer : : Token : : EQUAL ) {
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 11:00:47 +00:00
break ;
default :
2020-05-01 22:14:56 +00:00
break ; // Nothing to do.
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
token = advance ( ) ;
ParseFunction infix_rule = get_rule ( token . type ) - > infix ;
previous_operand = ( this - > * infix_rule ) ( previous_operand , p_can_assign ) ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
return previous_operand ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : IdentifierNode * GDScriptParser : : parse_identifier ( ) {
return static_cast < IdentifierNode * > ( parse_identifier ( nullptr , false ) ) ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_identifier ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
if ( ! previous . is_identifier ( ) ) {
ERR_FAIL_V_MSG ( nullptr , " Parser bug: parsing literal node without literal token. " ) ;
}
IdentifierNode * identifier = alloc_node < IdentifierNode > ( ) ;
2020-06-01 19:41:05 +00:00
identifier - > name = previous . get_identifier ( ) ;
2020-06-10 21:18:10 +00:00
if ( current_suite ! = nullptr & & current_suite - > has_local ( identifier - > name ) ) {
const SuiteNode : : Local & declaration = current_suite - > get_local ( identifier - > name ) ;
2021-03-26 12:03:16 +00:00
identifier - > source_function = declaration . source_function ;
2020-06-10 21:18:10 +00:00
switch ( declaration . type ) {
case SuiteNode : : Local : : CONSTANT :
identifier - > source = IdentifierNode : : LOCAL_CONSTANT ;
identifier - > constant_source = declaration . constant ;
2020-06-11 22:31:28 +00:00
declaration . constant - > usages + + ;
2020-06-10 21:18:10 +00:00
break ;
case SuiteNode : : Local : : VARIABLE :
identifier - > source = IdentifierNode : : LOCAL_VARIABLE ;
identifier - > variable_source = declaration . variable ;
2020-06-11 22:31:28 +00:00
declaration . variable - > usages + + ;
2020-06-10 21:18:10 +00:00
break ;
case SuiteNode : : Local : : PARAMETER :
identifier - > source = IdentifierNode : : FUNCTION_PARAMETER ;
identifier - > parameter_source = declaration . parameter ;
2020-06-11 22:31:28 +00:00
declaration . parameter - > usages + + ;
2020-06-10 21:18:10 +00:00
break ;
case SuiteNode : : Local : : FOR_VARIABLE :
identifier - > source = IdentifierNode : : LOCAL_ITERATOR ;
identifier - > bind_source = declaration . bind ;
2020-06-11 22:31:28 +00:00
declaration . bind - > usages + + ;
2020-06-10 21:18:10 +00:00
break ;
case SuiteNode : : Local : : PATTERN_BIND :
identifier - > source = IdentifierNode : : LOCAL_BIND ;
identifier - > bind_source = declaration . bind ;
2020-06-11 22:31:28 +00:00
declaration . bind - > usages + + ;
2020-06-10 21:18:10 +00:00
break ;
case SuiteNode : : Local : : UNDEFINED :
ERR_FAIL_V_MSG ( nullptr , " Undefined local found. " ) ;
}
}
2020-05-01 22:14:56 +00:00
return identifier ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : LiteralNode * GDScriptParser : : parse_literal ( ) {
return static_cast < LiteralNode * > ( parse_literal ( nullptr , false ) ) ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
LiteralNode * literal = alloc_node < LiteralNode > ( ) ;
literal - > value = previous . literal ;
return literal ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_self ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
2020-08-31 13:01:45 +00:00
if ( current_function & & current_function - > is_static ) {
push_error ( R " (Cannot use " self " inside a static function.) " ) ;
2020-07-16 01:02:44 +00:00
}
2021-03-25 13:36:29 +00:00
if ( in_lambda ) {
push_error ( R " (Cannot use " self " inside a lambda.) " ) ;
}
2020-05-01 22:14:56 +00:00
SelfNode * self = alloc_node < SelfNode > ( ) ;
self - > current_class = current_class ;
return self ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 08:40:31 +00:00
constant - > value = INFINITY ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : CONST_NAN :
2021-07-21 08:40:31 +00:00
constant - > value = NAN ;
2020-05-01 22:14:56 +00:00
break ;
default :
return nullptr ; // Unreachable.
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return constant ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
switch ( op_type ) {
case GDScriptTokenizer : : Token : : MINUS :
operation - > operation = UnaryOpNode : : OP_NEGATIVE ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_NEGATE ;
2020-05-01 22:14:56 +00:00
operation - > operand = parse_precedence ( PREC_SIGN , false ) ;
2021-09-15 14:08:59 +00:00
if ( operation - > operand = = nullptr ) {
push_error ( R " (Expected expression after " - " operator.) " ) ;
}
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : PLUS :
operation - > operation = UnaryOpNode : : OP_POSITIVE ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_POSITIVE ;
2020-05-01 22:14:56 +00:00
operation - > operand = parse_precedence ( PREC_SIGN , false ) ;
2021-09-15 14:08:59 +00:00
if ( operation - > operand = = nullptr ) {
push_error ( R " (Expected expression after " + " operator.) " ) ;
}
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : TILDE :
operation - > operation = UnaryOpNode : : OP_COMPLEMENT ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_BIT_NEGATE ;
2020-05-01 22:14:56 +00:00
operation - > operand = parse_precedence ( PREC_BIT_NOT , false ) ;
2021-09-15 14:08:59 +00:00
if ( operation - > operand = = nullptr ) {
push_error ( R " (Expected expression after " ~ " operator.) " ) ;
}
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : NOT :
case GDScriptTokenizer : : Token : : BANG :
operation - > operation = UnaryOpNode : : OP_LOGIC_NOT ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_NOT ;
2020-05-01 22:14:56 +00:00
operation - > operand = parse_precedence ( PREC_LOGIC_NOT , false ) ;
2021-09-15 14:08:59 +00:00
if ( operation - > operand = = nullptr ) {
push_error ( vformat ( R " (Expected expression after " % s " operator.) " , op_type = = GDScriptTokenizer : : Token : : NOT ? " not " : " ! " ) ) ;
}
2020-05-01 22:14:56 +00:00
break ;
default :
return nullptr ; // Unreachable.
}
2016-08-25 18:18:35 +00:00
2020-05-01 22:14:56 +00:00
return operation ;
}
2014-02-10 01:10:30 +00:00
2020-09-01 07:39:17 +00: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
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 ) ;
UnaryOpNode * operation = alloc_node < UnaryOpNode > ( ) ;
operation - > operation = UnaryOpNode : : OP_LOGIC_NOT ;
operation - > variant_op = Variant : : OP_NOT ;
operation - > operand = in_operation ;
return operation ;
}
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_binary_operator ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
GDScriptTokenizer : : Token op = previous ;
BinaryOpNode * operation = alloc_node < BinaryOpNode > ( ) ;
2017-03-05 15:44:50 +00:00
2020-05-01 22:14:56 +00:00
Precedence precedence = ( Precedence ) ( get_rule ( op . type ) - > precedence + 1 ) ;
operation - > left_operand = p_previous_operand ;
operation - > right_operand = parse_precedence ( precedence , false ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( operation - > right_operand = = nullptr ) {
push_error ( vformat ( R " (Expected expression after " % s " operator. " ) " , op.get_name()));
}
2014-02-10 01:10:30 +00:00
2020-06-10 21:18:10 +00:00
// TODO: Also for unary, ternary, and assignment.
2020-05-01 22:14:56 +00:00
switch ( op . type ) {
case GDScriptTokenizer : : Token : : PLUS :
operation - > operation = BinaryOpNode : : OP_ADDITION ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_ADD ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : MINUS :
operation - > operation = BinaryOpNode : : OP_SUBTRACTION ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_SUBTRACT ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : STAR :
operation - > operation = BinaryOpNode : : OP_MULTIPLICATION ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_MULTIPLY ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : SLASH :
operation - > operation = BinaryOpNode : : OP_DIVISION ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_DIVIDE ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : PERCENT :
operation - > operation = BinaryOpNode : : OP_MODULO ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_MODULE ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : LESS_LESS :
operation - > operation = BinaryOpNode : : OP_BIT_LEFT_SHIFT ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_SHIFT_LEFT ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : GREATER_GREATER :
operation - > operation = BinaryOpNode : : OP_BIT_RIGHT_SHIFT ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_SHIFT_RIGHT ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : AMPERSAND :
operation - > operation = BinaryOpNode : : OP_BIT_AND ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_BIT_AND ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : PIPE :
2020-06-10 21:18:10 +00:00
operation - > operation = BinaryOpNode : : OP_BIT_OR ;
operation - > variant_op = Variant : : OP_BIT_OR ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : CARET :
operation - > operation = BinaryOpNode : : OP_BIT_XOR ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_BIT_XOR ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : AND :
case GDScriptTokenizer : : Token : : AMPERSAND_AMPERSAND :
operation - > operation = BinaryOpNode : : OP_LOGIC_AND ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_AND ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : OR :
case GDScriptTokenizer : : Token : : PIPE_PIPE :
operation - > operation = BinaryOpNode : : OP_LOGIC_OR ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_OR ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : IS :
operation - > operation = BinaryOpNode : : OP_TYPE_TEST ;
break ;
case GDScriptTokenizer : : Token : : IN :
operation - > operation = BinaryOpNode : : OP_CONTENT_TEST ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_IN ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : EQUAL_EQUAL :
operation - > operation = BinaryOpNode : : OP_COMP_EQUAL ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_EQUAL ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : BANG_EQUAL :
operation - > operation = BinaryOpNode : : OP_COMP_NOT_EQUAL ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_NOT_EQUAL ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : LESS :
operation - > operation = BinaryOpNode : : OP_COMP_LESS ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_LESS ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : LESS_EQUAL :
operation - > operation = BinaryOpNode : : OP_COMP_LESS_EQUAL ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_LESS_EQUAL ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : GREATER :
operation - > operation = BinaryOpNode : : OP_COMP_GREATER ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_GREATER ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : GREATER_EQUAL :
operation - > operation = BinaryOpNode : : OP_COMP_GREATER_EQUAL ;
2020-06-10 21:18:10 +00:00
operation - > variant_op = Variant : : OP_GREATER_EQUAL ;
2020-05-01 22:14:56 +00:00
break ;
default :
return nullptr ; // Unreachable.
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return operation ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 > ( ) ;
operation - > true_expr = p_previous_operand ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
operation - > condition = parse_precedence ( PREC_TERNARY , false ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( operation - > condition = = nullptr ) {
push_error ( R " (Expected expression as ternary condition after " if " .) " ) ;
}
2017-03-05 15:44:50 +00:00
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : ELSE , R " (Expected " else " after ternary operator condition.) " ) ;
2016-08-25 18:18:35 +00:00
2020-05-01 22:14:56 +00:00
operation - > false_expr = parse_precedence ( PREC_TERNARY , false ) ;
2016-08-25 18:18:35 +00:00
2021-09-15 14:43:36 +00:00
if ( operation - > false_expr = = nullptr ) {
push_error ( R " (Expected expression after " else " .) " ) ;
}
2020-05-01 22:14:56 +00:00
return operation ;
}
2016-08-25 18:18:35 +00:00
2020-05-01 22:14:56 +00: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.
}
2016-08-25 18:18:35 +00:00
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-06-11 22:31:28 +00:00
VariableNode * source_variable = nullptr ;
2020-07-16 01:02:44 +00:00
# endif
2020-06-11 22:31:28 +00:00
2020-05-01 22:14:56 +00:00
switch ( p_previous_operand - > type ) {
2020-06-11 22:31:28 +00:00
case Node : : IDENTIFIER : {
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-06-11 22:31:28 +00: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 :
2020-07-16 01:02:44 +00:00
2020-06-11 22:31:28 +00:00
source_variable = id - > variable_source ;
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 01:02:44 +00:00
# endif
} break ;
2020-05-01 22:14:56 +00: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 18:18:35 +00:00
2020-05-01 22:14:56 +00:00
AssignmentNode * assignment = alloc_node < AssignmentNode > ( ) ;
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_ASSIGN , assignment ) ;
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-06-11 22:31:28 +00:00
bool has_operator = true ;
2020-07-16 01:02:44 +00:00
# endif
2020-05-01 22:14:56 +00:00
switch ( previous . type ) {
case GDScriptTokenizer : : Token : : EQUAL :
assignment - > operation = AssignmentNode : : OP_NONE ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_MAX ;
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2020-06-11 22:31:28 +00:00
has_operator = false ;
2020-07-16 01:02:44 +00:00
# endif
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : PLUS_EQUAL :
assignment - > operation = AssignmentNode : : OP_ADDITION ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_ADD ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : MINUS_EQUAL :
assignment - > operation = AssignmentNode : : OP_SUBTRACTION ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_SUBTRACT ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : STAR_EQUAL :
assignment - > operation = AssignmentNode : : OP_MULTIPLICATION ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_MULTIPLY ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : SLASH_EQUAL :
assignment - > operation = AssignmentNode : : OP_DIVISION ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_DIVIDE ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : PERCENT_EQUAL :
assignment - > operation = AssignmentNode : : OP_MODULO ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_MODULE ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : LESS_LESS_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_SHIFT_LEFT ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_SHIFT_LEFT ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : GREATER_GREATER_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_SHIFT_RIGHT ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_SHIFT_RIGHT ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : AMPERSAND_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_AND ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_BIT_AND ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : PIPE_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_OR ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_BIT_OR ;
2020-05-01 22:14:56 +00:00
break ;
case GDScriptTokenizer : : Token : : CARET_EQUAL :
assignment - > operation = AssignmentNode : : OP_BIT_XOR ;
2020-08-07 12:51:09 +00:00
assignment - > variant_op = Variant : : OP_BIT_XOR ;
2020-05-01 22:14:56 +00:00
break ;
default :
break ; // Unreachable.
}
assignment - > assignee = p_previous_operand ;
assignment - > assigned_value = parse_expression ( false ) ;
2021-08-25 16:32:24 +00:00
if ( assignment - > assigned_value = = nullptr ) {
push_error ( R " (Expected an expression after " = " .) " ) ;
}
2016-08-25 18:18:35 +00:00
2020-07-16 01:02:44 +00:00
# ifdef DEBUG_ENABLED
2021-09-10 20:08:02 +00:00
if ( source_variable ! = nullptr ) {
if ( has_operator & & source_variable - > assignments = = 0 ) {
push_warning ( assignment , GDScriptWarning : : UNASSIGNED_VARIABLE_OP_ASSIGN , source_variable - > identifier - > name ) ;
}
source_variable - > assignments + = 1 ;
2020-06-11 22:31:28 +00:00
}
2020-07-16 01:02:44 +00:00
# endif
2020-06-11 22:31:28 +00:00
2020-05-01 22:14:56 +00:00
return assignment ;
}
2016-08-25 18:18:35 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_await ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
AwaitNode * await = alloc_node < AwaitNode > ( ) ;
2021-08-24 22:33:17 +00:00
ExpressionNode * element = parse_precedence ( PREC_AWAIT , false ) ;
if ( element = = nullptr ) {
push_error ( R " (Expected signal or coroutine after " await " .) " ) ;
}
await - > to_await = element ;
2016-08-25 18:18:35 +00:00
2021-08-12 22:19:55 +00:00
if ( current_function ) { // Might be null in a getter or setter.
current_function - > is_coroutine = true ;
}
2020-06-10 22:53:25 +00:00
2020-05-01 22:14:56 +00:00
return await ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_array ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
ArrayNode * array = alloc_node < ArrayNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( ! check ( GDScriptTokenizer : : Token : : BRACKET_CLOSE ) ) {
do {
if ( check ( GDScriptTokenizer : : Token : : BRACKET_CLOSE ) ) {
// Allow for trailing comma.
break ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) & & ! is_at_end ( ) ) ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : BRACKET_CLOSE , R " (Expected closing " ] " after array elements.) " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return array ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_dictionary ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
DictionaryNode * dictionary = alloc_node < DictionaryNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
// Key.
ExpressionNode * key = parse_expression ( false , true ) ; // Stop on "=" so we can check for Lua table style.
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( key = = nullptr ) {
push_error ( R " (Expected expression as dictionary key.) " ) ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
decided_style = true ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
switch ( dictionary - > style ) {
case DictionaryNode : : LUA_TABLE :
2021-09-15 12:56:24 +00:00
if ( key ! = nullptr & & key - > type ! = Node : : IDENTIFIER & & key - > type ! = Node : : LITERAL ) {
push_error ( " Expected identifier or string as LUA-style dictionary key. " ) ;
advance ( ) ;
break ;
}
if ( key ! = nullptr & & key - > type = = Node : : LITERAL & & static_cast < LiteralNode * > ( key ) - > value . get_type ( ) ! = Variant : : STRING ) {
push_error ( " Expected identifier or string as LUA-style dictionary key. " ) ;
2021-08-10 13:32:07 +00:00
advance ( ) ;
break ;
2020-05-01 22:14:56 +00: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 11:19:40 +00:00
if ( key ! = nullptr ) {
key - > is_constant = true ;
2021-09-15 12:56:24 +00: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 11:19:40 +00:00
}
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
// Value.
ExpressionNode * value = parse_expression ( false ) ;
if ( value = = nullptr ) {
push_error ( R " (Expected expression as dictionary value.) " ) ;
2017-03-05 15:44:50 +00:00
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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.) " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return dictionary ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_grouping ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
ExpressionNode * grouped = parse_expression ( false ) ;
pop_multiline ( ) ;
2020-08-05 19:42:33 +00:00
if ( grouped = = nullptr ) {
push_error ( R " (Expected grouping expression.) " ) ;
} else {
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected closing " ) " after grouping expression.)* " ) ;
}
2020-05-01 22:14:56 +00:00
return grouped ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_attribute ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
SubscriptNode * attribute = alloc_node < SubscriptNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-07-06 15:24:24 +00:00
if ( for_completion ) {
bool is_builtin = false ;
2021-04-05 14:17:59 +00:00
if ( p_previous_operand & & p_previous_operand - > type = = Node : : IDENTIFIER ) {
2020-07-06 15:24:24 +00: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 ) {
make_completion_context ( COMPLETION_BUILT_IN_TYPE_CONSTANT , builtin_type , true ) ;
is_builtin = true ;
}
}
if ( ! is_builtin ) {
make_completion_context ( COMPLETION_ATTRIBUTE , attribute , - 1 , true ) ;
}
}
2020-05-01 22:14:56 +00:00
attribute - > is_attribute = true ;
attribute - > base = p_previous_operand ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected identifier after " . " for attribute access.) " ) ) {
2020-07-06 15:24:24 +00:00
return attribute ;
2020-05-01 22:14:56 +00:00
}
attribute - > attribute = parse_identifier ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return attribute ;
}
2016-07-22 12:22:34 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_subscript ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
SubscriptNode * subscript = alloc_node < SubscriptNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_SUBSCRIPT , subscript ) ;
2020-05-01 22:14:56 +00:00
subscript - > base = p_previous_operand ;
subscript - > index = parse_expression ( false ) ;
2014-02-10 01:10:30 +00:00
2021-09-17 17:31:51 +00:00
if ( subscript - > index = = nullptr ) {
push_error ( R " (Expected expression after " [ " .) " ) ;
}
2020-05-01 22:14:56 +00:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : BRACKET_CLOSE , R " (Expected " ] " after subscription index.) " ) ;
2014-09-15 14:33:30 +00:00
2020-05-01 22:14:56 +00:00
return subscript ;
}
2014-09-15 14:33:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_cast ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
CastNode * cast = alloc_node < CastNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
cast - > operand = p_previous_operand ;
cast - > cast_type = parse_type ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( cast - > cast_type = = nullptr ) {
push_error ( R " (Expected type specifier after " as " .) " ) ;
return p_previous_operand ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
return cast ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_call ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
CallNode * call = alloc_node < CallNode > ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 ( ) ;
return nullptr ;
}
2020-06-10 21:18:10 +00:00
call - > function_name = current_function - > identifier - > name ;
2020-05-01 22:14:56 +00:00
} else {
consume ( GDScriptTokenizer : : Token : : PERIOD , R " (Expected " . " or " ( " after " super " .) " ) ;
2020-07-16 01:02:44 +00:00
make_completion_context ( COMPLETION_SUPER_METHOD , call , true ) ;
2020-05-01 22:14:56 +00:00
if ( ! consume ( GDScriptTokenizer : : Token : : IDENTIFIER , R " (Expected function name after " . " .) " ) ) {
pop_multiline ( ) ;
return nullptr ;
}
2020-06-10 21:18:10 +00:00
IdentifierNode * identifier = parse_identifier ( ) ;
call - > callee = identifier ;
call - > function_name = identifier - > name ;
2020-05-01 22:14:56 +00:00
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected " ( " after function name.) " ) ;
}
} else {
call - > callee = p_previous_operand ;
2020-06-10 21:18:10 +00:00
2020-08-05 19:41:46 +00: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 21:18:10 +00:00
call - > function_name = static_cast < IdentifierNode * > ( call - > callee ) - > name ;
2020-07-16 01:02:44 +00:00
make_completion_context ( COMPLETION_METHOD , call - > callee ) ;
2020-06-10 21:18:10 +00:00
} else if ( call - > callee - > type = = Node : : SUBSCRIPT ) {
SubscriptNode * attribute = static_cast < SubscriptNode * > ( call - > callee ) ;
if ( attribute - > is_attribute ) {
2020-07-16 01:02:44 +00:00
if ( attribute - > attribute ) {
call - > function_name = attribute - > attribute - > name ;
}
make_completion_context ( COMPLETION_ATTRIBUTE_METHOD , call - > callee ) ;
2020-06-10 21:18:10 +00: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-01 22:14:56 +00:00
}
2014-11-06 00:20:42 +00:00
2020-11-06 08:53:51 +00:00
// Arguments.
CompletionType ct = COMPLETION_CALL_ARGUMENTS ;
2020-11-26 14:56:32 +00:00
if ( call - > function_name = = " load " ) {
2020-11-06 08:53:51 +00:00
ct = COMPLETION_RESOURCE_PATH ;
2020-05-01 22:14:56 +00:00
}
2020-11-06 08:53:51 +00: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 ;
}
ExpressionNode * argument = parse_expression ( false ) ;
if ( argument = = nullptr ) {
push_error ( R " (Expected expression as the function argument.) " ) ;
} else {
call - > arguments . push_back ( argument ) ;
}
ct = COMPLETION_CALL_ARGUMENTS ;
} while ( match ( GDScriptTokenizer : : Token : : COMMA ) ) ;
pop_completion_call ( ) ;
2014-11-06 00:20:42 +00:00
2020-05-01 22:14:56 +00:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected closing " ) " after call arguments.)* " ) ;
2014-11-06 00:20:42 +00:00
2020-05-01 22:14:56 +00:00
return call ;
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_get_node ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
if ( match ( GDScriptTokenizer : : Token : : LITERAL ) ) {
if ( previous . literal . get_type ( ) ! = Variant : : STRING ) {
2020-09-18 11:35:51 +00:00
push_error ( R " (Expect node path as string or identifier after " $ " .) " ) ;
2020-05-01 22:14:56 +00:00
return nullptr ;
}
GetNodeNode * get_node = alloc_node < GetNodeNode > ( ) ;
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_GET_NODE , get_node ) ;
2020-05-01 22:14:56 +00:00
get_node - > string = parse_literal ( ) ;
return get_node ;
2020-08-19 13:19:05 +00:00
} else if ( current . is_node_name ( ) ) {
2020-05-01 22:14:56 +00:00
GetNodeNode * get_node = alloc_node < GetNodeNode > ( ) ;
2020-07-06 15:24:24 +00:00
int chain_position = 0 ;
2020-05-01 22:14:56 +00:00
do {
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_GET_NODE , get_node , chain_position + + ) ;
2020-08-19 13:19:05 +00:00
if ( ! current . is_node_name ( ) ) {
push_error ( R " (Expect node path after " / " .) " ) ;
2020-05-01 22:14:56 +00:00
return nullptr ;
2014-02-10 01:10:30 +00:00
}
2020-08-19 13:19:05 +00:00
advance ( ) ;
IdentifierNode * identifier = alloc_node < IdentifierNode > ( ) ;
identifier - > name = previous . get_identifier ( ) ;
2020-05-01 22:14:56 +00:00
get_node - > chain . push_back ( identifier ) ;
} while ( match ( GDScriptTokenizer : : Token : : SLASH ) ) ;
return get_node ;
} else {
2020-09-18 11:35:51 +00:00
push_error ( R " (Expect node path as string or identifier after " $ " .) " ) ;
2020-05-01 22:14:56 +00:00
return nullptr ;
}
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
push_multiline ( true ) ;
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_OPEN , R " (Expected " ( " after " preload " .) " ) ;
2016-12-29 10:31:19 +00:00
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_RESOURCE_PATH , preload ) ;
push_completion_call ( preload ) ;
2020-05-01 22:14:56 +00:00
preload - > path = parse_expression ( false ) ;
if ( preload - > path = = nullptr ) {
push_error ( R " (Expected resource path after " ( " .) " ) ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
2020-07-06 15:24:24 +00:00
pop_completion_call ( ) ;
2020-05-01 22:14:56 +00:00
pop_multiline ( ) ;
consume ( GDScriptTokenizer : : Token : : PARENTHESIS_CLOSE , R " *(Expected " ) " after preload path.)* " ) ;
return preload ;
2014-02-10 01:10:30 +00:00
}
2021-03-25 13:36:29 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_lambda ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
LambdaNode * lambda = alloc_node < LambdaNode > ( ) ;
2021-03-26 12:03:16 +00:00
lambda - > parent_function = current_function ;
2021-03-25 13:36:29 +00:00
FunctionNode * function = alloc_node < FunctionNode > ( ) ;
2021-03-26 12:03:16 +00:00
function - > source_lambda = lambda ;
function - > is_static = current_function ! = nullptr ? current_function - > is_static : false ;
2021-03-25 13:36:29 +00: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 ) {
tokenizer . push_expression_indented_block ( ) ;
}
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 ;
SuiteNode * body = alloc_node < SuiteNode > ( ) ;
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 ;
function - > body = parse_suite ( " lambda declaration " , body , true ) ;
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 ) ) {
current = tokenizer . scan ( ) ; // Not advance() since we don't want to change the previous token.
}
tokenizer . pop_expression_indented_block ( ) ;
}
current_function = previous_function ;
in_lambda = previous_in_lambda ;
lambda - > function = function ;
return lambda ;
}
2021-09-21 17:38:14 +00:00
GDScriptParser : : ExpressionNode * GDScriptParser : : parse_yield ( ExpressionNode * p_previous_operand , bool p_can_assign ) {
push_error ( R " ( " yield " was removed in Godot 4.0. Use " await " instead.) " ) ;
return nullptr ;
}
2020-05-01 22:14:56 +00: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 14:41:43 +00:00
}
2020-05-01 22:14:56 +00:00
// Return the previous expression.
return p_previous_operand ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
GDScriptParser : : TypeNode * GDScriptParser : : parse_type ( bool p_allow_void ) {
2020-07-06 15:24:24 +00:00
TypeNode * type = alloc_node < TypeNode > ( ) ;
make_completion_context ( p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME , type ) ;
2020-05-01 22:14:56 +00:00
if ( ! match ( GDScriptTokenizer : : Token : : IDENTIFIER ) ) {
if ( match ( GDScriptTokenizer : : Token : : VOID ) ) {
if ( p_allow_void ) {
2020-07-16 01:02:44 +00:00
TypeNode * void_type = alloc_node < TypeNode > ( ) ;
return void_type ;
2020-05-01 22:14:56 +00:00
} else {
push_error ( R " ( " void " is only allowed for a function return type.) " ) ;
2020-05-14 14:41:43 +00:00
}
2020-03-08 23:24:12 +00:00
}
2020-05-01 22:14:56 +00:00
// Leave error message to the caller who knows the context.
return nullptr ;
2020-03-08 23:24:12 +00:00
}
2020-06-10 21:18:10 +00:00
IdentifierNode * type_element = parse_identifier ( ) ;
2020-03-08 23:24:12 +00:00
2020-06-10 21:18:10 +00:00
type - > type_chain . push_back ( type_element ) ;
2021-03-09 15:32:35 +00:00
if ( match ( GDScriptTokenizer : : Token : : BRACKET_OPEN ) ) {
// Typed collection (like Array[int]).
type - > container_type = parse_type ( false ) ; // Don't allow void for array element type.
if ( type - > container_type = = nullptr ) {
push_error ( R " (Expected type for collection after " [ " .) " ) ;
type = nullptr ;
} else if ( type - > container_type - > container_type ! = nullptr ) {
push_error ( " Nested typed collections are not supported. " ) ;
}
consume ( GDScriptTokenizer : : Token : : BRACKET_CLOSE , R " (Expected closing " ] " after collection type.) " ) ;
return type ;
}
2020-07-06 15:24:24 +00:00
int chain_index = 1 ;
2020-06-10 21:18:10 +00:00
while ( match ( GDScriptTokenizer : : Token : : PERIOD ) ) {
2020-07-06 15:24:24 +00:00
make_completion_context ( COMPLETION_TYPE_ATTRIBUTE , type , chain_index + + ) ;
2020-06-10 21:18:10 +00: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-08 23:24:12 +00:00
}
2020-05-01 22:14:56 +00:00
}
return type ;
}
2020-11-29 02:37:57 +00:00
# ifdef TOOLS_ENABLED
static bool _in_codeblock ( String p_line , bool p_already_in , int * r_block_begins = nullptr ) {
int start_block = p_line . rfind ( " [codeblock] " ) ;
int end_block = p_line . rfind ( " [/codeblock] " ) ;
if ( start_block ! = - 1 & & r_block_begins ) {
* r_block_begins = start_block ;
}
if ( p_already_in ) {
if ( end_block = = - 1 ) {
return true ;
} else if ( start_block = = - 1 ) {
return false ;
} else {
return start_block > end_block ;
}
} else {
if ( start_block = = - 1 ) {
return false ;
} else if ( end_block = = - 1 ) {
return true ;
} else {
return start_block > end_block ;
}
}
}
bool GDScriptParser : : has_comment ( int p_line ) {
return tokenizer . get_comments ( ) . has ( p_line ) ;
}
String GDScriptParser : : get_doc_comment ( int p_line , bool p_single_line ) {
const Map < int , GDScriptTokenizer : : CommentData > & comments = tokenizer . get_comments ( ) ;
ERR_FAIL_COND_V ( ! comments . has ( p_line ) , String ( ) ) ;
if ( p_single_line ) {
if ( comments [ p_line ] . comment . begins_with ( " ## " ) ) {
return comments [ p_line ] . comment . trim_prefix ( " ## " ) . strip_edges ( ) ;
}
return " " ;
}
String doc ;
int line = p_line ;
bool in_codeblock = false ;
while ( comments . has ( line - 1 ) ) {
if ( ! comments [ line - 1 ] . new_line | | ! comments [ line - 1 ] . comment . begins_with ( " ## " ) ) {
break ;
}
line - - ;
}
int codeblock_begins = 0 ;
while ( comments . has ( line ) ) {
if ( ! comments [ line ] . new_line | | ! comments [ line ] . comment . begins_with ( " ## " ) ) {
break ;
}
String doc_line = comments [ line ] . comment . trim_prefix ( " ## " ) ;
in_codeblock = _in_codeblock ( doc_line , in_codeblock , & codeblock_begins ) ;
if ( in_codeblock ) {
int i = 0 ;
for ( ; i < codeblock_begins ; i + + ) {
if ( doc_line [ i ] ! = ' ' ) {
break ;
}
}
doc_line = doc_line . substr ( i ) ;
} else {
doc_line = doc_line . strip_edges ( ) ;
}
String line_join = ( in_codeblock ) ? " \n " : " " ;
2020-12-15 12:04:21 +00:00
doc = ( doc . is_empty ( ) ) ? doc_line : doc + line_join + doc_line ;
2020-11-29 02:37:57 +00:00
line + + ;
}
return doc ;
}
void GDScriptParser : : get_class_doc_comment ( int p_line , String & p_brief , String & p_desc , Vector < Pair < String , String > > & p_tutorials , bool p_inner_class ) {
const Map < int , GDScriptTokenizer : : CommentData > & comments = tokenizer . get_comments ( ) ;
if ( ! comments . has ( p_line ) ) {
return ;
}
ERR_FAIL_COND ( p_brief ! = " " | | p_desc ! = " " | | p_tutorials . size ( ) ! = 0 ) ;
int line = p_line ;
bool in_codeblock = false ;
enum Mode {
BRIEF ,
DESC ,
TUTORIALS ,
DONE ,
} ;
Mode mode = BRIEF ;
if ( p_inner_class ) {
while ( comments . has ( line - 1 ) ) {
if ( ! comments [ line - 1 ] . new_line | | ! comments [ line - 1 ] . comment . begins_with ( " ## " ) ) {
break ;
}
line - - ;
}
}
int codeblock_begins = 0 ;
while ( comments . has ( line ) ) {
if ( ! comments [ line ] . new_line | | ! comments [ line ] . comment . begins_with ( " ## " ) ) {
break ;
}
String title , link ; // For tutorials.
String doc_line = comments [ line + + ] . comment . trim_prefix ( " ## " ) ;
String striped_line = doc_line . strip_edges ( ) ;
// Set the read mode.
if ( striped_line . begins_with ( " @desc: " ) & & p_desc = = " " ) {
mode = DESC ;
striped_line = striped_line . trim_prefix ( " @desc: " ) ;
in_codeblock = _in_codeblock ( doc_line , in_codeblock ) ;
} else if ( striped_line . begins_with ( " @tutorial " ) ) {
int begin_scan = String ( " @tutorial " ) . length ( ) ;
if ( begin_scan > = striped_line . length ( ) ) {
continue ; // invalid syntax.
}
if ( striped_line [ begin_scan ] = = ' : ' ) { // No title.
// Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional.
title = " " ;
link = striped_line . trim_prefix ( " @tutorial: " ) . strip_edges ( ) ;
} else {
/* Syntax:
2021-08-22 01:56:25 +00:00
@ tutorial ( The Title Here ) : https : //the.url/
2020-11-29 02:37:57 +00:00
^ open ^ close ^ colon ^ url
*/
int open_bracket_pos = begin_scan , close_bracket_pos = 0 ;
while ( open_bracket_pos < striped_line . length ( ) & & ( striped_line [ open_bracket_pos ] = = ' ' | | striped_line [ open_bracket_pos ] = = ' \t ' ) ) {
open_bracket_pos + + ;
}
if ( open_bracket_pos = = striped_line . length ( ) | | striped_line [ open_bracket_pos + + ] ! = ' ( ' ) {
continue ; // invalid syntax.
}
close_bracket_pos = open_bracket_pos ;
while ( close_bracket_pos < striped_line . length ( ) & & striped_line [ close_bracket_pos ] ! = ' ) ' ) {
close_bracket_pos + + ;
}
if ( close_bracket_pos = = striped_line . length ( ) ) {
continue ; // invalid syntax.
}
int colon_pos = close_bracket_pos + 1 ;
while ( colon_pos < striped_line . length ( ) & & ( striped_line [ colon_pos ] = = ' ' | | striped_line [ colon_pos ] = = ' \t ' ) ) {
colon_pos + + ;
}
if ( colon_pos = = striped_line . length ( ) | | striped_line [ colon_pos + + ] ! = ' : ' ) {
continue ; // invalid syntax.
}
title = striped_line . substr ( open_bracket_pos , close_bracket_pos - open_bracket_pos ) . strip_edges ( ) ;
link = striped_line . substr ( colon_pos ) . strip_edges ( ) ;
}
mode = TUTORIALS ;
in_codeblock = false ;
2020-12-15 12:04:21 +00:00
} else if ( striped_line . is_empty ( ) ) {
2020-11-29 02:37:57 +00:00
continue ;
} else {
// Tutorial docs are single line, we need a @tag after it.
if ( mode = = TUTORIALS ) {
mode = DONE ;
}
in_codeblock = _in_codeblock ( doc_line , in_codeblock , & codeblock_begins ) ;
}
if ( in_codeblock ) {
int i = 0 ;
for ( ; i < codeblock_begins ; i + + ) {
if ( doc_line [ i ] ! = ' ' ) {
break ;
}
}
doc_line = doc_line . substr ( i ) ;
} else {
doc_line = striped_line ;
}
String line_join = ( in_codeblock ) ? " \n " : " " ;
switch ( mode ) {
case BRIEF :
p_brief = ( p_brief . length ( ) = = 0 ) ? doc_line : p_brief + line_join + doc_line ;
break ;
case DESC :
p_desc = ( p_desc . length ( ) = = 0 ) ? doc_line : p_desc + line_join + doc_line ;
break ;
case TUTORIALS :
p_tutorials . append ( Pair < String , String > ( title , link ) ) ;
break ;
case DONE :
return ;
}
}
}
# endif // TOOLS_ENABLED
2020-05-01 22:14:56 +00: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 21:18:10 +00:00
// PREFIX INFIX PRECEDENCE (for infix)
2020-05-01 22:14:56 +00: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 07:39:17 +00:00
{ & GDScriptParser : : parse_unary_operator , & GDScriptParser : : parse_binary_not_in_operator , PREC_CONTENT_TEST } , // NOT,
2020-05-01 22:14:56 +00: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 13:03:05 +00: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-01 22:14:56 +00:00
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_FACTOR } , // STAR,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_FACTOR } , // SLASH,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_FACTOR } , // PERCENT,
// 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,
{ 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,
// 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 13:36:29 +00:00
{ & GDScriptParser : : parse_lambda , nullptr , PREC_NONE } , // FUNC,
2020-05-01 22:14:56 +00:00
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_CONTENT_TEST } , // IN,
{ nullptr , & GDScriptParser : : parse_binary_operator , PREC_TYPE_TEST } , // IS,
{ 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 01:02:44 +00:00
{ nullptr , nullptr , PREC_NONE } , // TRAIT,
2020-05-01 22:14:56 +00:00
{ nullptr , nullptr , PREC_NONE } , // VAR,
{ nullptr , nullptr , PREC_NONE } , // VOID,
2021-09-21 17:38:14 +00:00
{ & GDScriptParser : : parse_yield , nullptr , PREC_NONE } , // YIELD,
2020-05-01 22:14:56 +00: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 13:35:16 +00:00
// Let's assume this is never invalid, since nothing generates a TK_MAX.
2020-05-01 22:14:56 +00:00
return & rules [ p_token_type ] ;
}
bool GDScriptParser : : SuiteNode : : has_local ( const StringName & p_name ) const {
if ( locals_indices . has ( p_name ) ) {
2020-03-08 23:24:12 +00:00
return true ;
}
2020-05-01 22:14:56 +00:00
if ( parent_block ! = nullptr ) {
return parent_block - > has_local ( p_name ) ;
2020-03-08 23:24:12 +00:00
}
return false ;
}
2020-05-01 22:14:56 +00: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 14:50:10 +00:00
}
2020-05-01 22:14:56 +00:00
if ( parent_block ! = nullptr ) {
return parent_block - > get_local ( p_name ) ;
2015-09-02 03:56:51 +00:00
}
2020-05-01 22:14:56 +00:00
return empty ;
}
2015-09-02 03:56:51 +00:00
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : AnnotationNode : : apply ( GDScriptParser * p_this , Node * p_target ) const {
return ( p_this - > * ( p_this - > valid_annotations [ name ] . apply ) ) ( this , p_target ) ;
2015-08-30 14:50:10 +00:00
}
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : AnnotationNode : : applies_to ( uint32_t p_target_kinds ) const {
return ( info - > target_kind & p_target_kinds ) > 0 ;
2018-05-30 02:16:54 +00:00
}
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : validate_annotation_arguments ( AnnotationNode * p_annotation ) {
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 22:43:33 +00:00
2020-05-01 22:14:56 +00:00
const MethodInfo & info = valid_annotations [ p_annotation - > name ] . info ;
2018-06-05 16:50:21 +00:00
2020-05-01 22:14:56 +00: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 02:16:54 +00:00
}
2020-05-01 22:14:56 +00: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 02:16:54 +00:00
}
2020-05-01 22:14:56 +00:00
const List < PropertyInfo > : : Element * E = info . arguments . front ( ) ;
for ( int i = 0 ; i < p_annotation - > arguments . size ( ) ; i + + ) {
ExpressionNode * argument = p_annotation - > arguments [ i ] ;
const PropertyInfo & parameter = E - > get ( ) ;
if ( E - > next ( ) ! = nullptr ) {
E = E - > next ( ) ;
}
switch ( parameter . type ) {
case Variant : : STRING :
case Variant : : STRING_NAME :
case Variant : : NODE_PATH :
// Allow "quote-less strings", as long as they are recognized as identifiers.
if ( argument - > type = = Node : : IDENTIFIER ) {
IdentifierNode * string = static_cast < IdentifierNode * > ( argument ) ;
Callable : : CallError error ;
Vector < Variant > args = varray ( string - > name ) ;
const Variant * name = args . ptr ( ) ;
2020-11-09 03:19:09 +00:00
Variant r ;
Variant : : construct ( parameter . type , r , & ( name ) , 1 , error ) ;
p_annotation - > resolved_arguments . push_back ( r ) ;
2020-05-01 22:14:56 +00:00
if ( error . error ! = Callable : : CallError : : CALL_OK ) {
push_error ( vformat ( R " (Expected %s as argument %d of annotation " % s " ).) " , Variant : : get_type_name ( parameter . type ) , i + 1 , p_annotation - > name ) ) ;
p_annotation - > resolved_arguments . remove ( p_annotation - > resolved_arguments . size ( ) - 1 ) ;
return false ;
2019-03-04 11:25:59 +00:00
}
2020-05-01 22:14:56 +00:00
break ;
2018-06-19 05:55:52 +00:00
}
2020-05-01 22:14:56 +00:00
[[fallthrough]] ;
default : {
if ( argument - > type ! = Node : : LITERAL ) {
push_error ( vformat ( R " (Expected %s as argument %d of annotation " % s " ).) " , Variant : : get_type_name ( parameter . type ) , i + 1 , p_annotation - > name ) ) ;
return false ;
2020-05-10 05:05:29 +00:00
}
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
Variant value = static_cast < LiteralNode * > ( argument ) - > value ;
if ( ! Variant : : can_convert_strict ( value . get_type ( ) , parameter . type ) ) {
push_error ( vformat ( R " (Expected %s as argument %d of annotation " % s " ).) " , Variant : : get_type_name ( parameter . type ) , i + 1 , p_annotation - > name ) ) ;
return false ;
2018-05-30 02:16:54 +00:00
}
2020-05-01 22:14:56 +00:00
Callable : : CallError error ;
const Variant * args = & value ;
2020-11-09 03:19:09 +00:00
Variant r ;
Variant : : construct ( parameter . type , r , & ( args ) , 1 , error ) ;
p_annotation - > resolved_arguments . push_back ( r ) ;
2020-05-01 22:14:56 +00:00
if ( error . error ! = Callable : : CallError : : CALL_OK ) {
push_error ( vformat ( R " (Expected %s as argument %d of annotation " % s " ).) " , Variant : : get_type_name ( parameter . type ) , i + 1 , p_annotation - > name ) ) ;
p_annotation - > resolved_arguments . remove ( p_annotation - > resolved_arguments . size ( ) - 1 ) ;
return false ;
2018-05-30 02:16:54 +00:00
}
2020-05-10 10:56:01 +00:00
break ;
2020-05-14 14:41:43 +00:00
}
2018-05-30 02:16:54 +00:00
}
2019-11-17 23:19:06 +00:00
}
2020-05-01 22:14:56 +00:00
return true ;
2018-05-30 02:16:54 +00:00
}
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : tool_annotation ( const AnnotationNode * p_annotation , Node * p_node ) {
this - > _is_tool = true ;
return true ;
}
2018-07-25 15:12:46 +00:00
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : icon_annotation ( const AnnotationNode * p_annotation , Node * p_node ) {
ERR_FAIL_COND_V_MSG ( p_node - > type ! = Node : : CLASS , false , R " ( " @ icon " annotation can only be applied to classes.) " ) ;
ClassNode * p_class = static_cast < ClassNode * > ( p_node ) ;
p_class - > icon_path = p_annotation - > resolved_arguments [ 0 ] ;
return true ;
}
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : onready_annotation ( const AnnotationNode * p_annotation , Node * p_node ) {
ERR_FAIL_COND_V_MSG ( p_node - > type ! = Node : : VARIABLE , false , R " ( " @ onready " annotation can only be applied to class variables.) " ) ;
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
VariableNode * variable = static_cast < VariableNode * > ( p_node ) ;
if ( variable - > onready ) {
push_error ( R " ( " @ onready " annotation can only be used once per variable.) " ) ;
return false ;
2018-05-30 02:16:54 +00:00
}
2020-05-01 22:14:56 +00:00
variable - > onready = true ;
current_class - > onready_used = true ;
return true ;
2018-05-30 02:16:54 +00:00
}
2020-05-01 22:14:56 +00:00
template < PropertyHint t_hint , Variant : : Type t_type >
bool GDScriptParser : : export_annotations ( const AnnotationNode * p_annotation , Node * p_node ) {
ERR_FAIL_COND_V_MSG ( p_node - > type ! = Node : : VARIABLE , false , vformat ( R " ( " % s " annotation can only be applied to variables.) " , p_annotation - > name ) ) ;
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
VariableNode * variable = static_cast < VariableNode * > ( p_node ) ;
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 16:17:40 +00:00
}
2020-05-01 22:14:56 +00:00
variable - > exported = true ;
2021-03-17 13:57:30 +00:00
2020-05-01 22:14:56 +00:00
variable - > export_info . type = t_type ;
variable - > export_info . hint = t_hint ;
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
String hint_string ;
for ( int i = 0 ; i < p_annotation - > resolved_arguments . size ( ) ; i + + ) {
if ( i > 0 ) {
hint_string + = " , " ;
}
hint_string + = String ( p_annotation - > resolved_arguments [ i ] ) ;
}
2018-06-05 16:50:21 +00:00
2020-05-01 22:14:56 +00:00
variable - > export_info . hint_string = hint_string ;
2018-05-30 02:16:54 +00:00
2021-03-17 13:57:30 +00:00
// This is called after tne analyzer is done finding the type, so this should be set here.
2021-03-18 13:17:42 +00:00
DataType export_type = variable - > get_datatype ( ) ;
2021-03-17 13:57:30 +00:00
if ( p_annotation - > name = = " @export " ) {
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 ;
}
2021-03-18 13:17:42 +00:00
bool is_array = false ;
if ( export_type . builtin_type = = Variant : : ARRAY & & export_type . has_container_element_type ( ) ) {
export_type = export_type . get_container_element_type ( ) ; // Use inner type for.
is_array = true ;
}
2021-03-17 13:57:30 +00:00
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 ;
variable - > export_info . hint_string = Variant : : get_type_name ( export_type . builtin_type ) ;
break ;
case GDScriptParser : : DataType : : NATIVE :
2021-08-17 13:06:54 +00:00
if ( ClassDB : : is_parent_class ( export_type . native_type , " Resource " ) ) {
2021-03-17 13:57:30 +00:00
variable - > export_info . type = Variant : : OBJECT ;
variable - > export_info . hint = PROPERTY_HINT_RESOURCE_TYPE ;
2021-08-17 13:06:54 +00:00
variable - > export_info . hint_string = export_type . native_type ;
2021-03-17 13:57:30 +00:00
} else {
2021-03-17 17:58:05 +00:00
push_error ( R " (Export type can only be built-in, a resource, or an enum.) " , variable ) ;
2021-03-18 13:17:42 +00:00
return false ;
2021-03-17 13:57:30 +00:00
}
break ;
2021-03-17 17:58:05 +00:00
case GDScriptParser : : DataType : : ENUM : {
variable - > export_info . type = Variant : : INT ;
variable - > export_info . hint = PROPERTY_HINT_ENUM ;
String enum_hint_string ;
for ( const Map < StringName , int > : : Element * E = export_type . enum_values . front ( ) ; E ; E = E - > next ( ) ) {
enum_hint_string + = E - > key ( ) . operator String ( ) . camelcase_to_underscore ( true ) . capitalize ( ) . xml_escape ( ) ;
enum_hint_string + = " : " ;
enum_hint_string + = String : : num_int64 ( E - > get ( ) ) . xml_escape ( ) ;
if ( E - > next ( ) ) {
enum_hint_string + = " , " ;
}
}
variable - > export_info . hint_string = enum_hint_string ;
} break ;
2021-03-17 13:57:30 +00:00
default :
// TODO: Allow custom user resources.
2021-03-17 17:58:05 +00:00
push_error ( R " (Export type can only be built-in, a resource, or an enum.) " , variable ) ;
2021-03-17 13:57:30 +00:00
break ;
}
2021-03-18 13:17:42 +00:00
if ( is_array ) {
String hint_prefix = itos ( variable - > export_info . type ) ;
if ( variable - > export_info . hint ) {
hint_prefix + = " / " + itos ( variable - > export_info . hint ) ;
}
variable - > export_info . hint = PROPERTY_HINT_TYPE_STRING ;
variable - > export_info . hint_string = hint_prefix + " : " + variable - > export_info . hint_string ;
variable - > export_info . type = Variant : : ARRAY ;
}
2021-03-17 13:57:30 +00:00
} else {
// 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 ) ) {
push_error ( vformat ( R " ( " % s " annotation requires a variable of type " % s " but type " % s " was given instead.) " , p_annotation - > name . operator String ( ) , Variant : : get_type_name ( t_type ) , export_type . to_string ( ) ) , variable ) ;
return false ;
}
}
}
2020-05-01 22:14:56 +00:00
return true ;
}
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : warning_annotations ( const AnnotationNode * p_annotation , Node * p_node ) {
ERR_FAIL_V_MSG ( false , " Not implemented. " ) ;
}
2021-09-03 17:40:47 +00:00
template < Multiplayer : : RPCMode t_mode >
2020-05-01 22:14:56 +00:00
bool GDScriptParser : : network_annotations ( const AnnotationNode * p_annotation , Node * p_node ) {
ERR_FAIL_COND_V_MSG ( p_node - > type ! = Node : : VARIABLE & & p_node - > type ! = Node : : FUNCTION , false , vformat ( R " ( " % s " annotation can only be applied to variables and functions.) " , p_annotation - > name ) ) ;
2021-09-03 17:40:47 +00:00
Multiplayer : : RPCConfig rpc_config ;
2021-06-24 08:28:15 +00:00
rpc_config . rpc_mode = t_mode ;
2021-08-10 19:45:58 +00:00
if ( p_annotation - > resolved_arguments . size ( ) ) {
int last = p_annotation - > resolved_arguments . size ( ) - 1 ;
if ( p_annotation - > resolved_arguments [ last ] . get_type ( ) = = Variant : : INT ) {
rpc_config . channel = p_annotation - > resolved_arguments [ last ] . operator int ( ) ;
last - = 1 ;
}
if ( last > 3 ) {
push_error ( R " (Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.') " , p_annotation ) ;
return false ;
}
for ( int i = last ; i > = 0 ; i - - ) {
2021-06-24 08:28:15 +00:00
String mode = p_annotation - > resolved_arguments [ i ] . operator String ( ) ;
if ( mode = = " any " ) {
2021-09-03 17:40:47 +00:00
rpc_config . rpc_mode = Multiplayer : : RPC_MODE_ANY ;
2021-08-10 18:44:06 +00:00
} else if ( mode = = " auth " ) {
2021-09-03 17:40:47 +00:00
rpc_config . rpc_mode = Multiplayer : : RPC_MODE_AUTHORITY ;
2021-08-10 19:45:58 +00:00
} else if ( mode = = " sync " ) {
2021-06-24 08:28:15 +00:00
rpc_config . sync = true ;
2021-08-10 19:45:58 +00:00
} else if ( mode = = " nosync " ) {
2021-06-24 08:28:15 +00:00
rpc_config . sync = false ;
2021-08-10 19:45:58 +00:00
} else if ( mode = = " reliable " ) {
2021-09-03 17:40:47 +00:00
rpc_config . transfer_mode = Multiplayer : : TRANSFER_MODE_RELIABLE ;
2021-06-24 08:28:15 +00:00
} else if ( mode = = " unreliable " ) {
2021-09-03 17:40:47 +00:00
rpc_config . transfer_mode = Multiplayer : : TRANSFER_MODE_UNRELIABLE ;
2021-06-24 08:28:15 +00:00
} else if ( mode = = " ordered " ) {
2021-09-03 17:40:47 +00:00
rpc_config . transfer_mode = Multiplayer : : TRANSFER_MODE_ORDERED ;
2021-06-24 08:28:15 +00:00
} else {
2021-08-10 19:45:58 +00:00
push_error ( R " (Invalid RPC argument. Must be one of: 'sync'/'nosync' (local calls), 'any'/'auth' (permission), 'reliable'/'unreliable'/'ordered' (transfer mode).) " , p_annotation ) ;
2021-06-24 08:28:15 +00:00
}
2020-05-01 22:14:56 +00:00
}
2021-06-24 08:28:15 +00:00
}
switch ( p_node - > type ) {
2020-05-01 22:14:56 +00:00
case Node : : FUNCTION : {
FunctionNode * function = static_cast < FunctionNode * > ( p_node ) ;
2021-09-03 17:40:47 +00:00
if ( function - > rpc_config . rpc_mode ! = Multiplayer : : RPC_MODE_DISABLED ) {
2020-05-01 22:14:56 +00:00
push_error ( R " (RPC annotations can only be used once per function.) " , p_annotation ) ;
2021-06-24 08:28:15 +00:00
return false ;
2018-05-30 02:16:54 +00:00
}
2021-06-24 08:28:15 +00:00
function - > rpc_config = rpc_config ;
2020-05-01 22:14:56 +00:00
break ;
2018-05-30 02:16:54 +00:00
}
2020-05-01 22:14:56 +00:00
default :
return false ; // Unreachable.
2018-05-30 02:16:54 +00:00
}
2020-05-01 22:14:56 +00:00
return true ;
}
2020-06-10 21:18:10 +00: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 {
String name ;
switch ( type ) {
case SuiteNode : : Local : : PARAMETER :
name = " parameter " ;
break ;
case SuiteNode : : Local : : CONSTANT :
name = " constant " ;
break ;
case SuiteNode : : Local : : VARIABLE :
name = " variable " ;
break ;
case SuiteNode : : Local : : FOR_VARIABLE :
name = " for loop iterator " ;
break ;
case SuiteNode : : Local : : PATTERN_BIND :
name = " pattern_bind " ;
break ;
case SuiteNode : : Local : : UNDEFINED :
name = " <undefined> " ;
break ;
}
return name ;
}
String GDScriptParser : : DataType : : to_string ( ) const {
switch ( kind ) {
case VARIANT :
return " Variant " ;
case BUILTIN :
if ( builtin_type = = Variant : : NIL ) {
return " null " ;
}
2021-03-09 15:32:35 +00:00
if ( builtin_type = = Variant : : ARRAY & & has_container_element_type ( ) ) {
return vformat ( " Array[%s] " , container_element_type - > to_string ( ) ) ;
}
2020-06-10 21:18:10 +00: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 ( is_meta_type ) {
return GDScript : : get_class_static ( ) ;
}
if ( class_type - > identifier ! = nullptr ) {
return class_type - > identifier - > name . operator String ( ) ;
}
2020-06-12 00:49:58 +00:00
return class_type - > fqcn ;
2020-06-10 21:18:10 +00:00
case SCRIPT : {
if ( is_meta_type ) {
return script_type - > get_class_name ( ) . operator String ( ) ;
}
String name = script_type - > get_name ( ) ;
2020-12-15 12:04:21 +00:00
if ( ! name . is_empty ( ) ) {
2020-06-10 21:18:10 +00:00
return name ;
}
2020-07-16 01:02:44 +00:00
name = script_path ;
2020-12-15 12:04:21 +00:00
if ( ! name . is_empty ( ) ) {
2020-06-10 21:18:10 +00:00
return name ;
}
return native_type . operator String ( ) ;
}
2020-06-12 00:49:58 +00:00
case ENUM :
return enum_type . operator String ( ) + " (enum) " ;
case ENUM_VALUE :
return enum_type . operator String ( ) + " (enum value) " ;
2020-06-10 21:18:10 +00:00
case UNRESOLVED :
return " <unresolved type> " ;
}
ERR_FAIL_V_MSG ( " <unresolved type " , " Kind set outside the enum range. " ) ;
}
2021-08-14 03:44:22 +00: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 ;
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 ;
}
2020-05-01 22:14:56 +00:00
/*---------- PRETTY PRINT FOR DEBUG ----------*/
2018-07-01 16:17:40 +00:00
# ifdef DEBUG_ENABLED
2020-05-01 22:14:56 +00: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 16:17:40 +00:00
}
}
2018-05-30 02:16:54 +00:00
}
2020-05-01 22:14:56 +00: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 14:41:43 +00:00
}
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : push_line ( const String & p_line ) {
2020-12-15 12:04:21 +00:00
if ( ! p_line . is_empty ( ) ) {
2020-05-01 22:14:56 +00:00
push_text ( p_line ) ;
2018-07-01 16:17:40 +00:00
}
2020-05-01 22:14:56 +00:00
printed + = " \n " ;
pending_indent = true ;
2018-07-01 16:17:40 +00:00
}
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : push_text ( const String & p_text ) {
if ( pending_indent ) {
printed + = indent ;
pending_indent = false ;
2018-07-01 16:17:40 +00:00
}
2020-05-01 22:14:56 +00:00
printed + = p_text ;
}
2021-07-24 13:46:25 +00:00
void GDScriptParser : : TreePrinter : : print_annotation ( const AnnotationNode * p_annotation ) {
2020-05-01 22:14:56 +00: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 16:17:40 +00:00
}
2020-05-01 22:14:56 +00: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 16:17:40 +00:00
}
2020-05-01 22:14:56 +00:00
push_text ( " ] " ) ;
}
2018-07-01 16:17:40 +00:00
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : print_assert ( AssertNode * p_assert ) {
push_text ( " Assert ( " ) ;
print_expression ( p_assert - > condition ) ;
push_line ( " ) " ) ;
}
2018-07-01 16:17:40 +00:00
2020-05-01 22:14:56 +00: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 16:17:40 +00:00
break ;
2020-05-01 22:14:56 +00:00
case Node : : SUBSCRIPT :
print_subscript ( static_cast < SubscriptNode * > ( p_assignment - > assignee ) ) ;
break ;
default :
break ; // Unreachable.
2018-07-01 16:17:40 +00:00
}
2020-05-01 22:14:56 +00: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 ;
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 16:17:40 +00:00
}
2020-05-01 22:14:56 +00:00
push_text ( " = " ) ;
print_expression ( p_assignment - > assigned_value ) ;
push_line ( ) ;
2018-07-01 16:17:40 +00:00
}
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : print_await ( AwaitNode * p_await ) {
push_text ( " Await " ) ;
print_expression ( p_await - > to_await ) ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00: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 ;
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_TYPE_TEST :
push_text ( " IS " ) ;
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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00: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 14:38:54 +00:00
}
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
if ( p_class - > extends_used ) {
bool first = true ;
push_text ( " Extends " ) ;
2020-12-15 12:04:21 +00:00
if ( ! p_class - > extends_path . is_empty ( ) ) {
2020-05-01 22:14:56 +00: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 ;
}
push_text ( p_class - > extends [ i ] ) ;
}
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
push_line ( " : " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
increase_indent ( ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
for ( int i = 0 ; i < p_class - > members . size ( ) ; i + + ) {
const ClassNode : : Member & m = p_class - > members [ i ] ;
2020-03-28 08:12:19 +00:00
2020-05-01 22:14:56 +00: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.
case ClassNode : : Member : : UNDEFINED :
push_line ( " <unknown member> " ) ;
break ;
}
2018-05-30 02:16:52 +00:00
}
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
decrease_indent ( ) ;
}
2019-03-03 19:36:42 +00:00
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : print_constant ( ConstantNode * p_constant ) {
push_text ( " Constant " ) ;
print_identifier ( p_constant - > identifier ) ;
2018-05-30 02:16:52 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
decrease_indent ( ) ;
push_line ( ) ;
}
2018-05-30 02:16:52 +00:00
2020-05-01 22:14:56 +00: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 15:22:41 +00:00
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : print_expression ( ExpressionNode * p_expression ) {
2021-03-25 13:36:29 +00:00
if ( p_expression = = nullptr ) {
push_text ( " <invalid expression> " ) ;
return ;
}
2020-05-01 22:14:56 +00: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 13:36:29 +00:00
case Node : : LAMBDA :
print_lambda ( static_cast < LambdaNode * > ( p_expression ) ) ;
break ;
2020-05-01 22:14:56 +00: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 ;
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 14:41:43 +00:00
}
2020-05-01 22:14:56 +00:00
}
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00: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 02:16:54 +00:00
2020-05-01 22:14:56 +00: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 02:16:54 +00:00
}
2020-05-01 22:14:56 +00:00
decrease_indent ( ) ;
push_line ( " } " ) ;
}
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00: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 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
increase_indent ( ) ;
2020-03-28 08:12:19 +00:00
2020-05-01 22:14:56 +00:00
print_suite ( p_for - > loop ) ;
2018-05-30 02:16:54 +00:00
2020-05-01 22:14:56 +00:00
decrease_indent ( ) ;
}
2019-01-23 20:45:33 +00:00
2021-03-25 13:36:29 +00:00
void GDScriptParser : : TreePrinter : : print_function ( FunctionNode * p_function , const String & p_context ) {
2021-07-24 13:46:25 +00:00
for ( const AnnotationNode * E : p_function - > annotations ) {
2021-07-16 03:45:57 +00:00
print_annotation ( E ) ;
2020-05-01 22:14:56 +00:00
}
2021-03-25 13:36:29 +00:00
push_text ( p_context ) ;
push_text ( " " ) ;
if ( p_function - > identifier ) {
print_identifier ( p_function - > identifier ) ;
} else {
push_text ( " <anonymous> " ) ;
}
2020-05-01 22:14:56 +00:00
push_text ( " ( " ) ;
for ( int i = 0 ; i < p_function - > parameters . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " , " ) ;
2018-07-01 16:17:40 +00:00
}
2020-05-01 22:14:56 +00: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 ) {
push_text ( " $ " ) ;
if ( p_get_node - > string ! = nullptr ) {
print_literal ( p_get_node - > string ) ;
} else {
for ( int i = 0 ; i < p_get_node - > chain . size ( ) ; i + + ) {
if ( i > 0 ) {
push_text ( " / " ) ;
2018-07-01 16:17:40 +00:00
}
2020-05-01 22:14:56 +00:00
print_identifier ( p_get_node - > chain [ i ] ) ;
2018-07-01 16:17:40 +00:00
}
}
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : print_identifier ( IdentifierNode * p_identifier ) {
push_text ( p_identifier - > name ) ;
2014-02-25 12:31:47 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
2020-05-01 22:14:56 +00:00
increase_indent ( ) ;
print_suite ( p_if - > true_block ) ;
decrease_indent ( ) ;
2014-02-25 12:31:47 +00:00
2020-05-01 22:14:56 +00: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 12:31:47 +00:00
}
2021-03-25 13:36:29 +00:00
void GDScriptParser : : TreePrinter : : print_lambda ( LambdaNode * p_lambda ) {
print_function ( p_lambda - > function , " Lambda " ) ;
2021-03-26 12:03:16 +00:00
push_text ( " | captures [ " ) ;
2021-03-28 14:03:13 +00:00
for ( int i = 0 ; i < p_lambda - > captures . size ( ) ; i + + ) {
if ( i > 0 ) {
2021-03-26 12:03:16 +00:00
push_text ( " , " ) ;
}
2021-03-28 14:03:13 +00:00
push_text ( p_lambda - > captures [ i ] - > name . operator String ( ) ) ;
2021-03-26 12:03:16 +00:00
}
push_line ( " ] " ) ;
2021-03-25 13:36:29 +00:00
}
2020-05-01 22:14:56 +00: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 18:36:03 +00:00
}
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
push_line ( " : " ) ;
2014-02-10 01:10:30 +00:00
2020-05-01 22:14:56 +00:00
increase_indent ( ) ;
print_suite ( p_match_branch - > block ) ;
decrease_indent ( ) ;
}
2014-12-17 02:46:55 +00:00
2020-05-01 22:14:56 +00: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 14:50:10 +00:00
2020-05-01 22:14:56 +00: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 ) ;
}
if ( p_parameter - > default_value ! = nullptr ) {
push_text ( " = " ) ;
print_expression ( p_parameter - > default_value ) ;
}
}
2014-12-17 01:31:57 +00:00
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : print_type ( TypeNode * p_type ) {
2020-12-15 12:04:21 +00:00
if ( p_type - > type_chain . is_empty ( ) ) {
2020-05-01 22:14:56 +00:00
push_text ( " Void " ) ;
2020-06-10 21:18:10 +00: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-01 22:14:56 +00:00
}
2014-12-17 01:31:57 +00:00
}
2020-05-01 22:14:56 +00: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 01:31:57 +00:00
}
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : print_variable ( VariableNode * p_variable ) {
2021-07-24 13:46:25 +00:00
for ( const AnnotationNode * E : p_variable - > annotations ) {
2021-07-16 03:45:57 +00:00
print_annotation ( E ) ;
2020-05-01 22:14:56 +00:00
}
push_text ( " Variable " ) ;
print_identifier ( p_variable - > identifier ) ;
2020-06-01 19:41:05 +00: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-01 22:14:56 +00: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 19:41:05 +00: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 ( ) ;
print_suite ( p_variable - > getter ) ;
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 ( ) ;
print_suite ( p_variable - > setter ) ;
decrease_indent ( ) ;
} else {
push_line ( " = " ) ;
increase_indent ( ) ;
print_identifier ( p_variable - > setter_pointer ) ;
push_line ( ) ;
decrease_indent ( ) ;
}
}
}
2020-05-01 22:14:56 +00:00
decrease_indent ( ) ;
push_line ( ) ;
2016-09-12 13:52:29 +00:00
}
2020-05-01 22:14:56 +00: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 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
void GDScriptParser : : TreePrinter : : print_tree ( const GDScriptParser & p_parser ) {
ERR_FAIL_COND_MSG ( p_parser . get_tree ( ) = = nullptr , " Parse the code before printing the parse tree. " ) ;
if ( p_parser . is_tool ( ) ) {
push_line ( " @tool " ) ;
}
2020-12-15 12:04:21 +00:00
if ( ! p_parser . get_tree ( ) - > icon_path . is_empty ( ) ) {
2020-05-01 22:14:56 +00:00
push_text ( R " (@icon ( " ) " );
push_text ( p_parser . get_tree ( ) - > icon_path ) ;
push_line ( " \" ) " ) ;
}
print_class ( p_parser . get_tree ( ) ) ;
print_line ( printed ) ;
2014-02-10 01:10:30 +00:00
}
2020-05-01 22:14:56 +00:00
# endif // DEBUG_ENABLED