2019-01-01 12:46:36 +01:00
/*************************************************************************/
/* script_class_parser.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
2019-01-01 12:53:14 +01:00
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
2019-01-01 12:46:36 +01: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-10-22 19:43:19 +02:00
# include "script_class_parser.h"
# include "core/map.h"
# include "core/os/os.h"
# include "../utils/string_utils.h"
2018-10-28 01:31:17 +02:00
const char * ScriptClassParser : : token_names [ ScriptClassParser : : TK_MAX ] = {
" [ " ,
" ] " ,
" { " ,
" } " ,
" . " ,
" : " ,
" , " ,
" Symbol " ,
" Identifier " ,
" String " ,
" Number " ,
" < " ,
" > " ,
" EOF " ,
" Error "
} ;
String ScriptClassParser : : get_token_name ( ScriptClassParser : : Token p_token ) {
ERR_FAIL_INDEX_V ( p_token , TK_MAX , " <error> " ) ;
return token_names [ p_token ] ;
}
2018-10-22 19:43:19 +02:00
ScriptClassParser : : Token ScriptClassParser : : get_token ( ) {
while ( true ) {
switch ( code [ idx ] ) {
case ' \n ' : {
line + + ;
idx + + ;
break ;
} ;
case 0 : {
return TK_EOF ;
} break ;
case ' { ' : {
idx + + ;
return TK_CURLY_BRACKET_OPEN ;
} ;
case ' } ' : {
idx + + ;
return TK_CURLY_BRACKET_CLOSE ;
} ;
case ' [ ' : {
idx + + ;
return TK_BRACKET_OPEN ;
} ;
case ' ] ' : {
idx + + ;
return TK_BRACKET_CLOSE ;
} ;
case ' < ' : {
idx + + ;
return TK_OP_LESS ;
} ;
case ' > ' : {
idx + + ;
return TK_OP_GREATER ;
} ;
case ' : ' : {
idx + + ;
return TK_COLON ;
} ;
case ' , ' : {
idx + + ;
return TK_COMMA ;
} ;
case ' . ' : {
idx + + ;
return TK_PERIOD ;
} ;
case ' # ' : {
//compiler directive
while ( code [ idx ] ! = ' \n ' & & code [ idx ] ! = 0 ) {
idx + + ;
}
continue ;
} break ;
case ' / ' : {
switch ( code [ idx + 1 ] ) {
case ' * ' : { // block comment
idx + = 2 ;
while ( true ) {
if ( code [ idx ] = = 0 ) {
error_str = " Unterminated comment " ;
error = true ;
return TK_ERROR ;
} else if ( code [ idx ] = = ' * ' & & code [ idx + 1 ] = = ' / ' ) {
idx + = 2 ;
break ;
} else if ( code [ idx ] = = ' \n ' ) {
line + + ;
}
idx + + ;
}
} break ;
case ' / ' : { // line comment skip
while ( code [ idx ] ! = ' \n ' & & code [ idx ] ! = 0 ) {
idx + + ;
}
} break ;
default : {
value = " / " ;
idx + + ;
return TK_SYMBOL ;
}
}
continue ; // a comment
} break ;
case ' \' ' :
case ' " ' : {
bool verbatim = idx ! = 0 & & code [ idx - 1 ] = = ' @ ' ;
CharType begin_str = code [ idx ] ;
idx + + ;
String tk_string = String ( ) ;
while ( true ) {
if ( code [ idx ] = = 0 ) {
error_str = " Unterminated String " ;
error = true ;
return TK_ERROR ;
} else if ( code [ idx ] = = begin_str ) {
if ( verbatim & & code [ idx + 1 ] = = ' " ' ) { // `""` is verbatim string's `\"`
idx + = 2 ; // skip next `"` as well
continue ;
}
idx + = 1 ;
break ;
} else if ( code [ idx ] = = ' \\ ' & & ! verbatim ) {
//escaped characters...
idx + + ;
CharType next = code [ idx ] ;
if ( next = = 0 ) {
error_str = " Unterminated String " ;
error = true ;
return TK_ERROR ;
}
CharType res = 0 ;
switch ( next ) {
case ' b ' : res = 8 ; break ;
case ' t ' : res = 9 ; break ;
case ' n ' : res = 10 ; break ;
case ' f ' : res = 12 ; break ;
case ' r ' :
res = 13 ;
break ;
case ' \" ' : res = ' \" ' ; break ;
case ' \\ ' :
res = ' \\ ' ;
break ;
default : {
res = next ;
} break ;
}
tk_string + = res ;
} else {
if ( code [ idx ] = = ' \n ' )
line + + ;
tk_string + = code [ idx ] ;
}
idx + + ;
}
value = tk_string ;
return TK_STRING ;
} break ;
default : {
if ( code [ idx ] < = 32 ) {
idx + + ;
break ;
}
if ( ( code [ idx ] > = 33 & & code [ idx ] < = 47 ) | | ( code [ idx ] > = 58 & & code [ idx ] < = 63 ) | | ( code [ idx ] > = 91 & & code [ idx ] < = 94 ) | | code [ idx ] = = 96 | | ( code [ idx ] > = 123 & & code [ idx ] < = 127 ) ) {
value = String : : chr ( code [ idx ] ) ;
idx + + ;
return TK_SYMBOL ;
}
if ( code [ idx ] = = ' - ' | | ( code [ idx ] > = ' 0 ' & & code [ idx ] < = ' 9 ' ) ) {
//a number
const CharType * rptr ;
double number = String : : to_double ( & code [ idx ] , & rptr ) ;
idx + = ( rptr - & code [ idx ] ) ;
value = number ;
return TK_NUMBER ;
} else if ( ( code [ idx ] = = ' @ ' & & code [ idx + 1 ] ! = ' " ' ) | | code [ idx ] = = ' _ ' | | ( code [ idx ] > = ' A ' & & code [ idx ] < = ' Z ' ) | | ( code [ idx ] > = ' a ' & & code [ idx ] < = ' z ' ) | | code [ idx ] > 127 ) {
String id ;
id + = code [ idx ] ;
idx + + ;
while ( code [ idx ] = = ' _ ' | | ( code [ idx ] > = ' A ' & & code [ idx ] < = ' Z ' ) | | ( code [ idx ] > = ' a ' & & code [ idx ] < = ' z ' ) | | ( code [ idx ] > = ' 0 ' & & code [ idx ] < = ' 9 ' ) | | code [ idx ] > 127 ) {
id + = code [ idx ] ;
idx + + ;
}
value = id ;
return TK_IDENTIFIER ;
} else if ( code [ idx ] = = ' @ ' & & code [ idx + 1 ] = = ' " ' ) {
// begin of verbatim string
idx + + ;
} else {
error_str = " Unexpected character. " ;
error = true ;
return TK_ERROR ;
}
}
}
}
}
2018-10-28 01:31:17 +02:00
Error ScriptClassParser : : _skip_generic_type_params ( ) {
2018-10-22 19:43:19 +02:00
Token tk ;
while ( true ) {
tk = get_token ( ) ;
if ( tk = = TK_IDENTIFIER ) {
tk = get_token ( ) ;
2019-03-20 17:23:11 +01:00
// Type specifications can end with "?" to denote nullable types, such as IList<int?>
if ( tk = = TK_SYMBOL ) {
tk = get_token ( ) ;
if ( value . operator String ( ) ! = " ? " ) {
error_str = " Expected " + get_token_name ( TK_IDENTIFIER ) + " , found unexpected symbol ' " + value + " ' " ;
error = true ;
return ERR_PARSE_ERROR ;
}
if ( tk ! = TK_OP_GREATER & & tk ! = TK_COMMA ) {
error_str = " Nullable type symbol '?' is only allowed after an identifier, but found " + get_token_name ( tk ) + " next. " ;
error = true ;
return ERR_PARSE_ERROR ;
}
}
2018-10-22 19:43:19 +02:00
2018-10-28 01:31:17 +02:00
if ( tk = = TK_PERIOD ) {
while ( true ) {
tk = get_token ( ) ;
if ( tk ! = TK_IDENTIFIER ) {
error_str = " Expected " + get_token_name ( TK_IDENTIFIER ) + " , found: " + get_token_name ( tk ) ;
error = true ;
return ERR_PARSE_ERROR ;
}
tk = get_token ( ) ;
if ( tk ! = TK_PERIOD )
break ;
}
}
2018-10-22 19:43:19 +02:00
if ( tk = = TK_OP_LESS ) {
2018-10-28 01:31:17 +02:00
Error err = _skip_generic_type_params ( ) ;
2018-10-22 19:43:19 +02:00
if ( err )
return err ;
continue ;
2018-11-05 01:51:59 +01:00
} else if ( tk = = TK_OP_GREATER ) {
return OK ;
2018-10-22 19:43:19 +02:00
} else if ( tk ! = TK_COMMA ) {
2018-10-28 01:31:17 +02:00
error_str = " Unexpected token: " + get_token_name ( tk ) ;
2018-10-22 19:43:19 +02:00
error = true ;
return ERR_PARSE_ERROR ;
}
} else if ( tk = = TK_OP_LESS ) {
2018-10-28 01:31:17 +02:00
error_str = " Expected " + get_token_name ( TK_IDENTIFIER ) + " , found " + get_token_name ( TK_OP_LESS ) ;
2018-10-22 19:43:19 +02:00
error = true ;
return ERR_PARSE_ERROR ;
} else if ( tk = = TK_OP_GREATER ) {
return OK ;
} else {
2018-10-28 01:31:17 +02:00
error_str = " Unexpected token: " + get_token_name ( tk ) ;
2018-10-22 19:43:19 +02:00
error = true ;
return ERR_PARSE_ERROR ;
}
}
}
2018-10-28 01:31:17 +02:00
Error ScriptClassParser : : _parse_type_full_name ( String & r_full_name ) {
2018-10-22 19:43:19 +02:00
2018-10-28 01:31:17 +02:00
Token tk = get_token ( ) ;
2018-10-22 19:43:19 +02:00
2018-10-28 01:31:17 +02:00
if ( tk ! = TK_IDENTIFIER ) {
error_str = " Expected " + get_token_name ( TK_IDENTIFIER ) + " , found: " + get_token_name ( tk ) ;
error = true ;
return ERR_PARSE_ERROR ;
}
2018-10-22 19:43:19 +02:00
2018-10-28 01:31:17 +02:00
r_full_name + = String ( value ) ;
2018-10-22 19:43:19 +02:00
2019-03-01 00:21:46 +01:00
if ( code [ idx ] = = ' < ' ) {
idx + + ;
// We don't mind if the base is generic, but we skip it any ways since this information is not needed
Error err = _skip_generic_type_params ( ) ;
if ( err )
return err ;
}
2018-10-28 01:31:17 +02:00
if ( code [ idx ] ! = ' . ' ) // We only want to take the next token if it's a period
return OK ;
2018-10-22 19:43:19 +02:00
2018-10-28 01:31:17 +02:00
tk = get_token ( ) ;
2018-10-22 19:43:19 +02:00
2018-10-28 01:31:17 +02:00
CRASH_COND ( tk ! = TK_PERIOD ) ; // Assertion
2018-10-22 19:43:19 +02:00
2018-10-28 01:31:17 +02:00
r_full_name + = " . " ;
2018-10-22 19:43:19 +02:00
2018-10-28 01:31:17 +02:00
return _parse_type_full_name ( r_full_name ) ;
}
Error ScriptClassParser : : _parse_class_base ( Vector < String > & r_base ) {
String name ;
Error err = _parse_type_full_name ( name ) ;
if ( err )
return err ;
Token tk = get_token ( ) ;
2018-11-05 01:51:59 +01:00
if ( tk = = TK_COMMA ) {
2019-02-12 21:10:08 +01:00
err = _parse_class_base ( r_base ) ;
2018-10-28 01:31:17 +02:00
if ( err )
return err ;
2018-11-05 01:51:59 +01:00
} else if ( tk = = TK_IDENTIFIER & & String ( value ) = = " where " ) {
2019-02-12 21:10:08 +01:00
err = _parse_type_constraints ( ) ;
2018-11-05 01:51:59 +01:00
if ( err ) {
return err ;
}
// An open curly bracket was parsed by _parse_type_constraints, so we can exit
2018-10-28 01:31:17 +02:00
} else if ( tk = = TK_CURLY_BRACKET_OPEN ) {
2018-11-05 01:51:59 +01:00
// we are finished when we hit the open curly bracket
2018-10-28 01:31:17 +02:00
} else {
error_str = " Unexpected token: " + get_token_name ( tk ) ;
error = true ;
return ERR_PARSE_ERROR ;
2018-10-22 19:43:19 +02:00
}
2018-10-28 01:31:17 +02:00
2019-03-01 00:21:46 +01:00
r_base . push_back ( name ) ;
2018-11-05 01:51:59 +01:00
2018-10-28 01:31:17 +02:00
return OK ;
2018-10-22 19:43:19 +02:00
}
2018-11-05 01:51:59 +01:00
Error ScriptClassParser : : _parse_type_constraints ( ) {
Token tk = get_token ( ) ;
if ( tk ! = TK_IDENTIFIER ) {
error_str = " Unexpected token: " + get_token_name ( tk ) ;
error = true ;
return ERR_PARSE_ERROR ;
}
tk = get_token ( ) ;
if ( tk ! = TK_COLON ) {
error_str = " Unexpected token: " + get_token_name ( tk ) ;
error = true ;
return ERR_PARSE_ERROR ;
}
while ( true ) {
tk = get_token ( ) ;
if ( tk = = TK_IDENTIFIER ) {
if ( String ( value ) = = " where " ) {
return _parse_type_constraints ( ) ;
}
tk = get_token ( ) ;
if ( tk = = TK_PERIOD ) {
while ( true ) {
tk = get_token ( ) ;
if ( tk ! = TK_IDENTIFIER ) {
error_str = " Expected " + get_token_name ( TK_IDENTIFIER ) + " , found: " + get_token_name ( tk ) ;
error = true ;
return ERR_PARSE_ERROR ;
}
tk = get_token ( ) ;
if ( tk ! = TK_PERIOD )
break ;
}
}
}
if ( tk = = TK_COMMA ) {
continue ;
} else if ( tk = = TK_IDENTIFIER & & String ( value ) = = " where " ) {
return _parse_type_constraints ( ) ;
} else if ( tk = = TK_SYMBOL & & String ( value ) = = " ( " ) {
tk = get_token ( ) ;
if ( tk ! = TK_SYMBOL | | String ( value ) ! = " ) " ) {
error_str = " Unexpected token: " + get_token_name ( tk ) ;
error = true ;
return ERR_PARSE_ERROR ;
}
} else if ( tk = = TK_OP_LESS ) {
Error err = _skip_generic_type_params ( ) ;
if ( err )
return err ;
} else if ( tk = = TK_CURLY_BRACKET_OPEN ) {
return OK ;
} else {
error_str = " Unexpected token: " + get_token_name ( tk ) ;
error = true ;
return ERR_PARSE_ERROR ;
}
}
}
2018-10-22 19:43:19 +02:00
Error ScriptClassParser : : _parse_namespace_name ( String & r_name , int & r_curly_stack ) {
Token tk = get_token ( ) ;
if ( tk = = TK_IDENTIFIER ) {
r_name + = String ( value ) ;
} else {
2018-10-28 01:31:17 +02:00
error_str = " Unexpected token: " + get_token_name ( tk ) ;
2018-10-22 19:43:19 +02:00
error = true ;
return ERR_PARSE_ERROR ;
}
tk = get_token ( ) ;
if ( tk = = TK_PERIOD ) {
r_name + = " . " ;
return _parse_namespace_name ( r_name , r_curly_stack ) ;
} else if ( tk = = TK_CURLY_BRACKET_OPEN ) {
r_curly_stack + + ;
return OK ;
} else {
2018-10-28 01:31:17 +02:00
error_str = " Unexpected token: " + get_token_name ( tk ) ;
2018-10-22 19:43:19 +02:00
error = true ;
return ERR_PARSE_ERROR ;
}
}
Error ScriptClassParser : : parse ( const String & p_code ) {
code = p_code ;
idx = 0 ;
line = 0 ;
error_str = String ( ) ;
error = false ;
value = Variant ( ) ;
classes . clear ( ) ;
Token tk = get_token ( ) ;
Map < int , NameDecl > name_stack ;
int curly_stack = 0 ;
int type_curly_stack = 0 ;
while ( ! error & & tk ! = TK_EOF ) {
if ( tk = = TK_IDENTIFIER & & String ( value ) = = " class " ) {
tk = get_token ( ) ;
if ( tk = = TK_IDENTIFIER ) {
String name = value ;
int at_level = type_curly_stack ;
ClassDecl class_decl ;
for ( Map < int , NameDecl > : : Element * E = name_stack . front ( ) ; E ; E = E - > next ( ) ) {
const NameDecl & name_decl = E - > value ( ) ;
if ( name_decl . type = = NameDecl : : NAMESPACE_DECL ) {
if ( E ! = name_stack . front ( ) )
class_decl . namespace_ + = " . " ;
class_decl . namespace_ + = name_decl . name ;
} else {
class_decl . name + = name_decl . name + " . " ;
}
}
class_decl . name + = name ;
class_decl . nested = type_curly_stack > 0 ;
bool generic = false ;
while ( true ) {
tk = get_token ( ) ;
if ( tk = = TK_COLON ) {
Error err = _parse_class_base ( class_decl . base ) ;
if ( err )
return err ;
curly_stack + + ;
type_curly_stack + + ;
break ;
} else if ( tk = = TK_CURLY_BRACKET_OPEN ) {
curly_stack + + ;
type_curly_stack + + ;
break ;
} else if ( tk = = TK_OP_LESS & & ! generic ) {
generic = true ;
2018-10-28 01:31:17 +02:00
Error err = _skip_generic_type_params ( ) ;
2018-10-22 19:43:19 +02:00
if ( err )
return err ;
2018-11-05 01:51:59 +01:00
} else if ( tk = = TK_IDENTIFIER & & String ( value ) = = " where " ) {
Error err = _parse_type_constraints ( ) ;
if ( err ) {
return err ;
}
// An open curly bracket was parsed by _parse_type_constraints, so we can exit
curly_stack + + ;
type_curly_stack + + ;
break ;
2018-10-22 19:43:19 +02:00
} else {
2018-10-28 01:31:17 +02:00
error_str = " Unexpected token: " + get_token_name ( tk ) ;
2018-10-22 19:43:19 +02:00
error = true ;
return ERR_PARSE_ERROR ;
}
}
NameDecl name_decl ;
name_decl . name = name ;
name_decl . type = NameDecl : : CLASS_DECL ;
name_stack [ at_level ] = name_decl ;
if ( ! generic ) { // no generics, thanks
classes . push_back ( class_decl ) ;
} else if ( OS : : get_singleton ( ) - > is_stdout_verbose ( ) ) {
String full_name = class_decl . namespace_ ;
if ( full_name . length ( ) )
full_name + = " . " ;
full_name + = class_decl . name ;
2019-03-01 00:21:46 +01:00
OS : : get_singleton ( ) - > print ( " Ignoring generic class declaration: %s \n " , class_decl . name . utf8 ( ) . get_data ( ) ) ;
2018-10-22 19:43:19 +02:00
}
}
} else if ( tk = = TK_IDENTIFIER & & String ( value ) = = " struct " ) {
String name ;
int at_level = type_curly_stack ;
while ( true ) {
tk = get_token ( ) ;
if ( tk = = TK_IDENTIFIER & & name . empty ( ) ) {
name = String ( value ) ;
} else if ( tk = = TK_CURLY_BRACKET_OPEN ) {
if ( name . empty ( ) ) {
2018-10-28 01:31:17 +02:00
error_str = " Expected " + get_token_name ( TK_IDENTIFIER ) + " after keyword `struct`, found " + get_token_name ( TK_CURLY_BRACKET_OPEN ) ;
2018-10-22 19:43:19 +02:00
error = true ;
return ERR_PARSE_ERROR ;
}
curly_stack + + ;
type_curly_stack + + ;
break ;
} else if ( tk = = TK_EOF ) {
2018-10-28 01:31:17 +02:00
error_str = " Expected " + get_token_name ( TK_CURLY_BRACKET_OPEN ) + " after struct decl, found " + get_token_name ( TK_EOF ) ;
2018-10-22 19:43:19 +02:00
error = true ;
return ERR_PARSE_ERROR ;
}
}
NameDecl name_decl ;
name_decl . name = name ;
name_decl . type = NameDecl : : STRUCT_DECL ;
name_stack [ at_level ] = name_decl ;
} else if ( tk = = TK_IDENTIFIER & & String ( value ) = = " namespace " ) {
if ( type_curly_stack > 0 ) {
error_str = " Found namespace nested inside type. " ;
error = true ;
return ERR_PARSE_ERROR ;
}
String name ;
int at_level = curly_stack ;
Error err = _parse_namespace_name ( name , curly_stack ) ;
if ( err )
return err ;
NameDecl name_decl ;
name_decl . name = name ;
name_decl . type = NameDecl : : NAMESPACE_DECL ;
name_stack [ at_level ] = name_decl ;
} else if ( tk = = TK_CURLY_BRACKET_OPEN ) {
curly_stack + + ;
} else if ( tk = = TK_CURLY_BRACKET_CLOSE ) {
curly_stack - - ;
if ( name_stack . has ( curly_stack ) ) {
if ( name_stack [ curly_stack ] . type ! = NameDecl : : NAMESPACE_DECL )
type_curly_stack - - ;
name_stack . erase ( curly_stack ) ;
}
}
tk = get_token ( ) ;
}
if ( ! error & & tk = = TK_EOF & & curly_stack > 0 ) {
error_str = " Reached EOF with missing close curly brackets. " ;
error = true ;
}
if ( error )
return ERR_PARSE_ERROR ;
return OK ;
}
Error ScriptClassParser : : parse_file ( const String & p_filepath ) {
String source ;
Error ferr = read_all_file_utf8 ( p_filepath , source ) ;
if ( ferr ! = OK ) {
if ( ferr = = ERR_INVALID_DATA ) {
ERR_EXPLAIN ( " File ' " + p_filepath + " ' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode. " ) ;
}
ERR_FAIL_V ( ferr ) ;
}
return parse ( source ) ;
}
String ScriptClassParser : : get_error ( ) {
return error_str ;
}
Vector < ScriptClassParser : : ClassDecl > ScriptClassParser : : get_classes ( ) {
return classes ;
}