mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-20 02:46:53 +01:00
Relintai
bb5acfd745
Backports features and bugfixes from current Godot 4.0 to 3.5 and brings functions and codebase of both version largely in sync to make tutorials more compatible and future backports easier.
- smix8
8bd7c6188b
8719 lines
272 KiB
C++
8719 lines
272 KiB
C++
/*************************************************************************/
|
|
/* gdscript_parser.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
|
/* */
|
|
/* 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. */
|
|
/*************************************************************************/
|
|
|
|
#include "cscript_parser.h"
|
|
|
|
#include "core/core_string_names.h"
|
|
#include "core/engine.h"
|
|
#include "core/io/resource_loader.h"
|
|
#include "core/os/file_access.h"
|
|
#include "core/print_string.h"
|
|
#include "core/project_settings.h"
|
|
#include "core/reference.h"
|
|
#include "core/script_language.h"
|
|
#include "cscript.h"
|
|
|
|
template <class T>
|
|
T *CScriptParser::alloc_node() {
|
|
T *t = memnew(T);
|
|
|
|
t->next = list;
|
|
list = t;
|
|
|
|
if (!head) {
|
|
head = t;
|
|
}
|
|
|
|
t->line = tokenizer->get_token_line();
|
|
t->column = tokenizer->get_token_column();
|
|
return t;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
static String _find_function_name(const CScriptParser::OperatorNode *p_call);
|
|
#endif // DEBUG_ENABLED
|
|
|
|
bool CScriptParser::_end_statement() {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_SEMICOLON) {
|
|
tokenizer->advance();
|
|
return true; //handle next
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == CScriptTokenizer::TK_EOF) {
|
|
return true; //will be handled properly
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CScriptParser::_set_end_statement_error(String p_name) {
|
|
String error_msg;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER) {
|
|
error_msg = vformat("Expected end of statement (\"%s\"), got %s (\"%s\") instead.", p_name, tokenizer->get_token_name(tokenizer->get_token()), tokenizer->get_token_identifier());
|
|
} else {
|
|
error_msg = vformat("Expected end of statement (\"%s\"), got %s instead.", p_name, tokenizer->get_token_name(tokenizer->get_token()));
|
|
}
|
|
_set_error(error_msg);
|
|
}
|
|
|
|
bool CScriptParser::_enter_indent_block(BlockNode *p_block) {
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_COLON) {
|
|
// report location at the previous token (on the previous line)
|
|
int error_line = tokenizer->get_token_line(-1);
|
|
int error_column = tokenizer->get_token_column(-1);
|
|
_set_error("':' expected at end of line.", error_line, error_column);
|
|
return false;
|
|
}
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_EOF) {
|
|
return false;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_NEWLINE) {
|
|
// be more python-like
|
|
IndentLevel current_level = indent_level.back()->get();
|
|
indent_level.push_back(current_level);
|
|
return true;
|
|
//_set_error("newline expected after ':'.");
|
|
//return false;
|
|
}
|
|
|
|
while (true) {
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_NEWLINE) {
|
|
return false; //wtf
|
|
} else if (tokenizer->get_token(1) == CScriptTokenizer::TK_EOF) {
|
|
return false;
|
|
} else if (tokenizer->get_token(1) != CScriptTokenizer::TK_NEWLINE) {
|
|
int indent = tokenizer->get_token_line_indent();
|
|
int tabs = tokenizer->get_token_line_tab_indent();
|
|
IndentLevel current_level = indent_level.back()->get();
|
|
IndentLevel new_indent(indent, tabs);
|
|
if (new_indent.is_mixed(current_level)) {
|
|
_set_error("Mixed tabs and spaces in indentation.");
|
|
return false;
|
|
}
|
|
|
|
if (indent <= current_level.indent) {
|
|
return false;
|
|
}
|
|
|
|
indent_level.push_back(new_indent);
|
|
tokenizer->advance();
|
|
return true;
|
|
|
|
} else if (p_block) {
|
|
NewLineNode *nl = alloc_node<NewLineNode>();
|
|
nl->line = tokenizer->get_token_line();
|
|
p_block->statements.push_back(nl);
|
|
}
|
|
|
|
tokenizer->advance(); // go to next newline
|
|
}
|
|
}
|
|
|
|
bool CScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete, bool p_parsing_constant) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
tokenizer->advance();
|
|
} else {
|
|
parenthesis++;
|
|
int argidx = 0;
|
|
|
|
while (true) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CURSOR) {
|
|
_make_completable_call(argidx);
|
|
completion_node = p_parent;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING && tokenizer->get_token(1) == CScriptTokenizer::TK_CURSOR) {
|
|
//completing a string argument..
|
|
completion_cursor = tokenizer->get_token_constant();
|
|
|
|
_make_completable_call(argidx);
|
|
completion_node = p_parent;
|
|
tokenizer->advance(1);
|
|
return false;
|
|
}
|
|
|
|
Node *arg = _parse_expression(p_parent, p_static, false, p_parsing_constant);
|
|
if (!arg) {
|
|
return false;
|
|
}
|
|
|
|
p_args.push_back(arg);
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
if (tokenizer->get_token(1) == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expression expected");
|
|
return false;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
argidx++;
|
|
} else {
|
|
// something is broken
|
|
_set_error("Expected ',' or ')'");
|
|
return false;
|
|
}
|
|
}
|
|
parenthesis--;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CScriptParser::_make_completable_call(int p_arg) {
|
|
completion_cursor = StringName();
|
|
completion_type = COMPLETION_CALL_ARGUMENTS;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_argument = p_arg;
|
|
completion_block = current_block;
|
|
completion_found = true;
|
|
tokenizer->advance();
|
|
}
|
|
|
|
bool CScriptParser::_get_completable_identifier(CompletionType p_type, StringName &identifier) {
|
|
identifier = StringName();
|
|
if (tokenizer->is_token_literal()) {
|
|
identifier = tokenizer->get_token_literal();
|
|
tokenizer->advance();
|
|
}
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CURSOR) {
|
|
completion_cursor = identifier;
|
|
completion_type = p_type;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_block = current_block;
|
|
completion_found = true;
|
|
completion_ident_is_call = false;
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->is_token_literal()) {
|
|
identifier = identifier.operator String() + tokenizer->get_token_literal().operator String();
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
completion_ident_is_call = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CScriptParser::Node *CScriptParser::_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign, bool p_parsing_constant) {
|
|
//Vector<Node*> expressions;
|
|
//Vector<OperatorNode::Operator> operators;
|
|
|
|
Vector<Expression> expression;
|
|
|
|
Node *expr = nullptr;
|
|
|
|
int op_line = tokenizer->get_token_line(); // when operators are created at the bottom, the line might have been changed (\n found)
|
|
|
|
while (true) {
|
|
/*****************/
|
|
/* Parse Operand */
|
|
/*****************/
|
|
|
|
if (parenthesis > 0) {
|
|
//remove empty space (only allowed if inside parenthesis
|
|
while (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
}
|
|
}
|
|
|
|
// Check that the next token is not TK_CURSOR and if it is, the offset should be incremented.
|
|
int next_valid_offset = 1;
|
|
if (tokenizer->get_token(next_valid_offset) == CScriptTokenizer::TK_CURSOR) {
|
|
next_valid_offset++;
|
|
// There is a chunk of the identifier that also needs to be ignored (not always there!)
|
|
if (tokenizer->get_token(next_valid_offset) == CScriptTokenizer::TK_IDENTIFIER) {
|
|
next_valid_offset++;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
//subexpression ()
|
|
tokenizer->advance();
|
|
parenthesis++;
|
|
Node *subexpr = _parse_expression(p_parent, p_static, p_allow_assign, p_parsing_constant);
|
|
parenthesis--;
|
|
if (!subexpr) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected ')' in expression");
|
|
return nullptr;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
expr = subexpr;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_DOLLAR) {
|
|
tokenizer->advance();
|
|
|
|
String path;
|
|
|
|
bool need_identifier = true;
|
|
bool done = false;
|
|
int line = tokenizer->get_token_line();
|
|
|
|
while (!done) {
|
|
switch (tokenizer->get_token()) {
|
|
case CScriptTokenizer::TK_CURSOR: {
|
|
completion_type = COMPLETION_GET_NODE;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_cursor = path;
|
|
completion_argument = 0;
|
|
completion_block = current_block;
|
|
completion_found = true;
|
|
tokenizer->advance();
|
|
} break;
|
|
case CScriptTokenizer::TK_CONSTANT: {
|
|
if (!need_identifier) {
|
|
done = true;
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token_constant().get_type() != Variant::STRING) {
|
|
_set_error("Expected string constant or identifier after '$' or '/'.");
|
|
return nullptr;
|
|
}
|
|
|
|
path += String(tokenizer->get_token_constant());
|
|
tokenizer->advance();
|
|
need_identifier = false;
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_OP_DIV: {
|
|
if (need_identifier) {
|
|
done = true;
|
|
break;
|
|
}
|
|
|
|
path += "/";
|
|
tokenizer->advance();
|
|
need_identifier = true;
|
|
|
|
} break;
|
|
default: {
|
|
// Instead of checking for TK_IDENTIFIER, we check with is_token_literal, as this allows us to use match/sync/etc. as a name
|
|
if (need_identifier && tokenizer->is_token_literal()) {
|
|
path += String(tokenizer->get_token_literal());
|
|
tokenizer->advance();
|
|
need_identifier = false;
|
|
} else {
|
|
done = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (path == "") {
|
|
_set_error("Path expected after $.");
|
|
return nullptr;
|
|
}
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_CALL;
|
|
op->line = line;
|
|
op->arguments.push_back(alloc_node<SelfNode>());
|
|
op->arguments[0]->line = line;
|
|
|
|
IdentifierNode *funcname = alloc_node<IdentifierNode>();
|
|
funcname->name = "get_node";
|
|
funcname->line = line;
|
|
op->arguments.push_back(funcname);
|
|
|
|
ConstantNode *nodepath = alloc_node<ConstantNode>();
|
|
nodepath->value = NodePath(StringName(path));
|
|
nodepath->datatype = _type_from_variant(nodepath->value);
|
|
nodepath->line = line;
|
|
op->arguments.push_back(nodepath);
|
|
|
|
expr = op;
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CURSOR) {
|
|
tokenizer->advance();
|
|
continue; //no point in cursor in the middle of expression
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CONSTANT) {
|
|
//constant defined by tokenizer
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = tokenizer->get_token_constant();
|
|
constant->datatype = _type_from_variant(constant->value);
|
|
tokenizer->advance();
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CONST_PI) {
|
|
//constant defined by tokenizer
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = Math_PI;
|
|
constant->datatype = _type_from_variant(constant->value);
|
|
tokenizer->advance();
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CONST_TAU) {
|
|
//constant defined by tokenizer
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = Math_TAU;
|
|
constant->datatype = _type_from_variant(constant->value);
|
|
tokenizer->advance();
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CONST_INF) {
|
|
//constant defined by tokenizer
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = Math_INF;
|
|
constant->datatype = _type_from_variant(constant->value);
|
|
tokenizer->advance();
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CONST_NAN) {
|
|
//constant defined by tokenizer
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = Math_NAN;
|
|
constant->datatype = _type_from_variant(constant->value);
|
|
tokenizer->advance();
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_PR_PRELOAD) {
|
|
//constant defined by tokenizer
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
_set_error("Expected '(' after 'preload'");
|
|
return nullptr;
|
|
}
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CURSOR) {
|
|
completion_cursor = StringName();
|
|
completion_node = p_parent;
|
|
completion_type = COMPLETION_RESOURCE_PATH;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_block = current_block;
|
|
completion_argument = 0;
|
|
completion_found = true;
|
|
tokenizer->advance();
|
|
}
|
|
|
|
String path;
|
|
bool found_constant = false;
|
|
bool valid = false;
|
|
ConstantNode *cn;
|
|
|
|
parenthesis++;
|
|
Node *subexpr = _parse_and_reduce_expression(p_parent, p_static);
|
|
parenthesis--;
|
|
if (subexpr) {
|
|
if (subexpr->type == Node::TYPE_CONSTANT) {
|
|
cn = static_cast<ConstantNode *>(subexpr);
|
|
found_constant = true;
|
|
}
|
|
if (subexpr->type == Node::TYPE_IDENTIFIER) {
|
|
IdentifierNode *in = static_cast<IdentifierNode *>(subexpr);
|
|
|
|
// Try to find the constant expression by the identifier
|
|
if (current_class->constant_expressions.has(in->name)) {
|
|
Node *cn_exp = current_class->constant_expressions[in->name].expression;
|
|
if (cn_exp->type == Node::TYPE_CONSTANT) {
|
|
cn = static_cast<ConstantNode *>(cn_exp);
|
|
found_constant = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found_constant && cn->value.get_type() == Variant::STRING) {
|
|
valid = true;
|
|
path = (String)cn->value;
|
|
}
|
|
}
|
|
|
|
if (!valid) {
|
|
_set_error("expected string constant as 'preload' argument.");
|
|
return nullptr;
|
|
}
|
|
|
|
if (!path.is_abs_path() && base_path != "") {
|
|
path = base_path.plus_file(path);
|
|
}
|
|
path = path.replace("///", "//").simplify_path();
|
|
if (path == self_path) {
|
|
_set_error("Can't preload itself (use 'get_script()').");
|
|
return nullptr;
|
|
}
|
|
|
|
Ref<Resource> res;
|
|
dependencies.push_back(path);
|
|
if (!dependencies_only) {
|
|
if (!validating) {
|
|
//this can be too slow for just validating code
|
|
if (for_completion && ScriptCodeCompletionCache::get_singleton() && FileAccess::exists(path)) {
|
|
res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path);
|
|
} else if (!for_completion || FileAccess::exists(path)) {
|
|
res = ResourceLoader::load(path);
|
|
}
|
|
} else {
|
|
if (!FileAccess::exists(path)) {
|
|
_set_error("Can't preload resource at path: " + path);
|
|
return nullptr;
|
|
} else if (ScriptCodeCompletionCache::get_singleton()) {
|
|
res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path);
|
|
}
|
|
}
|
|
if (!res.is_valid()) {
|
|
_set_error("Can't preload resource at path: " + path);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected ')' after 'preload' path");
|
|
return nullptr;
|
|
}
|
|
|
|
Ref<CScript> gds = res;
|
|
if (gds.is_valid() && !gds->is_valid()) {
|
|
_set_error("Couldn't fully preload the script, possible cyclic reference or compilation error. Use \"load()\" instead if a cyclic reference is intended.");
|
|
return nullptr;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = res;
|
|
constant->datatype = _type_from_variant(constant->value);
|
|
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_SELF) {
|
|
if (p_static) {
|
|
_set_error("\"self\" isn't allowed in a static function or constant expression.");
|
|
return nullptr;
|
|
}
|
|
//constant defined by tokenizer
|
|
SelfNode *self = alloc_node<SelfNode>();
|
|
tokenizer->advance();
|
|
expr = self;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token(1) == CScriptTokenizer::TK_PERIOD) {
|
|
Variant::Type bi_type = tokenizer->get_token_type();
|
|
tokenizer->advance(2);
|
|
|
|
StringName identifier;
|
|
|
|
if (_get_completable_identifier(COMPLETION_BUILT_IN_TYPE_CONSTANT, identifier)) {
|
|
completion_built_in_constant = bi_type;
|
|
}
|
|
|
|
if (identifier == StringName()) {
|
|
_set_error("Built-in type constant or static function expected after \".\".");
|
|
return nullptr;
|
|
}
|
|
if (!Variant::has_constant(bi_type, identifier)) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_OPEN &&
|
|
Variant::is_method_const(bi_type, identifier) &&
|
|
Variant::get_method_return_type(bi_type, identifier) == bi_type) {
|
|
tokenizer->advance();
|
|
|
|
OperatorNode *construct = alloc_node<OperatorNode>();
|
|
construct->op = OperatorNode::OP_CALL;
|
|
|
|
TypeNode *tn = alloc_node<TypeNode>();
|
|
tn->vtype = bi_type;
|
|
construct->arguments.push_back(tn);
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_CALL;
|
|
op->arguments.push_back(construct);
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = identifier;
|
|
op->arguments.push_back(id);
|
|
|
|
if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) {
|
|
return nullptr;
|
|
}
|
|
|
|
expr = op;
|
|
} else {
|
|
// Object is a special case
|
|
bool valid = false;
|
|
if (bi_type == Variant::OBJECT) {
|
|
int object_constant = ClassDB::get_integer_constant("Object", identifier, &valid);
|
|
if (valid) {
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = object_constant;
|
|
cn->datatype = _type_from_variant(cn->value);
|
|
expr = cn;
|
|
}
|
|
}
|
|
if (!valid) {
|
|
_set_error("Static constant '" + identifier.operator String() + "' not present in built-in type " + Variant::get_type_name(bi_type) + ".");
|
|
return nullptr;
|
|
}
|
|
}
|
|
} else {
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = Variant::get_constant_value(bi_type, identifier);
|
|
cn->datatype = _type_from_variant(cn->value);
|
|
expr = cn;
|
|
}
|
|
|
|
} else if (tokenizer->get_token(next_valid_offset) == CScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) {
|
|
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
|
|
//function or constructor
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_CALL;
|
|
|
|
//Do a quick Array and Dictionary Check. Replace if either require no arguments.
|
|
bool replaced = false;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_BUILT_IN_TYPE) {
|
|
Variant::Type ct = tokenizer->get_token_type();
|
|
if (!p_parsing_constant) {
|
|
if (ct == Variant::ARRAY) {
|
|
if (tokenizer->get_token(2) == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
ArrayNode *arr = alloc_node<ArrayNode>();
|
|
expr = arr;
|
|
replaced = true;
|
|
tokenizer->advance(3);
|
|
}
|
|
}
|
|
if (ct == Variant::DICTIONARY) {
|
|
if (tokenizer->get_token(2) == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
DictionaryNode *dict = alloc_node<DictionaryNode>();
|
|
expr = dict;
|
|
replaced = true;
|
|
tokenizer->advance(3);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!replaced) {
|
|
TypeNode *tn = alloc_node<TypeNode>();
|
|
tn->vtype = tokenizer->get_token_type();
|
|
op->arguments.push_back(tn);
|
|
tokenizer->advance(2);
|
|
}
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_BUILT_IN_FUNC) {
|
|
BuiltInFunctionNode *bn = alloc_node<BuiltInFunctionNode>();
|
|
bn->function = tokenizer->get_token_built_in_func();
|
|
op->arguments.push_back(bn);
|
|
tokenizer->advance(2);
|
|
} else {
|
|
SelfNode *self = alloc_node<SelfNode>();
|
|
op->arguments.push_back(self);
|
|
|
|
StringName identifier;
|
|
if (_get_completable_identifier(COMPLETION_FUNCTION, identifier)) {
|
|
}
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = identifier;
|
|
op->arguments.push_back(id);
|
|
tokenizer->advance(1);
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CURSOR) {
|
|
_make_completable_call(0);
|
|
completion_node = op;
|
|
}
|
|
if (!replaced) {
|
|
if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) {
|
|
return nullptr;
|
|
}
|
|
expr = op;
|
|
}
|
|
} else if (tokenizer->is_token_literal(0, true)) {
|
|
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
|
|
//identifier (reference)
|
|
|
|
const ClassNode *cln = current_class;
|
|
bool bfn = false;
|
|
StringName identifier;
|
|
int id_line = tokenizer->get_token_line();
|
|
if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) {
|
|
}
|
|
|
|
BlockNode *b = current_block;
|
|
while (!bfn && b) {
|
|
if (b->variables.has(identifier)) {
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = identifier;
|
|
id->declared_block = b;
|
|
id->line = id_line;
|
|
expr = id;
|
|
bfn = true;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
LocalVarNode *lv = b->variables[identifier];
|
|
switch (tokenizer->get_token()) {
|
|
case CScriptTokenizer::TK_OP_ASSIGN_ADD:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_DIV:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_MOD:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_MUL:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_SUB: {
|
|
if (lv->assignments == 0) {
|
|
if (!lv->datatype.has_type) {
|
|
_set_error("Using assignment with operation on a variable that was never assigned.");
|
|
return nullptr;
|
|
}
|
|
_add_warning(CScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, -1, identifier.operator String());
|
|
}
|
|
FALLTHROUGH;
|
|
}
|
|
case CScriptTokenizer::TK_OP_ASSIGN: {
|
|
lv->assignments += 1;
|
|
lv->usages--; // Assignment is not really usage
|
|
} break;
|
|
default: {
|
|
lv->usages++;
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
break;
|
|
}
|
|
b = b->parent_block;
|
|
}
|
|
|
|
if (!bfn && p_parsing_constant) {
|
|
if (cln->constant_expressions.has(identifier)) {
|
|
expr = cln->constant_expressions[identifier].expression;
|
|
bfn = true;
|
|
} else if (CScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
|
|
//check from constants
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = CScriptLanguage::get_singleton()->get_global_array()[CScriptLanguage::get_singleton()->get_global_map()[identifier]];
|
|
constant->datatype = _type_from_variant(constant->value);
|
|
constant->line = id_line;
|
|
expr = constant;
|
|
bfn = true;
|
|
}
|
|
|
|
if (!bfn && CScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
|
|
//check from singletons
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = CScriptLanguage::get_singleton()->get_named_globals_map()[identifier];
|
|
expr = constant;
|
|
bfn = true;
|
|
}
|
|
|
|
if (!dependencies_only) {
|
|
if (!bfn && ScriptServer::is_global_class(identifier)) {
|
|
Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
|
|
if (scr.is_valid() && scr->is_valid()) {
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = scr;
|
|
expr = constant;
|
|
bfn = true;
|
|
}
|
|
}
|
|
|
|
// Check parents for the constant
|
|
if (!bfn) {
|
|
// Using current_class instead of cln here, since cln is const*
|
|
_determine_inheritance(current_class, false);
|
|
if (cln->base_type.has_type && cln->base_type.kind == DataType::CSCRIPT && cln->base_type.script_type->is_valid()) {
|
|
Map<StringName, Variant> parent_constants;
|
|
current_class->base_type.script_type->get_constants(&parent_constants);
|
|
if (parent_constants.has(identifier)) {
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = parent_constants[identifier];
|
|
expr = constant;
|
|
bfn = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bfn) {
|
|
#ifdef DEBUG_ENABLED
|
|
if (current_function) {
|
|
int arg_idx = current_function->arguments.find(identifier);
|
|
if (arg_idx != -1) {
|
|
switch (tokenizer->get_token()) {
|
|
case CScriptTokenizer::TK_OP_ASSIGN_ADD:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_DIV:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_MOD:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_MUL:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
|
|
case CScriptTokenizer::TK_OP_ASSIGN_SUB:
|
|
case CScriptTokenizer::TK_OP_ASSIGN: {
|
|
// Assignment is not really usage
|
|
} break;
|
|
default: {
|
|
current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = identifier;
|
|
id->line = id_line;
|
|
expr = id;
|
|
}
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_OP_ADD || tokenizer->get_token() == CScriptTokenizer::TK_OP_SUB || tokenizer->get_token() == CScriptTokenizer::TK_OP_NOT || tokenizer->get_token() == CScriptTokenizer::TK_OP_BIT_INVERT) {
|
|
//single prefix operators like !expr +expr -expr ++expr --expr
|
|
alloc_node<OperatorNode>();
|
|
Expression e;
|
|
e.is_op = true;
|
|
|
|
switch (tokenizer->get_token()) {
|
|
case CScriptTokenizer::TK_OP_ADD:
|
|
e.op = OperatorNode::OP_POS;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_SUB:
|
|
e.op = OperatorNode::OP_NEG;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_NOT:
|
|
e.op = OperatorNode::OP_NOT;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_BIT_INVERT:
|
|
e.op = OperatorNode::OP_BIT_INVERT;
|
|
break;
|
|
default: {
|
|
}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
if (e.op != OperatorNode::OP_NOT && tokenizer->get_token() == CScriptTokenizer::TK_OP_NOT) {
|
|
_set_error("Misplaced 'not'.");
|
|
return nullptr;
|
|
}
|
|
|
|
expression.push_back(e);
|
|
continue; //only exception, must continue...
|
|
|
|
/*
|
|
Node *subexpr=_parse_expression(op,p_static);
|
|
if (!subexpr)
|
|
return NULL;
|
|
op->arguments.push_back(subexpr);
|
|
expr=op;*/
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_PR_IS && tokenizer->get_token(1) == CScriptTokenizer::TK_BUILT_IN_TYPE) {
|
|
// 'is' operator with built-in type
|
|
if (!expr) {
|
|
_set_error("Expected identifier before 'is' operator");
|
|
return nullptr;
|
|
}
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_IS_BUILTIN;
|
|
op->arguments.push_back(expr);
|
|
|
|
tokenizer->advance();
|
|
|
|
TypeNode *tn = alloc_node<TypeNode>();
|
|
tn->vtype = tokenizer->get_token_type();
|
|
op->arguments.push_back(tn);
|
|
tokenizer->advance();
|
|
|
|
expr = op;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_BRACKET_OPEN) {
|
|
// array
|
|
tokenizer->advance();
|
|
|
|
ArrayNode *arr = alloc_node<ArrayNode>();
|
|
bool expecting_comma = false;
|
|
|
|
while (true) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_EOF) {
|
|
_set_error("Unterminated array");
|
|
return nullptr;
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance(); //ignore newline
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
if (!expecting_comma) {
|
|
_set_error("expression or ']' expected");
|
|
return nullptr;
|
|
}
|
|
|
|
expecting_comma = false;
|
|
tokenizer->advance(); //ignore newline
|
|
} else {
|
|
//parse expression
|
|
if (expecting_comma) {
|
|
_set_error("',' or ']' expected");
|
|
return nullptr;
|
|
}
|
|
Node *n = _parse_expression(arr, p_static, p_allow_assign, p_parsing_constant);
|
|
if (!n) {
|
|
return nullptr;
|
|
}
|
|
arr->elements.push_back(n);
|
|
expecting_comma = true;
|
|
}
|
|
}
|
|
|
|
expr = arr;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CURLY_BRACKET_OPEN) {
|
|
// array
|
|
tokenizer->advance();
|
|
|
|
DictionaryNode *dict = alloc_node<DictionaryNode>();
|
|
|
|
enum DictExpect {
|
|
|
|
DICT_EXPECT_KEY,
|
|
DICT_EXPECT_COLON,
|
|
DICT_EXPECT_VALUE,
|
|
DICT_EXPECT_COMMA
|
|
|
|
};
|
|
|
|
Node *key = nullptr;
|
|
Set<Variant> keys;
|
|
|
|
DictExpect expecting = DICT_EXPECT_KEY;
|
|
|
|
while (true) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_EOF) {
|
|
_set_error("Unterminated dictionary");
|
|
return nullptr;
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
if (expecting == DICT_EXPECT_COLON) {
|
|
_set_error("':' expected");
|
|
return nullptr;
|
|
}
|
|
if (expecting == DICT_EXPECT_VALUE) {
|
|
_set_error("value expected");
|
|
return nullptr;
|
|
}
|
|
tokenizer->advance();
|
|
break;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance(); //ignore newline
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
if (expecting == DICT_EXPECT_KEY) {
|
|
_set_error("key or '}' expected");
|
|
return nullptr;
|
|
}
|
|
if (expecting == DICT_EXPECT_VALUE) {
|
|
_set_error("value expected");
|
|
return nullptr;
|
|
}
|
|
if (expecting == DICT_EXPECT_COLON) {
|
|
_set_error("':' expected");
|
|
return nullptr;
|
|
}
|
|
|
|
expecting = DICT_EXPECT_KEY;
|
|
tokenizer->advance(); //ignore newline
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_COLON) {
|
|
if (expecting == DICT_EXPECT_KEY) {
|
|
_set_error("key or '}' expected");
|
|
return nullptr;
|
|
}
|
|
if (expecting == DICT_EXPECT_VALUE) {
|
|
_set_error("value expected");
|
|
return nullptr;
|
|
}
|
|
if (expecting == DICT_EXPECT_COMMA) {
|
|
_set_error("',' or '}' expected");
|
|
return nullptr;
|
|
}
|
|
|
|
expecting = DICT_EXPECT_VALUE;
|
|
tokenizer->advance(); //ignore newline
|
|
} else {
|
|
if (expecting == DICT_EXPECT_COMMA) {
|
|
_set_error("',' or '}' expected");
|
|
return nullptr;
|
|
}
|
|
if (expecting == DICT_EXPECT_COLON) {
|
|
_set_error("':' expected");
|
|
return nullptr;
|
|
}
|
|
|
|
if (expecting == DICT_EXPECT_KEY) {
|
|
if (tokenizer->is_token_literal() && tokenizer->get_token(1) == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
|
|
//lua style identifier, easier to write
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = tokenizer->get_token_literal();
|
|
cn->datatype = _type_from_variant(cn->value);
|
|
key = cn;
|
|
tokenizer->advance(2);
|
|
expecting = DICT_EXPECT_VALUE;
|
|
} else {
|
|
//python/js style more flexible
|
|
key = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant);
|
|
if (!key) {
|
|
return nullptr;
|
|
}
|
|
expecting = DICT_EXPECT_COLON;
|
|
}
|
|
}
|
|
|
|
if (expecting == DICT_EXPECT_VALUE) {
|
|
Node *value = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant);
|
|
if (!value) {
|
|
return nullptr;
|
|
}
|
|
expecting = DICT_EXPECT_COMMA;
|
|
|
|
if (key->type == CScriptParser::Node::TYPE_CONSTANT) {
|
|
Variant const &keyName = static_cast<const CScriptParser::ConstantNode *>(key)->value;
|
|
|
|
if (keys.has(keyName)) {
|
|
_set_error("Duplicate key found in Dictionary literal");
|
|
return nullptr;
|
|
}
|
|
keys.insert(keyName);
|
|
}
|
|
|
|
DictionaryNode::Pair pair;
|
|
pair.key = key;
|
|
pair.value = value;
|
|
dict->elements.push_back(pair);
|
|
key = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
expr = dict;
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == CScriptTokenizer::TK_CURSOR)) {
|
|
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
|
|
// parent call
|
|
|
|
tokenizer->advance(); //goto identifier
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_PARENT_CALL;
|
|
|
|
/*SelfNode *self = alloc_node<SelfNode>();
|
|
op->arguments.push_back(self);
|
|
forbidden for now */
|
|
StringName identifier;
|
|
bool is_completion = _get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier) && for_completion;
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = identifier;
|
|
op->arguments.push_back(id);
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
if (!is_completion) {
|
|
_set_error("Expected '(' for parent function call.");
|
|
return nullptr;
|
|
}
|
|
} else {
|
|
tokenizer->advance();
|
|
if (!_parse_arguments(op, op->arguments, p_static, false, p_parsing_constant)) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
expr = op;
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_BUILT_IN_TYPE && expression.size() > 0 && expression[expression.size() - 1].is_op && expression[expression.size() - 1].op == OperatorNode::OP_IS) {
|
|
Expression e = expression[expression.size() - 1];
|
|
e.op = OperatorNode::OP_IS_BUILTIN;
|
|
expression.write[expression.size() - 1] = e;
|
|
|
|
TypeNode *tn = alloc_node<TypeNode>();
|
|
tn->vtype = tokenizer->get_token_type();
|
|
expr = tn;
|
|
tokenizer->advance();
|
|
} else {
|
|
//find list [ or find dictionary {
|
|
_set_error("Error parsing expression, misplaced: " + String(tokenizer->get_token_name(tokenizer->get_token())));
|
|
return nullptr; //nothing
|
|
}
|
|
|
|
ERR_FAIL_COND_V_MSG(!expr, nullptr, "CScriptParser bug, couldn't figure out what expression is.");
|
|
|
|
/******************/
|
|
/* Parse Indexing */
|
|
/******************/
|
|
|
|
while (true) {
|
|
//expressions can be indexed any number of times
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PERIOD) {
|
|
//indexing using "."
|
|
|
|
if (tokenizer->get_token(1) != CScriptTokenizer::TK_CURSOR && !tokenizer->is_token_literal(1)) {
|
|
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
|
|
_set_error("Expected identifier as member");
|
|
return nullptr;
|
|
} else if (tokenizer->get_token(2) == CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
//call!!
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_CALL;
|
|
|
|
tokenizer->advance();
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
StringName identifier;
|
|
if (_get_completable_identifier(COMPLETION_METHOD, identifier)) {
|
|
completion_node = op;
|
|
//indexing stuff
|
|
}
|
|
|
|
id->name = identifier;
|
|
|
|
op->arguments.push_back(expr); // call what
|
|
op->arguments.push_back(id); // call func
|
|
//get arguments
|
|
tokenizer->advance(1);
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CURSOR) {
|
|
_make_completable_call(0);
|
|
completion_node = op;
|
|
}
|
|
if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) {
|
|
return nullptr;
|
|
}
|
|
expr = op;
|
|
|
|
} else {
|
|
//simple indexing!
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_INDEX_NAMED;
|
|
tokenizer->advance();
|
|
|
|
StringName identifier;
|
|
if (_get_completable_identifier(COMPLETION_INDEX, identifier)) {
|
|
if (identifier == StringName()) {
|
|
identifier = "@temp"; //so it parses alright
|
|
}
|
|
completion_node = op;
|
|
|
|
//indexing stuff
|
|
}
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = identifier;
|
|
|
|
op->arguments.push_back(expr);
|
|
op->arguments.push_back(id);
|
|
|
|
expr = op;
|
|
}
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_BRACKET_OPEN) {
|
|
//indexing using "[]"
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_INDEX;
|
|
|
|
tokenizer->advance(1);
|
|
|
|
parenthesis++;
|
|
Node *subexpr = _parse_expression(op, p_static, p_allow_assign, p_parsing_constant);
|
|
parenthesis--;
|
|
if (!subexpr) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_BRACKET_CLOSE) {
|
|
_set_error("Expected ']'");
|
|
return nullptr;
|
|
}
|
|
|
|
op->arguments.push_back(expr);
|
|
op->arguments.push_back(subexpr);
|
|
tokenizer->advance(1);
|
|
expr = op;
|
|
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************/
|
|
/* Parse Casting */
|
|
/*****************/
|
|
|
|
bool has_casting = expr->type == Node::TYPE_CAST;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PR_AS) {
|
|
if (has_casting) {
|
|
_set_error("Unexpected 'as'.");
|
|
return nullptr;
|
|
}
|
|
CastNode *cn = alloc_node<CastNode>();
|
|
if (!_parse_type(cn->cast_type)) {
|
|
_set_error("Expected type after 'as'.");
|
|
return nullptr;
|
|
}
|
|
has_casting = true;
|
|
cn->source_node = expr;
|
|
expr = cn;
|
|
}
|
|
|
|
/******************/
|
|
/* Parse Operator */
|
|
/******************/
|
|
|
|
if (parenthesis > 0) {
|
|
//remove empty space (only allowed if inside parenthesis
|
|
while (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
}
|
|
}
|
|
|
|
Expression e;
|
|
e.is_op = false;
|
|
e.node = expr;
|
|
expression.push_back(e);
|
|
|
|
// determine which operator is next
|
|
|
|
OperatorNode::Operator op;
|
|
bool valid = true;
|
|
|
|
//assign, if allowed is only allowed on the first operator
|
|
#define _VALIDATE_ASSIGN \
|
|
if (!p_allow_assign || has_casting) { \
|
|
_set_error("Unexpected assign."); \
|
|
return NULL; \
|
|
} \
|
|
p_allow_assign = false;
|
|
|
|
switch (tokenizer->get_token()) { //see operator
|
|
|
|
case CScriptTokenizer::TK_OP_IN:
|
|
op = OperatorNode::OP_IN;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_EQUAL:
|
|
op = OperatorNode::OP_EQUAL;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_NOT_EQUAL:
|
|
op = OperatorNode::OP_NOT_EQUAL;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_LESS:
|
|
op = OperatorNode::OP_LESS;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_LESS_EQUAL:
|
|
op = OperatorNode::OP_LESS_EQUAL;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_GREATER:
|
|
op = OperatorNode::OP_GREATER;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_GREATER_EQUAL:
|
|
op = OperatorNode::OP_GREATER_EQUAL;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_AND:
|
|
op = OperatorNode::OP_AND;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_OR:
|
|
op = OperatorNode::OP_OR;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ADD:
|
|
op = OperatorNode::OP_ADD;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_SUB:
|
|
op = OperatorNode::OP_SUB;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_MUL:
|
|
op = OperatorNode::OP_MUL;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_DIV:
|
|
op = OperatorNode::OP_DIV;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_MOD:
|
|
op = OperatorNode::OP_MOD;
|
|
break;
|
|
//case CScriptTokenizer::TK_OP_NEG: op=OperatorNode::OP_NEG ; break;
|
|
case CScriptTokenizer::TK_OP_SHIFT_LEFT:
|
|
op = OperatorNode::OP_SHIFT_LEFT;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_SHIFT_RIGHT:
|
|
op = OperatorNode::OP_SHIFT_RIGHT;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN: {
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN;
|
|
|
|
if (tokenizer->get_token(1) == CScriptTokenizer::TK_CURSOR) {
|
|
//code complete assignment
|
|
completion_type = COMPLETION_ASSIGN;
|
|
completion_node = expr;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_block = current_block;
|
|
completion_found = true;
|
|
tokenizer->advance();
|
|
}
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_ADD:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_SUB:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_MUL:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_DIV:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_MOD:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
|
|
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_BIT_AND:
|
|
op = OperatorNode::OP_BIT_AND;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_BIT_OR:
|
|
op = OperatorNode::OP_BIT_OR;
|
|
break;
|
|
case CScriptTokenizer::TK_OP_BIT_XOR:
|
|
op = OperatorNode::OP_BIT_XOR;
|
|
break;
|
|
case CScriptTokenizer::TK_PR_IS:
|
|
op = OperatorNode::OP_IS;
|
|
break;
|
|
case CScriptTokenizer::TK_CF_IF:
|
|
op = OperatorNode::OP_TERNARY_IF;
|
|
break;
|
|
case CScriptTokenizer::TK_CF_ELSE:
|
|
op = OperatorNode::OP_TERNARY_ELSE;
|
|
break;
|
|
default:
|
|
valid = false;
|
|
break;
|
|
}
|
|
|
|
if (valid) {
|
|
e.is_op = true;
|
|
e.op = op;
|
|
expression.push_back(e);
|
|
tokenizer->advance();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Reduce the set set of expressions and place them in an operator tree, respecting precedence */
|
|
|
|
while (expression.size() > 1) {
|
|
int next_op = -1;
|
|
int min_priority = 0xFFFFF;
|
|
bool is_unary = false;
|
|
bool is_ternary = false;
|
|
|
|
for (int i = 0; i < expression.size(); i++) {
|
|
if (!expression[i].is_op) {
|
|
continue;
|
|
}
|
|
|
|
int priority;
|
|
|
|
bool unary = false;
|
|
bool ternary = false;
|
|
bool error = false;
|
|
bool right_to_left = false;
|
|
|
|
switch (expression[i].op) {
|
|
case OperatorNode::OP_IS:
|
|
case OperatorNode::OP_IS_BUILTIN:
|
|
priority = -1;
|
|
break; //before anything
|
|
|
|
case OperatorNode::OP_BIT_INVERT:
|
|
priority = 0;
|
|
unary = true;
|
|
break;
|
|
case OperatorNode::OP_NEG:
|
|
case OperatorNode::OP_POS:
|
|
priority = 1;
|
|
unary = true;
|
|
break;
|
|
|
|
case OperatorNode::OP_MUL:
|
|
priority = 2;
|
|
break;
|
|
case OperatorNode::OP_DIV:
|
|
priority = 2;
|
|
break;
|
|
case OperatorNode::OP_MOD:
|
|
priority = 2;
|
|
break;
|
|
|
|
case OperatorNode::OP_ADD:
|
|
priority = 3;
|
|
break;
|
|
case OperatorNode::OP_SUB:
|
|
priority = 3;
|
|
break;
|
|
|
|
case OperatorNode::OP_SHIFT_LEFT:
|
|
priority = 4;
|
|
break;
|
|
case OperatorNode::OP_SHIFT_RIGHT:
|
|
priority = 4;
|
|
break;
|
|
|
|
case OperatorNode::OP_BIT_AND:
|
|
priority = 5;
|
|
break;
|
|
case OperatorNode::OP_BIT_XOR:
|
|
priority = 6;
|
|
break;
|
|
case OperatorNode::OP_BIT_OR:
|
|
priority = 7;
|
|
break;
|
|
|
|
case OperatorNode::OP_LESS:
|
|
priority = 8;
|
|
break;
|
|
case OperatorNode::OP_LESS_EQUAL:
|
|
priority = 8;
|
|
break;
|
|
case OperatorNode::OP_GREATER:
|
|
priority = 8;
|
|
break;
|
|
case OperatorNode::OP_GREATER_EQUAL:
|
|
priority = 8;
|
|
break;
|
|
|
|
case OperatorNode::OP_EQUAL:
|
|
priority = 8;
|
|
break;
|
|
case OperatorNode::OP_NOT_EQUAL:
|
|
priority = 8;
|
|
break;
|
|
|
|
case OperatorNode::OP_IN:
|
|
priority = 10;
|
|
break;
|
|
|
|
case OperatorNode::OP_NOT:
|
|
priority = 11;
|
|
unary = true;
|
|
break;
|
|
case OperatorNode::OP_AND:
|
|
priority = 12;
|
|
break;
|
|
case OperatorNode::OP_OR:
|
|
priority = 13;
|
|
break;
|
|
|
|
case OperatorNode::OP_TERNARY_IF:
|
|
priority = 14;
|
|
ternary = true;
|
|
right_to_left = true;
|
|
break;
|
|
case OperatorNode::OP_TERNARY_ELSE:
|
|
priority = 14;
|
|
error = true;
|
|
// Rigth-to-left should be false in this case, otherwise it would always error.
|
|
break;
|
|
|
|
case OperatorNode::OP_ASSIGN:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_ADD:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_SUB:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_MUL:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_DIV:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_MOD:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_BIT_AND:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_BIT_OR:
|
|
priority = 15;
|
|
break;
|
|
case OperatorNode::OP_ASSIGN_BIT_XOR:
|
|
priority = 15;
|
|
break;
|
|
|
|
default: {
|
|
_set_error("CScriptParser bug, invalid operator in expression: " + itos(expression[i].op));
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (priority < min_priority || (right_to_left && priority == min_priority)) {
|
|
// < is used for left to right (default)
|
|
// <= is used for right to left
|
|
if (error) {
|
|
_set_error("Unexpected operator");
|
|
return nullptr;
|
|
}
|
|
next_op = i;
|
|
min_priority = priority;
|
|
is_unary = unary;
|
|
is_ternary = ternary;
|
|
}
|
|
}
|
|
|
|
if (next_op == -1) {
|
|
_set_error("Yet another parser bug....");
|
|
ERR_FAIL_V(nullptr);
|
|
}
|
|
|
|
// OK! create operator..
|
|
if (is_unary) {
|
|
int expr_pos = next_op;
|
|
while (expression[expr_pos].is_op) {
|
|
expr_pos++;
|
|
if (expr_pos == expression.size()) {
|
|
//can happen..
|
|
_set_error("Unexpected end of expression...");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
//consecutively do unary operators
|
|
for (int i = expr_pos - 1; i >= next_op; i--) {
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = expression[i].op;
|
|
op->arguments.push_back(expression[i + 1].node);
|
|
op->line = op_line; //line might have been changed from a \n
|
|
expression.write[i].is_op = false;
|
|
expression.write[i].node = op;
|
|
expression.remove(i + 1);
|
|
}
|
|
|
|
} else if (is_ternary) {
|
|
if (next_op < 1 || next_op >= (expression.size() - 1)) {
|
|
_set_error("Parser bug...");
|
|
ERR_FAIL_V(nullptr);
|
|
}
|
|
|
|
if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) {
|
|
_set_error("Expected else after ternary if.");
|
|
return nullptr;
|
|
}
|
|
if (next_op >= (expression.size() - 3)) {
|
|
_set_error("Expected value after ternary else.");
|
|
return nullptr;
|
|
}
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = expression[next_op].op;
|
|
op->line = op_line; //line might have been changed from a \n
|
|
|
|
if (expression[next_op - 1].is_op) {
|
|
_set_error("Parser bug...");
|
|
ERR_FAIL_V(nullptr);
|
|
}
|
|
|
|
if (expression[next_op + 1].is_op) {
|
|
// this is not invalid and can really appear
|
|
// but it becomes invalid anyway because no binary op
|
|
// can be followed by a unary op in a valid combination,
|
|
// due to how precedence works, unaries will always disappear first
|
|
|
|
_set_error("Unexpected two consecutive operators after ternary if.");
|
|
return nullptr;
|
|
}
|
|
|
|
if (expression[next_op + 3].is_op) {
|
|
// this is not invalid and can really appear
|
|
// but it becomes invalid anyway because no binary op
|
|
// can be followed by a unary op in a valid combination,
|
|
// due to how precedence works, unaries will always disappear first
|
|
|
|
_set_error("Unexpected two consecutive operators after ternary else.");
|
|
return nullptr;
|
|
}
|
|
|
|
op->arguments.push_back(expression[next_op + 1].node); //next expression goes as first
|
|
op->arguments.push_back(expression[next_op - 1].node); //left expression goes as when-true
|
|
op->arguments.push_back(expression[next_op + 3].node); //expression after next goes as when-false
|
|
|
|
//replace all 3 nodes by this operator and make it an expression
|
|
expression.write[next_op - 1].node = op;
|
|
expression.remove(next_op);
|
|
expression.remove(next_op);
|
|
expression.remove(next_op);
|
|
expression.remove(next_op);
|
|
} else {
|
|
if (next_op < 1 || next_op >= (expression.size() - 1)) {
|
|
_set_error("Parser bug...");
|
|
ERR_FAIL_V(nullptr);
|
|
}
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = expression[next_op].op;
|
|
op->line = op_line; //line might have been changed from a \n
|
|
|
|
if (expression[next_op - 1].is_op) {
|
|
_set_error("Parser bug...");
|
|
ERR_FAIL_V(nullptr);
|
|
}
|
|
|
|
if (expression[next_op + 1].is_op) {
|
|
// this is not invalid and can really appear
|
|
// but it becomes invalid anyway because no binary op
|
|
// can be followed by a unary op in a valid combination,
|
|
// due to how precedence works, unaries will always disappear first
|
|
|
|
_set_error("Unexpected two consecutive operators.");
|
|
return nullptr;
|
|
}
|
|
|
|
op->arguments.push_back(expression[next_op - 1].node); //expression goes as left
|
|
op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right
|
|
|
|
//replace all 3 nodes by this operator and make it an expression
|
|
expression.write[next_op - 1].node = op;
|
|
expression.remove(next_op);
|
|
expression.remove(next_op);
|
|
}
|
|
}
|
|
|
|
return expression[0].node;
|
|
}
|
|
|
|
CScriptParser::Node *CScriptParser::_reduce_expression(Node *p_node, bool p_to_const) {
|
|
switch (p_node->type) {
|
|
case Node::TYPE_BUILT_IN_FUNCTION: {
|
|
//many may probably be optimizable
|
|
return p_node;
|
|
} break;
|
|
case Node::TYPE_ARRAY: {
|
|
ArrayNode *an = static_cast<ArrayNode *>(p_node);
|
|
bool all_constants = true;
|
|
|
|
for (int i = 0; i < an->elements.size(); i++) {
|
|
an->elements.write[i] = _reduce_expression(an->elements[i], p_to_const);
|
|
if (an->elements[i]->type != Node::TYPE_CONSTANT) {
|
|
all_constants = false;
|
|
}
|
|
}
|
|
|
|
if (all_constants && p_to_const) {
|
|
//reduce constant array expression
|
|
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
Array arr;
|
|
arr.resize(an->elements.size());
|
|
for (int i = 0; i < an->elements.size(); i++) {
|
|
ConstantNode *acn = static_cast<ConstantNode *>(an->elements[i]);
|
|
arr[i] = acn->value;
|
|
}
|
|
cn->value = arr;
|
|
cn->datatype = _type_from_variant(cn->value);
|
|
return cn;
|
|
}
|
|
|
|
return an;
|
|
|
|
} break;
|
|
case Node::TYPE_DICTIONARY: {
|
|
DictionaryNode *dn = static_cast<DictionaryNode *>(p_node);
|
|
bool all_constants = true;
|
|
|
|
for (int i = 0; i < dn->elements.size(); i++) {
|
|
dn->elements.write[i].key = _reduce_expression(dn->elements[i].key, p_to_const);
|
|
if (dn->elements[i].key->type != Node::TYPE_CONSTANT) {
|
|
all_constants = false;
|
|
}
|
|
dn->elements.write[i].value = _reduce_expression(dn->elements[i].value, p_to_const);
|
|
if (dn->elements[i].value->type != Node::TYPE_CONSTANT) {
|
|
all_constants = false;
|
|
}
|
|
}
|
|
|
|
if (all_constants && p_to_const) {
|
|
//reduce constant array expression
|
|
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
Dictionary dict;
|
|
for (int i = 0; i < dn->elements.size(); i++) {
|
|
ConstantNode *key_c = static_cast<ConstantNode *>(dn->elements[i].key);
|
|
ConstantNode *value_c = static_cast<ConstantNode *>(dn->elements[i].value);
|
|
|
|
dict[key_c->value] = value_c->value;
|
|
}
|
|
cn->value = dict;
|
|
cn->datatype = _type_from_variant(cn->value);
|
|
return cn;
|
|
}
|
|
|
|
return dn;
|
|
|
|
} break;
|
|
case Node::TYPE_OPERATOR: {
|
|
OperatorNode *op = static_cast<OperatorNode *>(p_node);
|
|
|
|
bool all_constants = true;
|
|
int last_not_constant = -1;
|
|
|
|
for (int i = 0; i < op->arguments.size(); i++) {
|
|
op->arguments.write[i] = _reduce_expression(op->arguments[i], p_to_const);
|
|
if (op->arguments[i]->type != Node::TYPE_CONSTANT) {
|
|
all_constants = false;
|
|
last_not_constant = i;
|
|
}
|
|
}
|
|
|
|
if (op->op == OperatorNode::OP_IS) {
|
|
//nothing much
|
|
return op;
|
|
}
|
|
if (op->op == OperatorNode::OP_PARENT_CALL) {
|
|
//nothing much
|
|
return op;
|
|
|
|
} else if (op->op == OperatorNode::OP_CALL) {
|
|
//can reduce base type constructors
|
|
if ((op->arguments[0]->type == Node::TYPE_TYPE || (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && CScriptFunctions::is_deterministic(static_cast<BuiltInFunctionNode *>(op->arguments[0])->function))) && last_not_constant == 0) {
|
|
//native type constructor or intrinsic function
|
|
const Variant **vptr = nullptr;
|
|
Vector<Variant *> ptrs;
|
|
if (op->arguments.size() > 1) {
|
|
ptrs.resize(op->arguments.size() - 1);
|
|
for (int i = 0; i < ptrs.size(); i++) {
|
|
ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[i + 1]);
|
|
ptrs.write[i] = &cn->value;
|
|
}
|
|
|
|
vptr = (const Variant **)&ptrs[0];
|
|
}
|
|
|
|
Variant::CallError ce;
|
|
Variant v;
|
|
|
|
if (op->arguments[0]->type == Node::TYPE_TYPE) {
|
|
TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]);
|
|
v = Variant::construct(tn->vtype, vptr, ptrs.size(), ce);
|
|
|
|
} else {
|
|
CScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function;
|
|
CScriptFunctions::call(func, vptr, ptrs.size(), v, ce);
|
|
}
|
|
|
|
if (ce.error != Variant::CallError::CALL_OK) {
|
|
String errwhere;
|
|
if (op->arguments[0]->type == Node::TYPE_TYPE) {
|
|
TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]);
|
|
errwhere = "'" + Variant::get_type_name(tn->vtype) + "' constructor";
|
|
|
|
} else {
|
|
CScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function;
|
|
errwhere = String("'") + CScriptFunctions::get_func_name(func) + "' intrinsic function";
|
|
}
|
|
|
|
switch (ce.error) {
|
|
case Variant::CallError::CALL_ERROR_INVALID_ARGUMENT: {
|
|
_set_error("Invalid argument (#" + itos(ce.argument + 1) + ") for " + errwhere + ".");
|
|
|
|
} break;
|
|
case Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: {
|
|
_set_error("Too many arguments for " + errwhere + ".");
|
|
} break;
|
|
case Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: {
|
|
_set_error("Too few arguments for " + errwhere + ".");
|
|
} break;
|
|
default: {
|
|
_set_error("Invalid arguments for " + errwhere + ".");
|
|
|
|
} break;
|
|
}
|
|
|
|
error_line = op->line;
|
|
|
|
return p_node;
|
|
}
|
|
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = v;
|
|
cn->datatype = _type_from_variant(v);
|
|
return cn;
|
|
}
|
|
|
|
return op; //don't reduce yet
|
|
|
|
} else if (op->op == OperatorNode::OP_INDEX) {
|
|
//can reduce indices into constant arrays or dictionaries
|
|
|
|
if (all_constants) {
|
|
ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]);
|
|
ConstantNode *cb = static_cast<ConstantNode *>(op->arguments[1]);
|
|
|
|
bool valid;
|
|
|
|
Variant v = ca->value.get(cb->value, &valid);
|
|
if (!valid) {
|
|
_set_error("invalid index in constant expression");
|
|
error_line = op->line;
|
|
return op;
|
|
}
|
|
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = v;
|
|
cn->datatype = _type_from_variant(v);
|
|
return cn;
|
|
}
|
|
|
|
return op;
|
|
|
|
} else if (op->op == OperatorNode::OP_INDEX_NAMED) {
|
|
if (op->arguments[0]->type == Node::TYPE_CONSTANT && op->arguments[1]->type == Node::TYPE_IDENTIFIER) {
|
|
ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]);
|
|
IdentifierNode *ib = static_cast<IdentifierNode *>(op->arguments[1]);
|
|
|
|
bool valid;
|
|
Variant v = ca->value.get_named(ib->name, &valid);
|
|
if (!valid) {
|
|
_set_error("invalid index '" + String(ib->name) + "' in constant expression");
|
|
error_line = op->line;
|
|
return op;
|
|
}
|
|
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = v;
|
|
cn->datatype = _type_from_variant(v);
|
|
return cn;
|
|
}
|
|
|
|
return op;
|
|
}
|
|
|
|
//validate assignment (don't assign to constant expression
|
|
switch (op->op) {
|
|
case OperatorNode::OP_ASSIGN:
|
|
case OperatorNode::OP_ASSIGN_ADD:
|
|
case OperatorNode::OP_ASSIGN_SUB:
|
|
case OperatorNode::OP_ASSIGN_MUL:
|
|
case OperatorNode::OP_ASSIGN_DIV:
|
|
case OperatorNode::OP_ASSIGN_MOD:
|
|
case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
|
|
case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
|
|
case OperatorNode::OP_ASSIGN_BIT_AND:
|
|
case OperatorNode::OP_ASSIGN_BIT_OR:
|
|
case OperatorNode::OP_ASSIGN_BIT_XOR: {
|
|
if (op->arguments[0]->type == Node::TYPE_CONSTANT) {
|
|
_set_error("Can't assign to constant", tokenizer->get_token_line() - 1);
|
|
error_line = op->line;
|
|
return op;
|
|
} else if (op->arguments[0]->type == Node::TYPE_SELF) {
|
|
_set_error("Can't assign to self.", op->line);
|
|
error_line = op->line;
|
|
return op;
|
|
}
|
|
|
|
if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
|
|
OperatorNode *on = static_cast<OperatorNode *>(op->arguments[0]);
|
|
if (on->op != OperatorNode::OP_INDEX && on->op != OperatorNode::OP_INDEX_NAMED) {
|
|
_set_error("Can't assign to an expression", tokenizer->get_token_line() - 1);
|
|
error_line = op->line;
|
|
return op;
|
|
}
|
|
}
|
|
|
|
} break;
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
//now se if all are constants
|
|
if (!all_constants) {
|
|
return op; //nothing to reduce from here on
|
|
}
|
|
#define _REDUCE_UNARY(m_vop) \
|
|
bool valid = false; \
|
|
Variant res; \
|
|
Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, Variant(), res, valid); \
|
|
if (!valid) { \
|
|
_set_error("Invalid operand for unary operator"); \
|
|
error_line = op->line; \
|
|
return p_node; \
|
|
} \
|
|
ConstantNode *cn = alloc_node<ConstantNode>(); \
|
|
cn->value = res; \
|
|
cn->datatype = _type_from_variant(res); \
|
|
return cn;
|
|
|
|
#define _REDUCE_BINARY(m_vop) \
|
|
bool valid = false; \
|
|
Variant res; \
|
|
Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, static_cast<ConstantNode *>(op->arguments[1])->value, res, valid); \
|
|
if (!valid) { \
|
|
_set_error("Invalid operands for operator"); \
|
|
error_line = op->line; \
|
|
return p_node; \
|
|
} \
|
|
ConstantNode *cn = alloc_node<ConstantNode>(); \
|
|
cn->value = res; \
|
|
cn->datatype = _type_from_variant(res); \
|
|
return cn;
|
|
|
|
switch (op->op) {
|
|
//unary operators
|
|
case OperatorNode::OP_NEG: {
|
|
_REDUCE_UNARY(Variant::OP_NEGATE);
|
|
} break;
|
|
case OperatorNode::OP_POS: {
|
|
_REDUCE_UNARY(Variant::OP_POSITIVE);
|
|
} break;
|
|
case OperatorNode::OP_NOT: {
|
|
_REDUCE_UNARY(Variant::OP_NOT);
|
|
} break;
|
|
case OperatorNode::OP_BIT_INVERT: {
|
|
_REDUCE_UNARY(Variant::OP_BIT_NEGATE);
|
|
} break;
|
|
//binary operators (in precedence order)
|
|
case OperatorNode::OP_IN: {
|
|
_REDUCE_BINARY(Variant::OP_IN);
|
|
} break;
|
|
case OperatorNode::OP_EQUAL: {
|
|
_REDUCE_BINARY(Variant::OP_EQUAL);
|
|
} break;
|
|
case OperatorNode::OP_NOT_EQUAL: {
|
|
_REDUCE_BINARY(Variant::OP_NOT_EQUAL);
|
|
} break;
|
|
case OperatorNode::OP_LESS: {
|
|
_REDUCE_BINARY(Variant::OP_LESS);
|
|
} break;
|
|
case OperatorNode::OP_LESS_EQUAL: {
|
|
_REDUCE_BINARY(Variant::OP_LESS_EQUAL);
|
|
} break;
|
|
case OperatorNode::OP_GREATER: {
|
|
_REDUCE_BINARY(Variant::OP_GREATER);
|
|
} break;
|
|
case OperatorNode::OP_GREATER_EQUAL: {
|
|
_REDUCE_BINARY(Variant::OP_GREATER_EQUAL);
|
|
} break;
|
|
case OperatorNode::OP_AND: {
|
|
_REDUCE_BINARY(Variant::OP_AND);
|
|
} break;
|
|
case OperatorNode::OP_OR: {
|
|
_REDUCE_BINARY(Variant::OP_OR);
|
|
} break;
|
|
case OperatorNode::OP_ADD: {
|
|
_REDUCE_BINARY(Variant::OP_ADD);
|
|
} break;
|
|
case OperatorNode::OP_SUB: {
|
|
_REDUCE_BINARY(Variant::OP_SUBTRACT);
|
|
} break;
|
|
case OperatorNode::OP_MUL: {
|
|
_REDUCE_BINARY(Variant::OP_MULTIPLY);
|
|
} break;
|
|
case OperatorNode::OP_DIV: {
|
|
_REDUCE_BINARY(Variant::OP_DIVIDE);
|
|
} break;
|
|
case OperatorNode::OP_MOD: {
|
|
_REDUCE_BINARY(Variant::OP_MODULE);
|
|
} break;
|
|
case OperatorNode::OP_SHIFT_LEFT: {
|
|
_REDUCE_BINARY(Variant::OP_SHIFT_LEFT);
|
|
} break;
|
|
case OperatorNode::OP_SHIFT_RIGHT: {
|
|
_REDUCE_BINARY(Variant::OP_SHIFT_RIGHT);
|
|
} break;
|
|
case OperatorNode::OP_BIT_AND: {
|
|
_REDUCE_BINARY(Variant::OP_BIT_AND);
|
|
} break;
|
|
case OperatorNode::OP_BIT_OR: {
|
|
_REDUCE_BINARY(Variant::OP_BIT_OR);
|
|
} break;
|
|
case OperatorNode::OP_BIT_XOR: {
|
|
_REDUCE_BINARY(Variant::OP_BIT_XOR);
|
|
} break;
|
|
case OperatorNode::OP_TERNARY_IF: {
|
|
if (static_cast<ConstantNode *>(op->arguments[0])->value.booleanize()) {
|
|
return op->arguments[1];
|
|
} else {
|
|
return op->arguments[2];
|
|
}
|
|
} break;
|
|
default: {
|
|
ERR_FAIL_V(op);
|
|
}
|
|
}
|
|
|
|
} break;
|
|
default: {
|
|
return p_node;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
CScriptParser::Node *CScriptParser::_parse_and_reduce_expression(Node *p_parent, bool p_static, bool p_reduce_const, bool p_allow_assign) {
|
|
Node *expr = _parse_expression(p_parent, p_static, p_allow_assign, p_reduce_const);
|
|
if (!expr || error_set) {
|
|
return nullptr;
|
|
}
|
|
expr = _reduce_expression(expr, p_reduce_const);
|
|
if (!expr || error_set) {
|
|
return nullptr;
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
bool CScriptParser::_reduce_export_var_type(Variant &p_value, int p_line) {
|
|
if (p_value.get_type() == Variant::ARRAY) {
|
|
Array arr = p_value;
|
|
for (int i = 0; i < arr.size(); i++) {
|
|
if (!_reduce_export_var_type(arr[i], p_line)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (p_value.get_type() == Variant::DICTIONARY) {
|
|
Dictionary dict = p_value;
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
Variant value = dict.get_value_at_index(i);
|
|
if (!_reduce_export_var_type(value, p_line)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// validate type
|
|
DataType type = _type_from_variant(p_value);
|
|
if (type.kind == DataType::BUILTIN) {
|
|
return true;
|
|
} else if (type.kind == DataType::NATIVE) {
|
|
if (ClassDB::is_parent_class(type.native_type, "Resource")) {
|
|
return true;
|
|
}
|
|
}
|
|
_set_error("Invalid export type. Only built-in and native resource types can be exported.", p_line);
|
|
return false;
|
|
}
|
|
|
|
bool CScriptParser::_recover_from_completion() {
|
|
if (!completion_found) {
|
|
return false; //can't recover if no completion
|
|
}
|
|
//skip stuff until newline
|
|
while (tokenizer->get_token() != CScriptTokenizer::TK_NEWLINE && tokenizer->get_token() != CScriptTokenizer::TK_EOF && tokenizer->get_token() != CScriptTokenizer::TK_ERROR) {
|
|
tokenizer->advance();
|
|
}
|
|
completion_found = false;
|
|
error_set = false;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_ERROR) {
|
|
error_set = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CScriptParser::PatternNode *CScriptParser::_parse_pattern(bool p_static) {
|
|
PatternNode *pattern = alloc_node<PatternNode>();
|
|
|
|
CScriptTokenizer::Token token = tokenizer->get_token();
|
|
if (error_set) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (token == CScriptTokenizer::TK_EOF) {
|
|
return nullptr;
|
|
}
|
|
|
|
switch (token) {
|
|
// array
|
|
case CScriptTokenizer::TK_BRACKET_OPEN: {
|
|
tokenizer->advance();
|
|
pattern->pt_type = CScriptParser::PatternNode::PT_ARRAY;
|
|
while (true) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == CScriptTokenizer::TK_PERIOD) {
|
|
// match everything
|
|
tokenizer->advance(2);
|
|
PatternNode *sub_pattern = alloc_node<PatternNode>();
|
|
sub_pattern->pt_type = CScriptParser::PatternNode::PT_IGNORE_REST;
|
|
pattern->array.push_back(sub_pattern);
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == CScriptTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance(2);
|
|
break;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance(1);
|
|
break;
|
|
} else {
|
|
_set_error("'..' pattern only allowed at the end of an array pattern");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
PatternNode *sub_pattern = _parse_pattern(p_static);
|
|
if (!sub_pattern) {
|
|
return nullptr;
|
|
}
|
|
|
|
pattern->array.push_back(sub_pattern);
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
continue;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
} else {
|
|
_set_error("Not a valid pattern");
|
|
return nullptr;
|
|
}
|
|
}
|
|
} break;
|
|
// bind
|
|
case CScriptTokenizer::TK_PR_VAR: {
|
|
tokenizer->advance();
|
|
if (!tokenizer->is_token_literal()) {
|
|
_set_error("Expected identifier for binding variable name.");
|
|
return nullptr;
|
|
}
|
|
pattern->pt_type = CScriptParser::PatternNode::PT_BIND;
|
|
pattern->bind = tokenizer->get_token_literal();
|
|
// Check if variable name is already used
|
|
BlockNode *bl = current_block;
|
|
while (bl) {
|
|
if (bl->variables.has(pattern->bind)) {
|
|
_set_error("Binding name of '" + pattern->bind.operator String() + "' is already declared in this scope.");
|
|
return nullptr;
|
|
}
|
|
bl = bl->parent_block;
|
|
}
|
|
// Create local variable for proper identifier detection later
|
|
LocalVarNode *lv = alloc_node<LocalVarNode>();
|
|
lv->name = pattern->bind;
|
|
current_block->variables.insert(lv->name, lv);
|
|
tokenizer->advance();
|
|
} break;
|
|
// dictionary
|
|
case CScriptTokenizer::TK_CURLY_BRACKET_OPEN: {
|
|
tokenizer->advance();
|
|
pattern->pt_type = CScriptParser::PatternNode::PT_DICTIONARY;
|
|
while (true) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == CScriptTokenizer::TK_PERIOD) {
|
|
// match everything
|
|
tokenizer->advance(2);
|
|
PatternNode *sub_pattern = alloc_node<PatternNode>();
|
|
sub_pattern->pt_type = PatternNode::PT_IGNORE_REST;
|
|
pattern->array.push_back(sub_pattern);
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == CScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
tokenizer->advance(2);
|
|
break;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
tokenizer->advance(1);
|
|
break;
|
|
} else {
|
|
_set_error("'..' pattern only allowed at the end of a dictionary pattern");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
Node *key = _parse_and_reduce_expression(pattern, p_static);
|
|
if (!key) {
|
|
_set_error("Not a valid key in pattern");
|
|
return nullptr;
|
|
}
|
|
|
|
if (key->type != CScriptParser::Node::TYPE_CONSTANT) {
|
|
_set_error("Not a constant expression as key");
|
|
return nullptr;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COLON) {
|
|
tokenizer->advance();
|
|
|
|
PatternNode *value = _parse_pattern(p_static);
|
|
if (!value) {
|
|
_set_error("Expected pattern in dictionary value");
|
|
return nullptr;
|
|
}
|
|
|
|
pattern->dictionary.insert(static_cast<ConstantNode *>(key), value);
|
|
} else {
|
|
pattern->dictionary.insert(static_cast<ConstantNode *>(key), NULL);
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
continue;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
} else {
|
|
_set_error("Not a valid pattern");
|
|
return nullptr;
|
|
}
|
|
}
|
|
} break;
|
|
case CScriptTokenizer::TK_WILDCARD: {
|
|
tokenizer->advance();
|
|
pattern->pt_type = PatternNode::PT_WILDCARD;
|
|
} break;
|
|
// all the constants like strings and numbers
|
|
default: {
|
|
Node *value = _parse_and_reduce_expression(pattern, p_static);
|
|
if (!value) {
|
|
_set_error("Expect constant expression or variables in a pattern");
|
|
return nullptr;
|
|
}
|
|
|
|
if (value->type == Node::TYPE_OPERATOR) {
|
|
// Maybe it's SomeEnum.VALUE
|
|
Node *current_value = value;
|
|
|
|
while (current_value->type == Node::TYPE_OPERATOR) {
|
|
OperatorNode *op_node = static_cast<OperatorNode *>(current_value);
|
|
|
|
if (op_node->op != OperatorNode::OP_INDEX_NAMED) {
|
|
_set_error("Invalid operator in pattern. Only index (`A.B`) is allowed");
|
|
return nullptr;
|
|
}
|
|
current_value = op_node->arguments[0];
|
|
}
|
|
|
|
if (current_value->type != Node::TYPE_IDENTIFIER) {
|
|
_set_error("Only constant expression or variables allowed in a pattern");
|
|
return nullptr;
|
|
}
|
|
|
|
} else if (value->type != Node::TYPE_IDENTIFIER && value->type != Node::TYPE_CONSTANT) {
|
|
_set_error("Only constant expressions or variables allowed in a pattern");
|
|
return nullptr;
|
|
}
|
|
|
|
pattern->pt_type = PatternNode::PT_CONSTANT;
|
|
pattern->constant = value;
|
|
} break;
|
|
}
|
|
|
|
return pattern;
|
|
}
|
|
|
|
void CScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) {
|
|
IndentLevel current_level = indent_level.back()->get();
|
|
|
|
p_block->has_return = true;
|
|
|
|
bool catch_all_appeared = false;
|
|
|
|
while (true) {
|
|
while (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE && _parse_newline()) {
|
|
;
|
|
}
|
|
|
|
// CScriptTokenizer::Token token = tokenizer->get_token();
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
|
|
if (current_level.indent > indent_level.back()->get().indent) {
|
|
break; // go back a level
|
|
}
|
|
|
|
pending_newline = -1;
|
|
|
|
PatternBranchNode *branch = alloc_node<PatternBranchNode>();
|
|
branch->body = alloc_node<BlockNode>();
|
|
branch->body->parent_block = p_block;
|
|
p_block->sub_blocks.push_back(branch->body);
|
|
current_block = branch->body;
|
|
|
|
branch->patterns.push_back(_parse_pattern(p_static));
|
|
if (!branch->patterns[0]) {
|
|
break;
|
|
}
|
|
|
|
bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND;
|
|
bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
// Branches after a wildcard or binding are unreachable
|
|
if (catch_all_appeared && !current_function->has_unreachable_code) {
|
|
_add_warning(CScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String());
|
|
current_function->has_unreachable_code = true;
|
|
}
|
|
#endif
|
|
|
|
while (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
branch->patterns.push_back(_parse_pattern(p_static));
|
|
if (!branch->patterns[branch->patterns.size() - 1]) {
|
|
return;
|
|
}
|
|
|
|
PatternNode::PatternType pt = branch->patterns[branch->patterns.size() - 1]->pt_type;
|
|
|
|
if (pt == PatternNode::PT_BIND) {
|
|
_set_error("Cannot use bindings with multipattern.");
|
|
return;
|
|
}
|
|
|
|
catch_all = catch_all || pt == PatternNode::PT_WILDCARD;
|
|
}
|
|
|
|
catch_all_appeared = catch_all_appeared || catch_all;
|
|
|
|
if (!_enter_indent_block()) {
|
|
_set_error("Expected block in pattern branch");
|
|
return;
|
|
}
|
|
|
|
_parse_block(branch->body, p_static);
|
|
|
|
current_block = p_block;
|
|
|
|
if (!branch->body->has_return) {
|
|
p_block->has_return = false;
|
|
}
|
|
|
|
p_branches.push_back(branch);
|
|
}
|
|
|
|
// Even if all branches return, there is possibility of default fallthrough
|
|
if (!catch_all_appeared) {
|
|
p_block->has_return = false;
|
|
}
|
|
}
|
|
|
|
void CScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) {
|
|
const DataType &to_match_type = p_node_to_match->get_datatype();
|
|
|
|
switch (p_pattern->pt_type) {
|
|
case PatternNode::PT_CONSTANT: {
|
|
DataType pattern_type = _reduce_node_type(p_pattern->constant);
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
|
|
OperatorNode *type_comp = nullptr;
|
|
|
|
// static type check if possible
|
|
if (pattern_type.has_type && to_match_type.has_type) {
|
|
if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) {
|
|
_set_error("The pattern type (" + pattern_type.to_string() + ") isn't compatible with the type of the value to match (" + to_match_type.to_string() + ").",
|
|
p_pattern->line);
|
|
return;
|
|
}
|
|
} else {
|
|
// runtime typecheck
|
|
BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
|
|
typeof_node->function = CScriptFunctions::TYPE_OF;
|
|
|
|
OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
|
|
typeof_match_value->op = OperatorNode::OP_CALL;
|
|
typeof_match_value->arguments.push_back(typeof_node);
|
|
typeof_match_value->arguments.push_back(p_node_to_match);
|
|
|
|
OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>();
|
|
typeof_pattern_value->op = OperatorNode::OP_CALL;
|
|
typeof_pattern_value->arguments.push_back(typeof_node);
|
|
typeof_pattern_value->arguments.push_back(p_pattern->constant);
|
|
|
|
type_comp = alloc_node<OperatorNode>();
|
|
type_comp->op = OperatorNode::OP_EQUAL;
|
|
type_comp->arguments.push_back(typeof_match_value);
|
|
type_comp->arguments.push_back(typeof_pattern_value);
|
|
}
|
|
|
|
// compare the actual values
|
|
OperatorNode *value_comp = alloc_node<OperatorNode>();
|
|
value_comp->op = OperatorNode::OP_EQUAL;
|
|
value_comp->arguments.push_back(p_pattern->constant);
|
|
value_comp->arguments.push_back(p_node_to_match);
|
|
|
|
if (type_comp) {
|
|
OperatorNode *full_comparison = alloc_node<OperatorNode>();
|
|
full_comparison->op = OperatorNode::OP_AND;
|
|
full_comparison->arguments.push_back(type_comp);
|
|
full_comparison->arguments.push_back(value_comp);
|
|
|
|
p_resulting_node = full_comparison;
|
|
} else {
|
|
p_resulting_node = value_comp;
|
|
}
|
|
|
|
} break;
|
|
case PatternNode::PT_BIND: {
|
|
p_bindings[p_pattern->bind] = p_node_to_match;
|
|
|
|
// a bind always matches
|
|
ConstantNode *true_value = alloc_node<ConstantNode>();
|
|
true_value->value = Variant(true);
|
|
p_resulting_node = true_value;
|
|
} break;
|
|
case PatternNode::PT_ARRAY: {
|
|
bool open_ended = false;
|
|
|
|
if (p_pattern->array.size() > 0) {
|
|
if (p_pattern->array[p_pattern->array.size() - 1]->pt_type == PatternNode::PT_IGNORE_REST) {
|
|
open_ended = true;
|
|
}
|
|
}
|
|
|
|
// typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() >= length
|
|
// typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length
|
|
|
|
{
|
|
OperatorNode *type_comp = nullptr;
|
|
// static type check if possible
|
|
if (to_match_type.has_type) {
|
|
// must be an array
|
|
if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) {
|
|
_set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line);
|
|
return;
|
|
}
|
|
} else {
|
|
// runtime typecheck
|
|
BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
|
|
typeof_node->function = CScriptFunctions::TYPE_OF;
|
|
|
|
OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
|
|
typeof_match_value->op = OperatorNode::OP_CALL;
|
|
typeof_match_value->arguments.push_back(typeof_node);
|
|
typeof_match_value->arguments.push_back(p_node_to_match);
|
|
|
|
IdentifierNode *typeof_array = alloc_node<IdentifierNode>();
|
|
typeof_array->name = "TYPE_ARRAY";
|
|
|
|
type_comp = alloc_node<OperatorNode>();
|
|
type_comp->op = OperatorNode::OP_EQUAL;
|
|
type_comp->arguments.push_back(typeof_match_value);
|
|
type_comp->arguments.push_back(typeof_array);
|
|
}
|
|
|
|
// size
|
|
ConstantNode *length = alloc_node<ConstantNode>();
|
|
length->value = Variant(open_ended ? p_pattern->array.size() - 1 : p_pattern->array.size());
|
|
|
|
OperatorNode *call = alloc_node<OperatorNode>();
|
|
call->op = OperatorNode::OP_CALL;
|
|
call->arguments.push_back(p_node_to_match);
|
|
|
|
IdentifierNode *size = alloc_node<IdentifierNode>();
|
|
size->name = "size";
|
|
call->arguments.push_back(size);
|
|
|
|
OperatorNode *length_comparison = alloc_node<OperatorNode>();
|
|
length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL;
|
|
length_comparison->arguments.push_back(call);
|
|
length_comparison->arguments.push_back(length);
|
|
|
|
if (type_comp) {
|
|
OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
|
|
type_and_length_comparison->op = OperatorNode::OP_AND;
|
|
type_and_length_comparison->arguments.push_back(type_comp);
|
|
type_and_length_comparison->arguments.push_back(length_comparison);
|
|
|
|
p_resulting_node = type_and_length_comparison;
|
|
} else {
|
|
p_resulting_node = length_comparison;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < p_pattern->array.size(); i++) {
|
|
PatternNode *pattern = p_pattern->array[i];
|
|
|
|
Node *condition = nullptr;
|
|
|
|
ConstantNode *index = alloc_node<ConstantNode>();
|
|
index->value = Variant(i);
|
|
|
|
OperatorNode *indexed_value = alloc_node<OperatorNode>();
|
|
indexed_value->op = OperatorNode::OP_INDEX;
|
|
indexed_value->arguments.push_back(p_node_to_match);
|
|
indexed_value->arguments.push_back(index);
|
|
|
|
_generate_pattern(pattern, indexed_value, condition, p_bindings);
|
|
|
|
// concatenate all the patterns with &&
|
|
OperatorNode *and_node = alloc_node<OperatorNode>();
|
|
and_node->op = OperatorNode::OP_AND;
|
|
and_node->arguments.push_back(p_resulting_node);
|
|
and_node->arguments.push_back(condition);
|
|
|
|
p_resulting_node = and_node;
|
|
}
|
|
|
|
} break;
|
|
case PatternNode::PT_DICTIONARY: {
|
|
bool open_ended = false;
|
|
|
|
if (p_pattern->array.size() > 0) {
|
|
open_ended = true;
|
|
}
|
|
|
|
// typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() >= length
|
|
// typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length
|
|
|
|
{
|
|
OperatorNode *type_comp = nullptr;
|
|
// static type check if possible
|
|
if (to_match_type.has_type) {
|
|
// must be an dictionary
|
|
if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) {
|
|
_set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line);
|
|
return;
|
|
}
|
|
} else {
|
|
// runtime typecheck
|
|
BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
|
|
typeof_node->function = CScriptFunctions::TYPE_OF;
|
|
|
|
OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
|
|
typeof_match_value->op = OperatorNode::OP_CALL;
|
|
typeof_match_value->arguments.push_back(typeof_node);
|
|
typeof_match_value->arguments.push_back(p_node_to_match);
|
|
|
|
IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>();
|
|
typeof_dictionary->name = "TYPE_DICTIONARY";
|
|
|
|
type_comp = alloc_node<OperatorNode>();
|
|
type_comp->op = OperatorNode::OP_EQUAL;
|
|
type_comp->arguments.push_back(typeof_match_value);
|
|
type_comp->arguments.push_back(typeof_dictionary);
|
|
}
|
|
|
|
// size
|
|
ConstantNode *length = alloc_node<ConstantNode>();
|
|
length->value = Variant(open_ended ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size());
|
|
|
|
OperatorNode *call = alloc_node<OperatorNode>();
|
|
call->op = OperatorNode::OP_CALL;
|
|
call->arguments.push_back(p_node_to_match);
|
|
|
|
IdentifierNode *size = alloc_node<IdentifierNode>();
|
|
size->name = "size";
|
|
call->arguments.push_back(size);
|
|
|
|
OperatorNode *length_comparison = alloc_node<OperatorNode>();
|
|
length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL;
|
|
length_comparison->arguments.push_back(call);
|
|
length_comparison->arguments.push_back(length);
|
|
|
|
if (type_comp) {
|
|
OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
|
|
type_and_length_comparison->op = OperatorNode::OP_AND;
|
|
type_and_length_comparison->arguments.push_back(type_comp);
|
|
type_and_length_comparison->arguments.push_back(length_comparison);
|
|
|
|
p_resulting_node = type_and_length_comparison;
|
|
} else {
|
|
p_resulting_node = length_comparison;
|
|
}
|
|
}
|
|
|
|
for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) {
|
|
Node *condition = nullptr;
|
|
|
|
// check for has, then for pattern
|
|
|
|
IdentifierNode *has = alloc_node<IdentifierNode>();
|
|
has->name = "has";
|
|
|
|
OperatorNode *has_call = alloc_node<OperatorNode>();
|
|
has_call->op = OperatorNode::OP_CALL;
|
|
has_call->arguments.push_back(p_node_to_match);
|
|
has_call->arguments.push_back(has);
|
|
has_call->arguments.push_back(e->key());
|
|
|
|
if (e->value()) {
|
|
OperatorNode *indexed_value = alloc_node<OperatorNode>();
|
|
indexed_value->op = OperatorNode::OP_INDEX;
|
|
indexed_value->arguments.push_back(p_node_to_match);
|
|
indexed_value->arguments.push_back(e->key());
|
|
|
|
_generate_pattern(e->value(), indexed_value, condition, p_bindings);
|
|
|
|
OperatorNode *has_and_pattern = alloc_node<OperatorNode>();
|
|
has_and_pattern->op = OperatorNode::OP_AND;
|
|
has_and_pattern->arguments.push_back(has_call);
|
|
has_and_pattern->arguments.push_back(condition);
|
|
|
|
condition = has_and_pattern;
|
|
|
|
} else {
|
|
condition = has_call;
|
|
}
|
|
|
|
// concatenate all the patterns with &&
|
|
OperatorNode *and_node = alloc_node<OperatorNode>();
|
|
and_node->op = OperatorNode::OP_AND;
|
|
and_node->arguments.push_back(p_resulting_node);
|
|
and_node->arguments.push_back(condition);
|
|
|
|
p_resulting_node = and_node;
|
|
}
|
|
|
|
} break;
|
|
case PatternNode::PT_IGNORE_REST:
|
|
case PatternNode::PT_WILDCARD: {
|
|
// simply generate a `true`
|
|
ConstantNode *true_value = alloc_node<ConstantNode>();
|
|
true_value->value = Variant(true);
|
|
p_resulting_node = true_value;
|
|
} break;
|
|
default: {
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void CScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = "#match_value";
|
|
id->line = p_match_statement->line;
|
|
id->datatype = _reduce_node_type(p_match_statement->val_to_match);
|
|
if (id->datatype.has_type) {
|
|
_mark_line_as_safe(id->line);
|
|
} else {
|
|
_mark_line_as_unsafe(id->line);
|
|
}
|
|
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < p_match_statement->branches.size(); i++) {
|
|
PatternBranchNode *branch = p_match_statement->branches[i];
|
|
|
|
MatchNode::CompiledPatternBranch compiled_branch;
|
|
compiled_branch.compiled_pattern = nullptr;
|
|
|
|
Map<StringName, Node *> binding;
|
|
|
|
for (int j = 0; j < branch->patterns.size(); j++) {
|
|
PatternNode *pattern = branch->patterns[j];
|
|
_mark_line_as_safe(pattern->line);
|
|
|
|
Map<StringName, Node *> bindings;
|
|
Node *resulting_node = nullptr;
|
|
_generate_pattern(pattern, id, resulting_node, bindings);
|
|
|
|
if (!resulting_node) {
|
|
return;
|
|
}
|
|
|
|
if (!binding.empty() && !bindings.empty()) {
|
|
_set_error("Multipatterns can't contain bindings");
|
|
return;
|
|
} else {
|
|
binding = bindings;
|
|
}
|
|
|
|
// Result is always a boolean
|
|
DataType resulting_node_type;
|
|
resulting_node_type.has_type = true;
|
|
resulting_node_type.is_constant = true;
|
|
resulting_node_type.kind = DataType::BUILTIN;
|
|
resulting_node_type.builtin_type = Variant::BOOL;
|
|
resulting_node->set_datatype(resulting_node_type);
|
|
|
|
if (compiled_branch.compiled_pattern) {
|
|
OperatorNode *or_node = alloc_node<OperatorNode>();
|
|
or_node->op = OperatorNode::OP_OR;
|
|
or_node->arguments.push_back(compiled_branch.compiled_pattern);
|
|
or_node->arguments.push_back(resulting_node);
|
|
|
|
compiled_branch.compiled_pattern = or_node;
|
|
} else {
|
|
// single pattern | first one
|
|
compiled_branch.compiled_pattern = resulting_node;
|
|
}
|
|
}
|
|
|
|
// prepare the body ...hehe
|
|
for (Map<StringName, Node *>::Element *e = binding.front(); e; e = e->next()) {
|
|
if (!branch->body->variables.has(e->key())) {
|
|
_set_error("Parser bug: missing pattern bind variable.", branch->line);
|
|
ERR_FAIL();
|
|
}
|
|
|
|
LocalVarNode *local_var = branch->body->variables[e->key()];
|
|
local_var->assign = e->value();
|
|
local_var->set_datatype(local_var->assign->get_datatype());
|
|
local_var->assignments++;
|
|
|
|
IdentifierNode *id2 = alloc_node<IdentifierNode>();
|
|
id2->name = local_var->name;
|
|
id2->datatype = local_var->datatype;
|
|
id2->declared_block = branch->body;
|
|
id2->set_datatype(local_var->assign->get_datatype());
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_ASSIGN;
|
|
op->arguments.push_back(id2);
|
|
op->arguments.push_back(local_var->assign);
|
|
local_var->assign_op = op;
|
|
|
|
branch->body->statements.insert(0, op);
|
|
branch->body->statements.insert(0, local_var);
|
|
}
|
|
|
|
compiled_branch.body = branch->body;
|
|
|
|
p_match_statement->compiled_pattern_branches.push_back(compiled_branch);
|
|
}
|
|
}
|
|
|
|
void CScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
|
|
IndentLevel current_level = indent_level.back()->get();
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
pending_newline = -1; // reset for the new block
|
|
|
|
NewLineNode *nl = alloc_node<NewLineNode>();
|
|
|
|
nl->line = tokenizer->get_token_line();
|
|
p_block->statements.push_back(nl);
|
|
#endif
|
|
|
|
bool is_first_line = true;
|
|
|
|
while (true) {
|
|
if (!is_first_line && indent_level.back()->prev() && indent_level.back()->prev()->get().indent == current_level.indent) {
|
|
if (indent_level.back()->prev()->get().is_mixed(current_level)) {
|
|
_set_error("Mixed tabs and spaces in indentation.");
|
|
return;
|
|
}
|
|
// pythonic single-line expression, don't parse future lines
|
|
indent_level.pop_back();
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return;
|
|
}
|
|
is_first_line = false;
|
|
|
|
CScriptTokenizer::Token token = tokenizer->get_token();
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
|
|
if (current_level.indent > indent_level.back()->get().indent) {
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return; //go back a level
|
|
}
|
|
|
|
if (pending_newline != -1) {
|
|
NewLineNode *nl2 = alloc_node<NewLineNode>();
|
|
nl2->line = pending_newline;
|
|
p_block->statements.push_back(nl2);
|
|
pending_newline = -1;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
switch (token) {
|
|
case CScriptTokenizer::TK_EOF:
|
|
case CScriptTokenizer::TK_ERROR:
|
|
case CScriptTokenizer::TK_NEWLINE:
|
|
case CScriptTokenizer::TK_CF_PASS: {
|
|
// will check later
|
|
} break;
|
|
default: {
|
|
if (p_block->has_return && !current_function->has_unreachable_code) {
|
|
_add_warning(CScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String());
|
|
current_function->has_unreachable_code = true;
|
|
}
|
|
} break;
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
switch (token) {
|
|
case CScriptTokenizer::TK_EOF:
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
case CScriptTokenizer::TK_ERROR: {
|
|
return; //go back
|
|
|
|
//end of file!
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_NEWLINE: {
|
|
int line = tokenizer->get_token_line();
|
|
|
|
if (!_parse_newline()) {
|
|
if (!error_set) {
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
pending_newline = p_block->end_line;
|
|
}
|
|
return;
|
|
}
|
|
|
|
_mark_line_as_safe(line);
|
|
NewLineNode *nl2 = alloc_node<NewLineNode>();
|
|
nl2->line = line;
|
|
p_block->statements.push_back(nl2);
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_CF_PASS: {
|
|
if (tokenizer->get_token(1) != CScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1) != CScriptTokenizer::TK_NEWLINE && tokenizer->get_token(1) != CScriptTokenizer::TK_EOF) {
|
|
_set_error("Expected \";\" or a line break.");
|
|
return;
|
|
}
|
|
_mark_line_as_safe(tokenizer->get_token_line());
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_SEMICOLON) {
|
|
// Ignore semicolon after 'pass'.
|
|
tokenizer->advance();
|
|
}
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_VAR: {
|
|
// Variable declaration and (eventual) initialization.
|
|
|
|
tokenizer->advance();
|
|
int var_line = tokenizer->get_token_line();
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
_set_error("Expected an identifier for the local variable name.");
|
|
return;
|
|
}
|
|
StringName n = tokenizer->get_token_literal();
|
|
if (current_function) {
|
|
for (int i = 0; i < current_function->arguments.size(); i++) {
|
|
if (n == current_function->arguments[i]) {
|
|
_set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(current_function->line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
BlockNode *check_block = p_block;
|
|
while (check_block) {
|
|
if (check_block->variables.has(n)) {
|
|
_set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(check_block->variables[n]->line) + ").");
|
|
return;
|
|
}
|
|
check_block = check_block->parent_block;
|
|
}
|
|
tokenizer->advance();
|
|
|
|
//must know when the local variable is declared
|
|
LocalVarNode *lv = alloc_node<LocalVarNode>();
|
|
lv->name = n;
|
|
lv->line = var_line;
|
|
p_block->statements.push_back(lv);
|
|
|
|
Node *assigned = nullptr;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COLON) {
|
|
if (tokenizer->get_token(1) == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
lv->datatype = DataType();
|
|
lv->datatype.infer_type = true;
|
|
tokenizer->advance();
|
|
} else if (!_parse_type(lv->datatype)) {
|
|
_set_error("Expected a type for the variable.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
tokenizer->advance();
|
|
Node *subexpr = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!subexpr) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
lv->assignments++;
|
|
assigned = subexpr;
|
|
} else {
|
|
assigned = _get_default_value_for_type(lv->datatype, var_line);
|
|
}
|
|
//must be added later, to avoid self-referencing.
|
|
p_block->variables.insert(n, lv);
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = n;
|
|
id->declared_block = p_block;
|
|
id->line = var_line;
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_ASSIGN;
|
|
op->arguments.push_back(id);
|
|
op->arguments.push_back(assigned);
|
|
op->line = var_line;
|
|
p_block->statements.push_back(op);
|
|
lv->assign_op = op;
|
|
lv->assign = assigned;
|
|
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("var");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_CF_IF: {
|
|
tokenizer->advance();
|
|
|
|
Node *condition = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!condition) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
ControlFlowNode *cf_if = alloc_node<ControlFlowNode>();
|
|
|
|
cf_if->cf_type = ControlFlowNode::CF_IF;
|
|
cf_if->arguments.push_back(condition);
|
|
|
|
cf_if->body = alloc_node<BlockNode>();
|
|
cf_if->body->parent_block = p_block;
|
|
cf_if->body->if_condition = condition; //helps code completion
|
|
|
|
p_block->sub_blocks.push_back(cf_if->body);
|
|
|
|
if (!_enter_indent_block(cf_if->body)) {
|
|
_set_error("Expected an indented block after \"if\".");
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return;
|
|
}
|
|
|
|
current_block = cf_if->body;
|
|
_parse_block(cf_if->body, p_static);
|
|
current_block = p_block;
|
|
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
p_block->statements.push_back(cf_if);
|
|
|
|
bool all_have_return = cf_if->body->has_return;
|
|
bool have_else = false;
|
|
|
|
while (true) {
|
|
while (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE && _parse_newline()) {
|
|
;
|
|
}
|
|
|
|
if (indent_level.back()->get().indent < current_level.indent) { //not at current indent level
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CF_ELIF) {
|
|
if (indent_level.back()->get().indent > current_level.indent) {
|
|
_set_error("Invalid indentation.");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
cf_if->body_else = alloc_node<BlockNode>();
|
|
cf_if->body_else->parent_block = p_block;
|
|
p_block->sub_blocks.push_back(cf_if->body_else);
|
|
|
|
ControlFlowNode *cf_else = alloc_node<ControlFlowNode>();
|
|
cf_else->cf_type = ControlFlowNode::CF_IF;
|
|
|
|
//condition
|
|
Node *condition2 = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!condition2) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
cf_else->arguments.push_back(condition2);
|
|
cf_else->cf_type = ControlFlowNode::CF_IF;
|
|
|
|
cf_if->body_else->statements.push_back(cf_else);
|
|
cf_if = cf_else;
|
|
cf_if->body = alloc_node<BlockNode>();
|
|
cf_if->body->parent_block = p_block;
|
|
p_block->sub_blocks.push_back(cf_if->body);
|
|
|
|
if (!_enter_indent_block(cf_if->body)) {
|
|
_set_error("Expected an indented block after \"elif\".");
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return;
|
|
}
|
|
|
|
current_block = cf_else->body;
|
|
_parse_block(cf_else->body, p_static);
|
|
current_block = p_block;
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
|
|
all_have_return = all_have_return && cf_else->body->has_return;
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CF_ELSE) {
|
|
if (indent_level.back()->get().indent > current_level.indent) {
|
|
_set_error("Invalid indentation.");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
cf_if->body_else = alloc_node<BlockNode>();
|
|
cf_if->body_else->parent_block = p_block;
|
|
p_block->sub_blocks.push_back(cf_if->body_else);
|
|
|
|
if (!_enter_indent_block(cf_if->body_else)) {
|
|
_set_error("Expected an indented block after \"else\".");
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return;
|
|
}
|
|
current_block = cf_if->body_else;
|
|
_parse_block(cf_if->body_else, p_static);
|
|
current_block = p_block;
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
|
|
all_have_return = all_have_return && cf_if->body_else->has_return;
|
|
have_else = true;
|
|
|
|
break; //after else, exit
|
|
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
cf_if->body->has_return = all_have_return;
|
|
// If there's no else block, path out of the if might not have a return
|
|
p_block->has_return = all_have_return && have_else;
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_CF_WHILE: {
|
|
tokenizer->advance();
|
|
Node *condition2 = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!condition2) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
ControlFlowNode *cf_while = alloc_node<ControlFlowNode>();
|
|
|
|
cf_while->cf_type = ControlFlowNode::CF_WHILE;
|
|
cf_while->arguments.push_back(condition2);
|
|
|
|
cf_while->body = alloc_node<BlockNode>();
|
|
cf_while->body->parent_block = p_block;
|
|
cf_while->body->can_break = true;
|
|
cf_while->body->can_continue = true;
|
|
p_block->sub_blocks.push_back(cf_while->body);
|
|
|
|
if (!_enter_indent_block(cf_while->body)) {
|
|
_set_error("Expected an indented block after \"while\".");
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return;
|
|
}
|
|
|
|
current_block = cf_while->body;
|
|
_parse_block(cf_while->body, p_static);
|
|
current_block = p_block;
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
p_block->statements.push_back(cf_while);
|
|
} break;
|
|
case CScriptTokenizer::TK_CF_FOR: {
|
|
tokenizer->advance();
|
|
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
_set_error("Identifier expected after \"for\".");
|
|
}
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = tokenizer->get_token_identifier();
|
|
#ifdef DEBUG_ENABLED
|
|
for (int j = 0; j < current_class->variables.size(); j++) {
|
|
if (current_class->variables[j].identifier == id->name) {
|
|
_add_warning(CScriptWarning::SHADOWED_VARIABLE, id->line, id->name, itos(current_class->variables[j].line));
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
BlockNode *check_block = p_block;
|
|
while (check_block) {
|
|
if (check_block->variables.has(id->name)) {
|
|
_set_error("Variable \"" + String(id->name) + "\" already defined in the scope (at line " + itos(check_block->variables[id->name]->line) + ").");
|
|
return;
|
|
}
|
|
check_block = check_block->parent_block;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_OP_IN) {
|
|
_set_error("\"in\" expected after identifier.");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
Node *container = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!container) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
DataType iter_type;
|
|
|
|
if (container->type == Node::TYPE_OPERATOR) {
|
|
OperatorNode *op = static_cast<OperatorNode *>(container);
|
|
if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && static_cast<BuiltInFunctionNode *>(op->arguments[0])->function == CScriptFunctions::GEN_RANGE) {
|
|
//iterating a range, so see if range() can be optimized without allocating memory, by replacing it by vectors (which can work as iterable too!)
|
|
|
|
Vector<Node *> args;
|
|
Vector<double> constants;
|
|
|
|
bool constant = true;
|
|
|
|
for (int i = 1; i < op->arguments.size(); i++) {
|
|
args.push_back(op->arguments[i]);
|
|
if (op->arguments[i]->type == Node::TYPE_CONSTANT) {
|
|
ConstantNode *c = static_cast<ConstantNode *>(op->arguments[i]);
|
|
if (c->value.get_type() == Variant::REAL || c->value.get_type() == Variant::INT) {
|
|
constants.push_back(c->value);
|
|
} else {
|
|
constant = false;
|
|
}
|
|
} else {
|
|
constant = false;
|
|
}
|
|
}
|
|
|
|
if (args.size() > 0 && args.size() < 4) {
|
|
if (constant) {
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
switch (args.size()) {
|
|
case 1:
|
|
cn->value = (int)constants[0];
|
|
break;
|
|
case 2:
|
|
cn->value = Vector2(constants[0], constants[1]);
|
|
break;
|
|
case 3:
|
|
cn->value = Vector3(constants[0], constants[1], constants[2]);
|
|
break;
|
|
}
|
|
cn->datatype = _type_from_variant(cn->value);
|
|
container = cn;
|
|
} else {
|
|
OperatorNode *on = alloc_node<OperatorNode>();
|
|
on->op = OperatorNode::OP_CALL;
|
|
|
|
TypeNode *tn = alloc_node<TypeNode>();
|
|
on->arguments.push_back(tn);
|
|
|
|
switch (args.size()) {
|
|
case 1:
|
|
tn->vtype = Variant::INT;
|
|
break;
|
|
case 2:
|
|
tn->vtype = Variant::VECTOR2;
|
|
break;
|
|
case 3:
|
|
tn->vtype = Variant::VECTOR3;
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < args.size(); i++) {
|
|
on->arguments.push_back(args[i]);
|
|
}
|
|
|
|
container = on;
|
|
}
|
|
}
|
|
|
|
iter_type.has_type = true;
|
|
iter_type.kind = DataType::BUILTIN;
|
|
iter_type.builtin_type = Variant::INT;
|
|
}
|
|
}
|
|
|
|
ControlFlowNode *cf_for = alloc_node<ControlFlowNode>();
|
|
|
|
cf_for->cf_type = ControlFlowNode::CF_FOR;
|
|
cf_for->arguments.push_back(id);
|
|
cf_for->arguments.push_back(container);
|
|
|
|
cf_for->body = alloc_node<BlockNode>();
|
|
cf_for->body->parent_block = p_block;
|
|
cf_for->body->can_break = true;
|
|
cf_for->body->can_continue = true;
|
|
p_block->sub_blocks.push_back(cf_for->body);
|
|
|
|
if (!_enter_indent_block(cf_for->body)) {
|
|
_set_error("Expected indented block after \"for\".");
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return;
|
|
}
|
|
|
|
current_block = cf_for->body;
|
|
|
|
// this is for checking variable for redefining
|
|
// inside this _parse_block
|
|
LocalVarNode *lv = alloc_node<LocalVarNode>();
|
|
lv->name = id->name;
|
|
lv->line = id->line;
|
|
lv->assignments++;
|
|
id->declared_block = cf_for->body;
|
|
lv->set_datatype(iter_type);
|
|
id->set_datatype(iter_type);
|
|
cf_for->body->variables.insert(id->name, lv);
|
|
_parse_block(cf_for->body, p_static);
|
|
current_block = p_block;
|
|
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
p_block->statements.push_back(cf_for);
|
|
} break;
|
|
case CScriptTokenizer::TK_CF_CONTINUE: {
|
|
BlockNode *upper_block = p_block;
|
|
bool is_continue_valid = false;
|
|
while (upper_block) {
|
|
if (upper_block->can_continue) {
|
|
is_continue_valid = true;
|
|
break;
|
|
}
|
|
upper_block = upper_block->parent_block;
|
|
}
|
|
|
|
if (!is_continue_valid) {
|
|
_set_error("Unexpected keyword \"continue\" outside a loop.");
|
|
return;
|
|
}
|
|
|
|
_mark_line_as_safe(tokenizer->get_token_line());
|
|
tokenizer->advance();
|
|
ControlFlowNode *cf_continue = alloc_node<ControlFlowNode>();
|
|
cf_continue->cf_type = ControlFlowNode::CF_CONTINUE;
|
|
p_block->statements.push_back(cf_continue);
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("continue");
|
|
return;
|
|
}
|
|
} break;
|
|
case CScriptTokenizer::TK_CF_BREAK: {
|
|
BlockNode *upper_block = p_block;
|
|
bool is_break_valid = false;
|
|
while (upper_block) {
|
|
if (upper_block->can_break) {
|
|
is_break_valid = true;
|
|
break;
|
|
}
|
|
upper_block = upper_block->parent_block;
|
|
}
|
|
|
|
if (!is_break_valid) {
|
|
_set_error("Unexpected keyword \"break\" outside a loop.");
|
|
return;
|
|
}
|
|
|
|
_mark_line_as_safe(tokenizer->get_token_line());
|
|
tokenizer->advance();
|
|
ControlFlowNode *cf_break = alloc_node<ControlFlowNode>();
|
|
cf_break->cf_type = ControlFlowNode::CF_BREAK;
|
|
p_block->statements.push_back(cf_break);
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("break");
|
|
return;
|
|
}
|
|
} break;
|
|
case CScriptTokenizer::TK_CF_RETURN: {
|
|
tokenizer->advance();
|
|
ControlFlowNode *cf_return = alloc_node<ControlFlowNode>();
|
|
cf_return->cf_type = ControlFlowNode::CF_RETURN;
|
|
cf_return->line = tokenizer->get_token_line(-1);
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_SEMICOLON || tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == CScriptTokenizer::TK_EOF) {
|
|
//expect end of statement
|
|
p_block->statements.push_back(cf_return);
|
|
if (!_end_statement()) {
|
|
return;
|
|
}
|
|
} else {
|
|
//expect expression
|
|
Node *retexpr = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!retexpr) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
cf_return->arguments.push_back(retexpr);
|
|
p_block->statements.push_back(cf_return);
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("return");
|
|
return;
|
|
}
|
|
}
|
|
p_block->has_return = true;
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_CF_MATCH: {
|
|
tokenizer->advance();
|
|
|
|
MatchNode *match_node = alloc_node<MatchNode>();
|
|
|
|
Node *val_to_match = _parse_and_reduce_expression(p_block, p_static);
|
|
|
|
if (!val_to_match) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
match_node->val_to_match = val_to_match;
|
|
|
|
if (!_enter_indent_block()) {
|
|
_set_error("Expected indented pattern matching block after \"match\".");
|
|
return;
|
|
}
|
|
|
|
BlockNode *compiled_branches = alloc_node<BlockNode>();
|
|
compiled_branches->parent_block = p_block;
|
|
compiled_branches->parent_class = p_block->parent_class;
|
|
compiled_branches->can_continue = true;
|
|
|
|
p_block->sub_blocks.push_back(compiled_branches);
|
|
|
|
_parse_pattern_block(compiled_branches, match_node->branches, p_static);
|
|
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
|
|
ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>();
|
|
match_cf_node->cf_type = ControlFlowNode::CF_MATCH;
|
|
match_cf_node->match = match_node;
|
|
match_cf_node->body = compiled_branches;
|
|
|
|
p_block->has_return = p_block->has_return || compiled_branches->has_return;
|
|
p_block->statements.push_back(match_cf_node);
|
|
|
|
_end_statement();
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_ASSERT: {
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
_set_error("Expected '(' after assert");
|
|
return;
|
|
}
|
|
|
|
int assert_line = tokenizer->get_token_line();
|
|
|
|
tokenizer->advance();
|
|
|
|
Vector<Node *> args;
|
|
const bool result = _parse_arguments(p_block, args, p_static);
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
if (args.empty() || args.size() > 2) {
|
|
_set_error("Wrong number of arguments, expected 1 or 2", assert_line);
|
|
return;
|
|
}
|
|
|
|
AssertNode *an = alloc_node<AssertNode>();
|
|
an->condition = _reduce_expression(args[0], p_static);
|
|
an->line = assert_line;
|
|
|
|
if (args.size() == 2) {
|
|
an->message = _reduce_expression(args[1], p_static);
|
|
} else {
|
|
ConstantNode *message_node = alloc_node<ConstantNode>();
|
|
message_node->value = String();
|
|
an->message = message_node;
|
|
}
|
|
|
|
p_block->statements.push_back(an);
|
|
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("assert");
|
|
return;
|
|
}
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_BREAKPOINT: {
|
|
tokenizer->advance();
|
|
BreakpointNode *bn = alloc_node<BreakpointNode>();
|
|
p_block->statements.push_back(bn);
|
|
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("breakpoint");
|
|
return;
|
|
}
|
|
} break;
|
|
default: {
|
|
Node *expression = _parse_and_reduce_expression(p_block, p_static, false, true);
|
|
if (!expression) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
p_block->statements.push_back(expression);
|
|
if (!_end_statement()) {
|
|
// Attempt to guess a better error message if the user "retypes" a variable
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COLON && tokenizer->get_token(1) == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
_set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression.");
|
|
} else {
|
|
_set_error(vformat("Expected end of statement after expression, got %s instead.", tokenizer->get_token_name(tokenizer->get_token())));
|
|
}
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CScriptParser::_parse_newline() {
|
|
if (tokenizer->get_token(1) != CScriptTokenizer::TK_EOF && tokenizer->get_token(1) != CScriptTokenizer::TK_NEWLINE) {
|
|
IndentLevel current_level = indent_level.back()->get();
|
|
int indent = tokenizer->get_token_line_indent();
|
|
int tabs = tokenizer->get_token_line_tab_indent();
|
|
IndentLevel new_level(indent, tabs);
|
|
|
|
if (new_level.is_mixed(current_level)) {
|
|
_set_error("Mixed tabs and spaces in indentation.");
|
|
return false;
|
|
}
|
|
|
|
if (indent > current_level.indent) {
|
|
_set_error("Unexpected indentation.");
|
|
return false;
|
|
}
|
|
|
|
if (indent < current_level.indent) {
|
|
while (indent < current_level.indent) {
|
|
//exit block
|
|
if (indent_level.size() == 1) {
|
|
_set_error("Invalid indentation. Bug?");
|
|
return false;
|
|
}
|
|
|
|
indent_level.pop_back();
|
|
|
|
if (indent_level.back()->get().indent < indent) {
|
|
_set_error("Unindent does not match any outer indentation level.");
|
|
return false;
|
|
}
|
|
|
|
if (indent_level.back()->get().is_mixed(current_level)) {
|
|
_set_error("Mixed tabs and spaces in indentation.");
|
|
return false;
|
|
}
|
|
|
|
current_level = indent_level.back()->get();
|
|
}
|
|
|
|
tokenizer->advance();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
return true;
|
|
}
|
|
|
|
void CScriptParser::_parse_extends(ClassNode *p_class) {
|
|
if (p_class->extends_used) {
|
|
_set_error("\"extends\" can only be present once per script.");
|
|
return;
|
|
}
|
|
|
|
if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty()) {
|
|
_set_error("\"extends\" must be used before anything else.");
|
|
return;
|
|
}
|
|
|
|
p_class->extends_used = true;
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token_type() == Variant::OBJECT) {
|
|
p_class->extends_class.push_back(Variant::get_type_name(Variant::OBJECT));
|
|
tokenizer->advance();
|
|
return;
|
|
}
|
|
|
|
// see if inheritance happens from a file
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CONSTANT) {
|
|
Variant constant = tokenizer->get_token_constant();
|
|
if (constant.get_type() != Variant::STRING) {
|
|
_set_error("\"extends\" constant must be a string.");
|
|
return;
|
|
}
|
|
|
|
p_class->extends_file = constant;
|
|
tokenizer->advance();
|
|
|
|
// Add parent script as a dependency
|
|
String parent = constant;
|
|
if (parent.is_rel_path()) {
|
|
parent = base_path.plus_file(parent).simplify_path();
|
|
}
|
|
dependencies.push_back(parent);
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PERIOD) {
|
|
return;
|
|
} else {
|
|
tokenizer->advance();
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
switch (tokenizer->get_token()) {
|
|
case CScriptTokenizer::TK_IDENTIFIER: {
|
|
StringName identifier = tokenizer->get_token_identifier();
|
|
p_class->extends_class.push_back(identifier);
|
|
} break;
|
|
|
|
case CScriptTokenizer::TK_CURSOR:
|
|
case CScriptTokenizer::TK_PERIOD:
|
|
break;
|
|
|
|
default: {
|
|
_set_error("Invalid \"extends\" syntax, expected string constant (path) and/or identifier (parent class).");
|
|
return;
|
|
}
|
|
}
|
|
|
|
tokenizer->advance(1);
|
|
|
|
switch (tokenizer->get_token()) {
|
|
case CScriptTokenizer::TK_IDENTIFIER:
|
|
case CScriptTokenizer::TK_PERIOD:
|
|
continue;
|
|
case CScriptTokenizer::TK_CURSOR:
|
|
completion_type = COMPLETION_EXTENDS;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_block = current_block;
|
|
completion_ident_is_call = false;
|
|
completion_found = true;
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CScriptParser::_parse_class(ClassNode *p_class) {
|
|
IndentLevel current_level = indent_level.back()->get();
|
|
|
|
while (true) {
|
|
CScriptTokenizer::Token token = tokenizer->get_token();
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
|
|
if (current_level.indent > indent_level.back()->get().indent) {
|
|
p_class->end_line = tokenizer->get_token_line();
|
|
return; //go back a level
|
|
}
|
|
|
|
switch (token) {
|
|
case CScriptTokenizer::TK_CURSOR: {
|
|
tokenizer->advance();
|
|
} break;
|
|
case CScriptTokenizer::TK_EOF:
|
|
p_class->end_line = tokenizer->get_token_line();
|
|
case CScriptTokenizer::TK_ERROR: {
|
|
return; //go back
|
|
//end of file!
|
|
} break;
|
|
case CScriptTokenizer::TK_NEWLINE: {
|
|
if (!_parse_newline()) {
|
|
if (!error_set) {
|
|
p_class->end_line = tokenizer->get_token_line();
|
|
}
|
|
return;
|
|
}
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_EXTENDS: {
|
|
_mark_line_as_safe(tokenizer->get_token_line());
|
|
_parse_extends(p_class);
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("extends");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_CLASS_NAME: {
|
|
_mark_line_as_safe(tokenizer->get_token_line());
|
|
if (p_class->owner) {
|
|
_set_error("\"class_name\" is only valid for the main class namespace.");
|
|
return;
|
|
}
|
|
if (self_path.begins_with("res://") && self_path.find("::") != -1) {
|
|
_set_error("\"class_name\" isn't allowed in built-in scripts.");
|
|
return;
|
|
}
|
|
if (tokenizer->get_token(1) != CScriptTokenizer::TK_IDENTIFIER) {
|
|
_set_error("\"class_name\" syntax: \"class_name <UniqueName>\"");
|
|
return;
|
|
}
|
|
if (p_class->classname_used) {
|
|
_set_error("\"class_name\" can only be present once per script.");
|
|
return;
|
|
}
|
|
|
|
p_class->classname_used = true;
|
|
|
|
p_class->name = tokenizer->get_token_identifier(1);
|
|
|
|
if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) {
|
|
_set_error("Unique global class \"" + p_class->name + "\" already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
|
|
return;
|
|
}
|
|
|
|
if (ClassDB::class_exists(p_class->name)) {
|
|
_set_error("The class \"" + p_class->name + "\" shadows a native class.");
|
|
return;
|
|
}
|
|
|
|
if (p_class->classname_used && ProjectSettings::get_singleton()->has_setting("autoload/" + p_class->name)) {
|
|
const String autoload_path = ProjectSettings::get_singleton()->get_setting("autoload/" + p_class->name);
|
|
if (autoload_path.begins_with("*")) {
|
|
// It's a singleton, and not just a regular AutoLoad script.
|
|
_set_error("The class \"" + p_class->name + "\" conflicts with the AutoLoad singleton of the same name, and is therefore redundant. Remove the class_name declaration to fix this error.");
|
|
}
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance(2);
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
|
|
if ((tokenizer->get_token() == CScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING)) {
|
|
#ifdef TOOLS_ENABLED
|
|
if (Engine::get_singleton()->is_editor_hint()) {
|
|
Variant constant = tokenizer->get_token_constant();
|
|
String icon_path = constant.operator String();
|
|
|
|
String abs_icon_path = icon_path.is_rel_path() ? self_path.get_base_dir().plus_file(icon_path).simplify_path() : icon_path;
|
|
if (!FileAccess::exists(abs_icon_path)) {
|
|
_set_error("No class icon found at: " + abs_icon_path);
|
|
return;
|
|
}
|
|
|
|
p_class->icon_path = icon_path;
|
|
}
|
|
#endif
|
|
|
|
tokenizer->advance();
|
|
} else {
|
|
_set_error("The optional parameter after \"class_name\" must be a string constant file path to an icon.");
|
|
return;
|
|
}
|
|
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CONSTANT) {
|
|
_set_error("The class icon must be separated by a comma.");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_TOOL: {
|
|
if (p_class->tool) {
|
|
_set_error("The \"tool\" keyword can only be present once per script.");
|
|
return;
|
|
}
|
|
|
|
p_class->tool = true;
|
|
tokenizer->advance();
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_CLASS: {
|
|
//class inside class :D
|
|
|
|
StringName name;
|
|
|
|
if (tokenizer->get_token(1) != CScriptTokenizer::TK_IDENTIFIER) {
|
|
_set_error("\"class\" syntax: \"class <Name>:\" or \"class <Name> extends <BaseClass>:\"");
|
|
return;
|
|
}
|
|
name = tokenizer->get_token_identifier(1);
|
|
tokenizer->advance(2);
|
|
|
|
// Check if name is shadowing something else
|
|
if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) {
|
|
_set_error("The class \"" + String(name) + "\" shadows a native class.");
|
|
return;
|
|
}
|
|
if (ScriptServer::is_global_class(name)) {
|
|
_set_error("Can't override name of the unique global class \"" + name + "\". It already exists at: " + ScriptServer::get_global_class_path(p_class->name));
|
|
return;
|
|
}
|
|
ClassNode *outer_class = p_class;
|
|
while (outer_class) {
|
|
for (int i = 0; i < outer_class->subclasses.size(); i++) {
|
|
if (outer_class->subclasses[i]->name == name) {
|
|
_set_error("Another class named \"" + String(name) + "\" already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
if (outer_class->constant_expressions.has(name)) {
|
|
_set_error("A constant named \"" + String(name) + "\" already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ").");
|
|
return;
|
|
}
|
|
for (int i = 0; i < outer_class->variables.size(); i++) {
|
|
if (outer_class->variables[i].identifier == name) {
|
|
_set_error("A variable named \"" + String(name) + "\" already exists in the outer class scope (at line " + itos(outer_class->variables[i].line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
|
|
outer_class = outer_class->owner;
|
|
}
|
|
|
|
ClassNode *newclass = alloc_node<ClassNode>();
|
|
newclass->initializer = alloc_node<BlockNode>();
|
|
newclass->initializer->parent_class = newclass;
|
|
newclass->ready = alloc_node<BlockNode>();
|
|
newclass->ready->parent_class = newclass;
|
|
newclass->name = name;
|
|
newclass->owner = p_class;
|
|
|
|
p_class->subclasses.push_back(newclass);
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PR_EXTENDS) {
|
|
_parse_extends(newclass);
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!_enter_indent_block()) {
|
|
_set_error("Indented block expected.");
|
|
return;
|
|
}
|
|
current_class = newclass;
|
|
_parse_class(newclass);
|
|
current_class = p_class;
|
|
|
|
} break;
|
|
/* this is for functions....
|
|
case CScriptTokenizer::TK_CF_PASS: {
|
|
|
|
tokenizer->advance(1);
|
|
} break;
|
|
*/
|
|
case CScriptTokenizer::TK_PR_STATIC: {
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PR_FUNCTION) {
|
|
_set_error("Expected \"func\".");
|
|
return;
|
|
}
|
|
|
|
FALLTHROUGH;
|
|
}
|
|
case CScriptTokenizer::TK_PR_FUNCTION: {
|
|
bool _static = false;
|
|
pending_newline = -1;
|
|
|
|
if (tokenizer->get_token(-1) == CScriptTokenizer::TK_PR_STATIC) {
|
|
_static = true;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
StringName name;
|
|
|
|
if (_get_completable_identifier(COMPLETION_VIRTUAL_FUNC, name)) {
|
|
}
|
|
|
|
if (name == StringName()) {
|
|
_set_error("Expected an identifier after \"func\" (syntax: \"func <identifier>([arguments]):\").");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < p_class->functions.size(); i++) {
|
|
if (p_class->functions[i]->name == name) {
|
|
_set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->functions[i]->line) + ").");
|
|
}
|
|
}
|
|
for (int i = 0; i < p_class->static_functions.size(); i++) {
|
|
if (p_class->static_functions[i]->name == name) {
|
|
_set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->static_functions[i]->line) + ").");
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (p_class->constant_expressions.has(name)) {
|
|
_add_warning(CScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name);
|
|
}
|
|
for (int i = 0; i < p_class->variables.size(); i++) {
|
|
if (p_class->variables[i].identifier == name) {
|
|
_add_warning(CScriptWarning::FUNCTION_CONFLICTS_VARIABLE, -1, name);
|
|
}
|
|
}
|
|
for (int i = 0; i < p_class->subclasses.size(); i++) {
|
|
if (p_class->subclasses[i]->name == name) {
|
|
_add_warning(CScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name);
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
_set_error("Expected \"(\" after the identifier (syntax: \"func <identifier>([arguments]):\" ).");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
Vector<StringName> arguments;
|
|
Vector<DataType> argument_types;
|
|
Vector<Node *> default_values;
|
|
#ifdef DEBUG_ENABLED
|
|
Vector<int> arguments_usage;
|
|
#endif // DEBUG_ENABLED
|
|
|
|
int fnline = tokenizer->get_token_line();
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
//has arguments
|
|
bool defaulting = false;
|
|
while (true) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
continue;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PR_VAR) {
|
|
tokenizer->advance(); //var before the identifier is allowed
|
|
}
|
|
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
_set_error("Expected an identifier for an argument.");
|
|
return;
|
|
}
|
|
|
|
StringName argname = tokenizer->get_token_identifier();
|
|
for (int i = 0; i < arguments.size(); i++) {
|
|
if (arguments[i] == argname) {
|
|
_set_error("The argument name \"" + String(argname) + "\" is defined multiple times.");
|
|
return;
|
|
}
|
|
}
|
|
arguments.push_back(argname);
|
|
#ifdef DEBUG_ENABLED
|
|
arguments_usage.push_back(0);
|
|
#endif // DEBUG_ENABLED
|
|
|
|
tokenizer->advance();
|
|
|
|
DataType argtype;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COLON) {
|
|
if (tokenizer->get_token(1) == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
argtype.infer_type = true;
|
|
tokenizer->advance();
|
|
} else if (!_parse_type(argtype)) {
|
|
_set_error("Expected a type for an argument.");
|
|
return;
|
|
}
|
|
}
|
|
argument_types.push_back(argtype);
|
|
|
|
if (defaulting && tokenizer->get_token() != CScriptTokenizer::TK_OP_ASSIGN) {
|
|
_set_error("Default parameter expected.");
|
|
return;
|
|
}
|
|
|
|
//tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
defaulting = true;
|
|
tokenizer->advance(1);
|
|
Node *defval = _parse_and_reduce_expression(p_class, _static);
|
|
if (!defval || error_set) {
|
|
return;
|
|
}
|
|
|
|
OperatorNode *on = alloc_node<OperatorNode>();
|
|
on->op = OperatorNode::OP_ASSIGN;
|
|
on->line = fnline;
|
|
|
|
IdentifierNode *in = alloc_node<IdentifierNode>();
|
|
in->name = argname;
|
|
in->line = fnline;
|
|
|
|
on->arguments.push_back(in);
|
|
on->arguments.push_back(defval);
|
|
/* no ..
|
|
if (defval->type!=Node::TYPE_CONSTANT) {
|
|
|
|
_set_error("default argument must be constant");
|
|
}
|
|
*/
|
|
default_values.push_back(on);
|
|
}
|
|
|
|
while (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
continue;
|
|
} else if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \",\" or \")\".");
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
BlockNode *block = alloc_node<BlockNode>();
|
|
block->parent_class = p_class;
|
|
|
|
FunctionNode *function = alloc_node<FunctionNode>();
|
|
function->name = name;
|
|
function->arguments = arguments;
|
|
function->argument_types = argument_types;
|
|
function->default_values = default_values;
|
|
function->_static = _static;
|
|
function->line = fnline;
|
|
#ifdef DEBUG_ENABLED
|
|
function->arguments_usage = arguments_usage;
|
|
#endif // DEBUG_ENABLED
|
|
|
|
if (name == "_init") {
|
|
if (_static) {
|
|
_set_error("The constructor cannot be static.");
|
|
return;
|
|
}
|
|
|
|
if (p_class->extends_used) {
|
|
OperatorNode *cparent = alloc_node<OperatorNode>();
|
|
cparent->op = OperatorNode::OP_PARENT_CALL;
|
|
block->statements.push_back(cparent);
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = "_init";
|
|
cparent->arguments.push_back(id);
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PERIOD) {
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
_set_error("Expected \"(\" for parent constructor arguments.");
|
|
return;
|
|
}
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
//has arguments
|
|
parenthesis++;
|
|
while (true) {
|
|
current_function = function;
|
|
Node *arg = _parse_and_reduce_expression(p_class, _static);
|
|
if (!arg) {
|
|
return;
|
|
}
|
|
current_function = nullptr;
|
|
cparent->arguments.push_back(arg);
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
continue;
|
|
} else if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \",\" or \")\".");
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
parenthesis--;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
}
|
|
} else {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PERIOD) {
|
|
_set_error("Parent constructor call found for a class without inheritance.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
DataType return_type;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_FORWARD_ARROW) {
|
|
if (!_parse_type(return_type, true)) {
|
|
_set_error("Expected a return type for the function.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!_enter_indent_block(block)) {
|
|
_set_error(vformat("Indented block expected after declaration of \"%s\" function.", function->name));
|
|
return;
|
|
}
|
|
|
|
function->return_type = return_type;
|
|
|
|
if (_static) {
|
|
p_class->static_functions.push_back(function);
|
|
} else {
|
|
p_class->functions.push_back(function);
|
|
}
|
|
|
|
current_function = function;
|
|
function->body = block;
|
|
current_block = block;
|
|
_parse_block(block, _static);
|
|
current_block = nullptr;
|
|
|
|
//arguments
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_SIGNAL: {
|
|
_mark_line_as_safe(tokenizer->get_token_line());
|
|
tokenizer->advance();
|
|
|
|
if (!tokenizer->is_token_literal()) {
|
|
_set_error("Expected an identifier after \"signal\".");
|
|
return;
|
|
}
|
|
|
|
ClassNode::Signal sig;
|
|
sig.name = tokenizer->get_token_identifier();
|
|
sig.emissions = 0;
|
|
sig.line = tokenizer->get_token_line();
|
|
|
|
for (int i = 0; i < current_class->_signals.size(); i++) {
|
|
if (current_class->_signals[i].name == sig.name) {
|
|
_set_error("The signal \"" + sig.name + "\" already exists in this class (at line: " + itos(current_class->_signals[i].line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
tokenizer->advance();
|
|
while (true) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
continue;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
}
|
|
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
_set_error("Expected an identifier in a \"signal\" argument.");
|
|
return;
|
|
}
|
|
|
|
sig.arguments.push_back(tokenizer->get_token_identifier());
|
|
tokenizer->advance();
|
|
|
|
while (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
} else if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \",\" or \")\" after a \"signal\" parameter identifier.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
p_class->_signals.push_back(sig);
|
|
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("signal");
|
|
return;
|
|
}
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_EXPORT: {
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_OPEN) {
|
|
#define _ADVANCE_AND_CONSUME_NEWLINES \
|
|
do { \
|
|
tokenizer->advance(); \
|
|
} while (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE)
|
|
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
parenthesis++;
|
|
|
|
String hint_prefix = "";
|
|
bool is_arrayed = false;
|
|
|
|
while (tokenizer->get_token() == CScriptTokenizer::TK_BUILT_IN_TYPE &&
|
|
tokenizer->get_token_type() == Variant::ARRAY &&
|
|
tokenizer->get_token(1) == CScriptTokenizer::TK_COMMA) {
|
|
tokenizer->advance(); // Array
|
|
tokenizer->advance(); // Comma
|
|
if (is_arrayed) {
|
|
hint_prefix += itos(Variant::ARRAY) + ":";
|
|
} else {
|
|
is_arrayed = true;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_BUILT_IN_TYPE) {
|
|
Variant::Type type = tokenizer->get_token_type();
|
|
if (type == Variant::NIL) {
|
|
_set_error("Can't export null type.");
|
|
return;
|
|
}
|
|
if (type == Variant::OBJECT) {
|
|
_set_error("Can't export raw object type.");
|
|
return;
|
|
}
|
|
current_export.type = type;
|
|
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
// hint expected next!
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
switch (type) {
|
|
case Variant::INT: {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
WARN_DEPRECATED_MSG("Exporting bit flags hint requires string constants.");
|
|
break;
|
|
}
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_COMMA) {
|
|
_set_error("Expected \",\" in the bit flags hint.");
|
|
return;
|
|
}
|
|
|
|
current_export.hint = PROPERTY_HINT_FLAGS;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
bool first = true;
|
|
while (true) {
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a string constant in the named bit flags hint.");
|
|
return;
|
|
}
|
|
|
|
String c = tokenizer->get_token_constant();
|
|
if (!first) {
|
|
current_export.hint_string += ",";
|
|
} else {
|
|
first = false;
|
|
}
|
|
|
|
current_export.hint_string += c.xml_escape();
|
|
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_COMMA) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected \")\" or \",\" in the named bit flags hint.");
|
|
return;
|
|
}
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_RENDER") {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the layers 2D render hint.");
|
|
return;
|
|
}
|
|
current_export.hint = PROPERTY_HINT_LAYERS_2D_RENDER;
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_PHYSICS") {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the layers 2D physics hint.");
|
|
return;
|
|
}
|
|
current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS;
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_NAVIGATION") {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the layers 2D navigation hint.");
|
|
return;
|
|
}
|
|
current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS;
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_RENDER") {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the layers 3D render hint.");
|
|
return;
|
|
}
|
|
current_export.hint = PROPERTY_HINT_LAYERS_3D_RENDER;
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_PHYSICS") {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the layers 3D physics hint.");
|
|
return;
|
|
}
|
|
current_export.hint = PROPERTY_HINT_LAYERS_3D_PHYSICS;
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_NAVIGATION") {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the layers 3D navigation hint.");
|
|
return;
|
|
}
|
|
current_export.hint = PROPERTY_HINT_LAYERS_3D_NAVIGATION;
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) {
|
|
//enumeration
|
|
current_export.hint = PROPERTY_HINT_ENUM;
|
|
bool first = true;
|
|
while (true) {
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a string constant in the enumeration hint.");
|
|
return;
|
|
}
|
|
|
|
String c = tokenizer->get_token_constant();
|
|
if (!first) {
|
|
current_export.hint_string += ",";
|
|
} else {
|
|
first = false;
|
|
}
|
|
|
|
current_export.hint_string += c.xml_escape();
|
|
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_COMMA) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected \")\" or \",\" in the enumeration hint.");
|
|
return;
|
|
}
|
|
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
FALLTHROUGH;
|
|
}
|
|
case Variant::REAL: {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EASE") {
|
|
current_export.hint = PROPERTY_HINT_EXP_EASING;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the hint.");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// range
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EXP") {
|
|
current_export.hint = PROPERTY_HINT_EXP_RANGE;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
break;
|
|
} else if (tokenizer->get_token() != CScriptTokenizer::TK_COMMA) {
|
|
_set_error("Expected \")\" or \",\" in the exponential range hint.");
|
|
return;
|
|
}
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
} else {
|
|
current_export.hint = PROPERTY_HINT_RANGE;
|
|
}
|
|
|
|
float sign = 1.0;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_OP_SUB) {
|
|
sign = -1;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
}
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a range in the numeric hint.");
|
|
return;
|
|
}
|
|
|
|
current_export.hint_string = rtos(sign * double(tokenizer->get_token_constant()));
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
current_export.hint_string = "0," + current_export.hint_string;
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_COMMA) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected \",\" or \")\" in the numeric range hint.");
|
|
return;
|
|
}
|
|
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
sign = 1.0;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_OP_SUB) {
|
|
sign = -1;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a number as upper bound in the numeric range hint.");
|
|
return;
|
|
}
|
|
|
|
current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant()));
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_COMMA) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected \",\" or \")\" in the numeric range hint.");
|
|
return;
|
|
}
|
|
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
sign = 1.0;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_OP_SUB) {
|
|
sign = -1;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a number as step in the numeric range hint.");
|
|
return;
|
|
}
|
|
|
|
current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant()));
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
} break;
|
|
case Variant::STRING: {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) {
|
|
//enumeration
|
|
current_export.hint = PROPERTY_HINT_ENUM;
|
|
bool first = true;
|
|
while (true) {
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a string constant in the enumeration hint.");
|
|
return;
|
|
}
|
|
|
|
String c = tokenizer->get_token_constant();
|
|
if (!first) {
|
|
current_export.hint_string += ",";
|
|
} else {
|
|
first = false;
|
|
}
|
|
|
|
current_export.hint_string += c.xml_escape();
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_COMMA) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected \")\" or \",\" in the enumeration hint.");
|
|
return;
|
|
}
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "DIR") {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
current_export.hint = PROPERTY_HINT_DIR;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_IDENTIFIER || !(tokenizer->get_token_identifier() == "GLOBAL")) {
|
|
_set_error("Expected \"GLOBAL\" after comma in the directory hint.");
|
|
return;
|
|
}
|
|
if (!p_class->tool) {
|
|
_set_error("Global filesystem hints may only be used in tool scripts.");
|
|
return;
|
|
}
|
|
current_export.hint = PROPERTY_HINT_GLOBAL_DIR;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the hint.");
|
|
return;
|
|
}
|
|
} else {
|
|
_set_error("Expected \")\" or \",\" in the hint.");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FILE") {
|
|
current_export.hint = PROPERTY_HINT_FILE;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "GLOBAL") {
|
|
if (!p_class->tool) {
|
|
_set_error("Global filesystem hints may only be used in tool scripts.");
|
|
return;
|
|
}
|
|
current_export.hint = PROPERTY_HINT_GLOBAL_FILE;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
break;
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
} else {
|
|
_set_error("Expected \")\" or \",\" in the hint.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
|
|
if (current_export.hint == PROPERTY_HINT_GLOBAL_FILE) {
|
|
_set_error("Expected string constant with filter.");
|
|
} else {
|
|
_set_error("Expected \"GLOBAL\" or string constant with filter.");
|
|
}
|
|
return;
|
|
}
|
|
current_export.hint_string = tokenizer->get_token_constant();
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the hint.");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "MULTILINE") {
|
|
current_export.hint = PROPERTY_HINT_MULTILINE_TEXT;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected \")\" in the hint.");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
} break;
|
|
case Variant::COLOR: {
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_IDENTIFIER) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Color type hint expects RGB or RGBA as hints.");
|
|
return;
|
|
}
|
|
|
|
String identifier = tokenizer->get_token_identifier();
|
|
if (identifier == "RGB") {
|
|
current_export.hint = PROPERTY_HINT_COLOR_NO_ALPHA;
|
|
} else if (identifier == "RGBA") {
|
|
//none
|
|
} else {
|
|
current_export = PropertyInfo();
|
|
_set_error("Color type hint expects RGB or RGBA as hints.");
|
|
return;
|
|
}
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
} break;
|
|
default: {
|
|
current_export = PropertyInfo();
|
|
_set_error("Type \"" + Variant::get_type_name(type) + "\" can't take hints.");
|
|
return;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
parenthesis++;
|
|
Node *subexpr = _parse_and_reduce_expression(p_class, true, true);
|
|
if (!subexpr) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
parenthesis--;
|
|
|
|
if (subexpr->type != Node::TYPE_CONSTANT) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a constant expression.");
|
|
return;
|
|
}
|
|
|
|
Variant constant = static_cast<ConstantNode *>(subexpr)->value;
|
|
|
|
if (constant.get_type() == Variant::OBJECT) {
|
|
CScriptNativeClass *native_class = Object::cast_to<CScriptNativeClass>(constant);
|
|
|
|
if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
|
|
current_export.type = Variant::OBJECT;
|
|
current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
|
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
|
|
|
current_export.hint_string = native_class->get_name();
|
|
current_export.class_name = native_class->get_name();
|
|
|
|
} else {
|
|
current_export = PropertyInfo();
|
|
_set_error("The export hint isn't a resource type.");
|
|
}
|
|
} else if (constant.get_type() == Variant::DICTIONARY) {
|
|
// Enumeration
|
|
bool is_flags = false;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") {
|
|
is_flags = true;
|
|
_ADVANCE_AND_CONSUME_NEWLINES;
|
|
} else {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected \"FLAGS\" after comma.");
|
|
}
|
|
}
|
|
|
|
current_export.type = Variant::INT;
|
|
current_export.hint = is_flags ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM;
|
|
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
|
Dictionary enum_values = constant;
|
|
|
|
List<Variant> keys;
|
|
enum_values.get_key_list(&keys);
|
|
|
|
bool first = true;
|
|
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
|
|
if (enum_values[E->get()].get_type() == Variant::INT) {
|
|
if (!first) {
|
|
current_export.hint_string += ",";
|
|
} else {
|
|
first = false;
|
|
}
|
|
|
|
current_export.hint_string += E->get().operator String().capitalize().xml_escape();
|
|
if (!is_flags) {
|
|
current_export.hint_string += ":";
|
|
current_export.hint_string += enum_values[E->get()].operator String().xml_escape();
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected type for export.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected \")\" or \",\" after the export hint.");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
parenthesis--;
|
|
|
|
if (is_arrayed) {
|
|
hint_prefix += itos(current_export.type);
|
|
if (current_export.hint) {
|
|
hint_prefix += "/" + itos(current_export.hint);
|
|
}
|
|
current_export.hint_string = hint_prefix + ":" + current_export.hint_string;
|
|
current_export.hint = PROPERTY_HINT_TYPE_STRING;
|
|
current_export.type = Variant::ARRAY;
|
|
}
|
|
#undef _ADVANCE_AND_CONSUME_NEWLINES
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != CScriptTokenizer::TK_PR_ONREADY) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected \"var\", \"onready\".");
|
|
return;
|
|
}
|
|
|
|
continue;
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_ONREADY: {
|
|
//may be fallthrough from export, ignore if so
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_PR_VAR) {
|
|
_set_error("Expected \"var\".");
|
|
return;
|
|
}
|
|
|
|
continue;
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_VAR: {
|
|
// variable declaration and (eventual) initialization
|
|
|
|
ClassNode::Member member;
|
|
|
|
bool autoexport = tokenizer->get_token(-1) == CScriptTokenizer::TK_PR_EXPORT;
|
|
if (current_export.type != Variant::NIL) {
|
|
member._export = current_export;
|
|
current_export = PropertyInfo();
|
|
}
|
|
|
|
bool onready = tokenizer->get_token(-1) == CScriptTokenizer::TK_PR_ONREADY;
|
|
|
|
tokenizer->advance();
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
_set_error("Expected an identifier for the member variable name.");
|
|
return;
|
|
}
|
|
|
|
member.identifier = tokenizer->get_token_literal();
|
|
member.expression = nullptr;
|
|
member._export.name = member.identifier;
|
|
member.line = tokenizer->get_token_line();
|
|
member.usages = 0;
|
|
|
|
if (current_class->constant_expressions.has(member.identifier)) {
|
|
_set_error("A constant named \"" + String(member.identifier) + "\" already exists in this class (at line: " +
|
|
itos(current_class->constant_expressions[member.identifier].expression->line) + ").");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < current_class->variables.size(); i++) {
|
|
if (current_class->variables[i].identifier == member.identifier) {
|
|
_set_error("Variable \"" + String(member.identifier) + "\" already exists in this class (at line: " +
|
|
itos(current_class->variables[i].line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < current_class->subclasses.size(); i++) {
|
|
if (current_class->subclasses[i]->name == member.identifier) {
|
|
_set_error("A class named \"" + String(member.identifier) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
for (int i = 0; i < current_class->functions.size(); i++) {
|
|
if (current_class->functions[i]->name == member.identifier) {
|
|
_add_warning(CScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier);
|
|
break;
|
|
}
|
|
}
|
|
for (int i = 0; i < current_class->static_functions.size(); i++) {
|
|
if (current_class->static_functions[i]->name == member.identifier) {
|
|
_add_warning(CScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier);
|
|
break;
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COLON) {
|
|
if (tokenizer->get_token(1) == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
member.data_type = DataType();
|
|
member.data_type.infer_type = true;
|
|
tokenizer->advance();
|
|
} else if (!_parse_type(member.data_type)) {
|
|
_set_error("Expected a type for the class variable.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (autoexport && member.data_type.has_type) {
|
|
if (member.data_type.kind == DataType::BUILTIN) {
|
|
member._export.type = member.data_type.builtin_type;
|
|
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
|
} else if (member.data_type.kind == DataType::NATIVE) {
|
|
if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) {
|
|
member._export.type = Variant::OBJECT;
|
|
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
|
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
|
member._export.hint_string = member.data_type.native_type;
|
|
member._export.class_name = member.data_type.native_type;
|
|
} else {
|
|
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
Variant::CallError ce;
|
|
member.default_value = Variant::construct(member._export.type, nullptr, 0, ce);
|
|
#endif
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
#ifdef DEBUG_ENABLED
|
|
int line = tokenizer->get_token_line();
|
|
#endif
|
|
tokenizer->advance();
|
|
|
|
Node *subexpr = _parse_and_reduce_expression(p_class, false, autoexport || member._export.type != Variant::NIL);
|
|
if (!subexpr) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
//discourage common error
|
|
if (!onready && subexpr->type == Node::TYPE_OPERATOR) {
|
|
OperatorNode *op = static_cast<OperatorNode *>(subexpr);
|
|
if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_SELF && op->arguments[1]->type == Node::TYPE_IDENTIFIER) {
|
|
IdentifierNode *id = static_cast<IdentifierNode *>(op->arguments[1]);
|
|
if (id->name == "get_node") {
|
|
_set_error("Use \"onready var " + String(member.identifier) + " = get_node(...)\" instead.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
member.expression = subexpr;
|
|
|
|
if (autoexport && !member.data_type.has_type) {
|
|
if (subexpr->type != Node::TYPE_CONSTANT) {
|
|
_set_error("Type-less export needs a constant expression assigned to infer type.");
|
|
return;
|
|
}
|
|
|
|
ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
|
|
if (cn->value.get_type() == Variant::NIL) {
|
|
_set_error("Can't accept a null constant expression for inferring export type.");
|
|
return;
|
|
}
|
|
|
|
if (!_reduce_export_var_type(cn->value, member.line)) {
|
|
return;
|
|
}
|
|
|
|
member._export.type = cn->value.get_type();
|
|
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
|
if (cn->value.get_type() == Variant::OBJECT) {
|
|
Object *obj = cn->value;
|
|
Resource *res = Object::cast_to<Resource>(obj);
|
|
if (res == nullptr) {
|
|
_set_error("The exported constant isn't a type or resource.");
|
|
return;
|
|
}
|
|
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
|
member._export.hint_string = res->get_class();
|
|
}
|
|
}
|
|
#ifdef TOOLS_ENABLED
|
|
// Warn if the default value set is not the same as the export type, since it won't be coerced and
|
|
// may create wrong expectations.
|
|
if (subexpr->type == Node::TYPE_CONSTANT && (member._export.type != Variant::NIL || member.data_type.has_type)) {
|
|
ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
|
|
if (cn->value.get_type() != Variant::NIL) {
|
|
if (member._export.type != Variant::NIL && cn->value.get_type() != member._export.type) {
|
|
if (!Variant::can_convert(cn->value.get_type(), member._export.type)) {
|
|
_set_error("Can't convert the provided value to the export type.");
|
|
return;
|
|
} else if (!member.data_type.has_type) {
|
|
_add_warning(CScriptWarning::EXPORT_HINT_TYPE_MISTMATCH, member.line, Variant::get_type_name(cn->value.get_type()), Variant::get_type_name(member._export.type));
|
|
}
|
|
}
|
|
}
|
|
member.default_value = cn->value;
|
|
}
|
|
#endif
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = member.identifier;
|
|
id->datatype = member.data_type;
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_INIT_ASSIGN;
|
|
op->arguments.push_back(id);
|
|
op->arguments.push_back(subexpr);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
NewLineNode *nl2 = alloc_node<NewLineNode>();
|
|
nl2->line = line;
|
|
if (onready) {
|
|
p_class->ready->statements.push_back(nl2);
|
|
} else {
|
|
p_class->initializer->statements.push_back(nl2);
|
|
}
|
|
#endif
|
|
if (onready) {
|
|
p_class->ready->statements.push_back(op);
|
|
} else {
|
|
p_class->initializer->statements.push_back(op);
|
|
}
|
|
|
|
member.initial_assignment = op;
|
|
|
|
} else {
|
|
if (autoexport && !member.data_type.has_type) {
|
|
_set_error("Type-less export needs a constant expression assigned to infer type.");
|
|
return;
|
|
}
|
|
|
|
Node *expr;
|
|
|
|
if (member.data_type.has_type) {
|
|
expr = _get_default_value_for_type(member.data_type);
|
|
} else {
|
|
DataType exported_type;
|
|
exported_type.has_type = true;
|
|
exported_type.kind = DataType::BUILTIN;
|
|
exported_type.builtin_type = member._export.type;
|
|
expr = _get_default_value_for_type(exported_type);
|
|
}
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = member.identifier;
|
|
id->datatype = member.data_type;
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_INIT_ASSIGN;
|
|
op->arguments.push_back(id);
|
|
op->arguments.push_back(expr);
|
|
|
|
p_class->initializer->statements.push_back(op);
|
|
|
|
member.initial_assignment = op;
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_PR_SETGET) {
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_COMMA) {
|
|
//just comma means using only getter
|
|
if (!tokenizer->is_token_literal()) {
|
|
_set_error("Expected an identifier for the setter function after \"setget\".");
|
|
}
|
|
|
|
member.setter = tokenizer->get_token_literal();
|
|
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
//there is a getter
|
|
tokenizer->advance();
|
|
|
|
if (!tokenizer->is_token_literal()) {
|
|
_set_error("Expected an identifier for the getter function after \",\".");
|
|
}
|
|
|
|
member.getter = tokenizer->get_token_literal();
|
|
tokenizer->advance();
|
|
}
|
|
}
|
|
|
|
p_class->variables.push_back(member);
|
|
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("var");
|
|
return;
|
|
}
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_CONST: {
|
|
// constant declaration and initialization
|
|
|
|
ClassNode::Constant constant;
|
|
|
|
tokenizer->advance();
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
_set_error("Expected an identifier for the constant.");
|
|
return;
|
|
}
|
|
|
|
StringName const_id = tokenizer->get_token_literal();
|
|
int line = tokenizer->get_token_line();
|
|
|
|
if (current_class->constant_expressions.has(const_id)) {
|
|
_set_error("Constant \"" + String(const_id) + "\" already exists in this class (at line " +
|
|
itos(current_class->constant_expressions[const_id].expression->line) + ").");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < current_class->variables.size(); i++) {
|
|
if (current_class->variables[i].identifier == const_id) {
|
|
_set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " +
|
|
itos(current_class->variables[i].line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < current_class->subclasses.size(); i++) {
|
|
if (current_class->subclasses[i]->name == const_id) {
|
|
_set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COLON) {
|
|
if (tokenizer->get_token(1) == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
constant.type = DataType();
|
|
constant.type.infer_type = true;
|
|
tokenizer->advance();
|
|
} else if (!_parse_type(constant.type)) {
|
|
_set_error("Expected a type for the class constant.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_OP_ASSIGN) {
|
|
_set_error("Constants must be assigned immediately.");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
Node *subexpr = _parse_and_reduce_expression(p_class, true, true);
|
|
if (!subexpr) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (subexpr->type != Node::TYPE_CONSTANT) {
|
|
_set_error("Expected a constant expression.", line);
|
|
return;
|
|
}
|
|
subexpr->line = line;
|
|
constant.expression = subexpr;
|
|
|
|
p_class->constant_expressions.insert(const_id, constant);
|
|
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("const");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
case CScriptTokenizer::TK_PR_ENUM: {
|
|
//multiple constant declarations..
|
|
|
|
int last_assign = -1; // Incremented by 1 right before the assignment.
|
|
String enum_name;
|
|
Dictionary enum_dict;
|
|
int enum_start_line = tokenizer->get_token_line();
|
|
|
|
tokenizer->advance();
|
|
if (tokenizer->is_token_literal(0, true)) {
|
|
enum_name = tokenizer->get_token_literal();
|
|
|
|
if (current_class->constant_expressions.has(enum_name)) {
|
|
_set_error("A constant named \"" + String(enum_name) + "\" already exists in this class (at line " +
|
|
itos(current_class->constant_expressions[enum_name].expression->line) + ").");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < current_class->variables.size(); i++) {
|
|
if (current_class->variables[i].identifier == enum_name) {
|
|
_set_error("A variable named \"" + String(enum_name) + "\" already exists in this class (at line " +
|
|
itos(current_class->variables[i].line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < current_class->subclasses.size(); i++) {
|
|
if (current_class->subclasses[i]->name == enum_name) {
|
|
_set_error("A class named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
}
|
|
if (tokenizer->get_token() != CScriptTokenizer::TK_CURLY_BRACKET_OPEN) {
|
|
_set_error("Expected \"{\" in the enum declaration.");
|
|
return;
|
|
}
|
|
tokenizer->advance();
|
|
|
|
while (true) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance(); // Ignore newlines
|
|
} else if (tokenizer->get_token() == CScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break; // End of enum
|
|
} else if (!tokenizer->is_token_literal(0, true)) {
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_EOF) {
|
|
_set_error("Unexpected end of file.");
|
|
} else {
|
|
_set_error(String("Unexpected ") + CScriptTokenizer::get_token_name(tokenizer->get_token()) + ", expected an identifier.");
|
|
}
|
|
|
|
return;
|
|
} else { // tokenizer->is_token_literal(0, true)
|
|
StringName const_id = tokenizer->get_token_literal();
|
|
|
|
tokenizer->advance();
|
|
|
|
ConstantNode *enum_value_expr;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_OP_ASSIGN) {
|
|
tokenizer->advance();
|
|
|
|
Node *subexpr = _parse_and_reduce_expression(p_class, true, true);
|
|
if (!subexpr) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (subexpr->type != Node::TYPE_CONSTANT) {
|
|
_set_error("Expected a constant expression.");
|
|
return;
|
|
}
|
|
|
|
enum_value_expr = static_cast<ConstantNode *>(subexpr);
|
|
|
|
if (enum_value_expr->value.get_type() != Variant::INT) {
|
|
_set_error("Expected an integer value for \"enum\".");
|
|
return;
|
|
}
|
|
|
|
last_assign = enum_value_expr->value;
|
|
|
|
} else {
|
|
last_assign = last_assign + 1;
|
|
enum_value_expr = alloc_node<ConstantNode>();
|
|
enum_value_expr->value = last_assign;
|
|
enum_value_expr->datatype = _type_from_variant(enum_value_expr->value);
|
|
}
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
} else if (tokenizer->is_token_literal(0, true)) {
|
|
_set_error("Unexpected identifier.");
|
|
return;
|
|
}
|
|
|
|
if (enum_name != "") {
|
|
enum_dict[const_id] = enum_value_expr->value;
|
|
} else {
|
|
if (current_class->constant_expressions.has(const_id)) {
|
|
_set_error("A constant named \"" + String(const_id) + "\" already exists in this class (at line " +
|
|
itos(current_class->constant_expressions[const_id].expression->line) + ").");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < current_class->variables.size(); i++) {
|
|
if (current_class->variables[i].identifier == const_id) {
|
|
_set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " +
|
|
itos(current_class->variables[i].line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < current_class->subclasses.size(); i++) {
|
|
if (current_class->subclasses[i]->name == const_id) {
|
|
_set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
|
|
return;
|
|
}
|
|
}
|
|
|
|
ClassNode::Constant constant;
|
|
constant.type.has_type = true;
|
|
constant.type.kind = DataType::BUILTIN;
|
|
constant.type.builtin_type = Variant::INT;
|
|
constant.expression = enum_value_expr;
|
|
p_class->constant_expressions.insert(const_id, constant);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (enum_name != "") {
|
|
ClassNode::Constant enum_constant;
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = enum_dict;
|
|
cn->datatype = _type_from_variant(cn->value);
|
|
cn->line = enum_start_line;
|
|
|
|
enum_constant.expression = cn;
|
|
enum_constant.type = cn->datatype;
|
|
p_class->constant_expressions.insert(enum_name, enum_constant);
|
|
}
|
|
|
|
if (!_end_statement()) {
|
|
_set_end_statement_error("enum");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
|
|
case CScriptTokenizer::TK_CONSTANT: {
|
|
if (tokenizer->get_token_constant().get_type() == Variant::STRING) {
|
|
tokenizer->advance();
|
|
// Ignore
|
|
} else {
|
|
_set_error(String() + "Unexpected constant of type: " + Variant::get_type_name(tokenizer->get_token_constant().get_type()));
|
|
return;
|
|
}
|
|
} break;
|
|
|
|
case CScriptTokenizer::TK_CF_PASS: {
|
|
tokenizer->advance();
|
|
} break;
|
|
|
|
default: {
|
|
if (token == CScriptTokenizer::TK_IDENTIFIER) {
|
|
completion_type = COMPLETION_IDENTIFIER;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_block = current_block;
|
|
completion_ident_is_call = false;
|
|
completion_found = true;
|
|
}
|
|
|
|
_set_error(String() + "Unexpected token: " + tokenizer->get_token_name(tokenizer->get_token()) + ":" + tokenizer->get_token_identifier());
|
|
return;
|
|
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive) {
|
|
if (p_class->base_type.has_type) {
|
|
// Already determined
|
|
} else if (p_class->extends_used) {
|
|
//do inheritance
|
|
String path = p_class->extends_file;
|
|
|
|
Ref<CScript> script;
|
|
StringName native;
|
|
ClassNode *base_class = nullptr;
|
|
|
|
if (path != "") {
|
|
//path (and optionally subclasses)
|
|
|
|
if (path.is_rel_path()) {
|
|
String base = base_path;
|
|
|
|
if (base == "" || base.is_rel_path()) {
|
|
_set_error("Couldn't resolve relative path for the parent class: " + path, p_class->line);
|
|
return;
|
|
}
|
|
path = base.plus_file(path).simplify_path();
|
|
}
|
|
script = ResourceLoader::load(path);
|
|
if (script.is_null()) {
|
|
_set_error("Couldn't load the base class: " + path, p_class->line);
|
|
return;
|
|
}
|
|
if (!script->is_valid()) {
|
|
_set_error("Script isn't fully loaded (cyclic preload?): " + path, p_class->line);
|
|
return;
|
|
}
|
|
|
|
if (p_class->extends_class.size()) {
|
|
for (int i = 0; i < p_class->extends_class.size(); i++) {
|
|
String sub = p_class->extends_class[i];
|
|
if (script->get_subclasses().has(sub)) {
|
|
Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing
|
|
script = subclass;
|
|
} else {
|
|
_set_error("Couldn't find the subclass: " + sub, p_class->line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if (p_class->extends_class.size() == 0) {
|
|
_set_error("Parser bug: undecidable inheritance.", p_class->line);
|
|
ERR_FAIL();
|
|
}
|
|
//look around for the subclasses
|
|
|
|
int extend_iter = 1;
|
|
String base = p_class->extends_class[0];
|
|
ClassNode *p = p_class->owner;
|
|
Ref<CScript> base_script;
|
|
|
|
if (ScriptServer::is_global_class(base)) {
|
|
base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base));
|
|
if (!base_script.is_valid()) {
|
|
_set_error("The class \"" + base + "\" couldn't be fully loaded (script error or cyclic dependency).", p_class->line);
|
|
return;
|
|
}
|
|
p = nullptr;
|
|
} else {
|
|
List<PropertyInfo> props;
|
|
ProjectSettings::get_singleton()->get_property_list(&props);
|
|
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
|
|
String s = E->get().name;
|
|
if (!s.begins_with("autoload/")) {
|
|
continue;
|
|
}
|
|
String name = s.get_slice("/", 1);
|
|
if (name == base) {
|
|
String singleton_path = ProjectSettings::get_singleton()->get(s);
|
|
if (singleton_path.begins_with("*")) {
|
|
singleton_path = singleton_path.right(1);
|
|
}
|
|
if (!singleton_path.begins_with("res://")) {
|
|
singleton_path = "res://" + singleton_path;
|
|
}
|
|
base_script = ResourceLoader::load(singleton_path);
|
|
if (!base_script.is_valid()) {
|
|
_set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line);
|
|
return;
|
|
}
|
|
p = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
while (p) {
|
|
bool found = false;
|
|
|
|
for (int i = 0; i < p->subclasses.size(); i++) {
|
|
if (p->subclasses[i]->name == base) {
|
|
ClassNode *test = p->subclasses[i];
|
|
while (test) {
|
|
if (test == p_class) {
|
|
_set_error("Cyclic inheritance.", test->line);
|
|
return;
|
|
}
|
|
if (test->base_type.kind == DataType::CLASS) {
|
|
test = test->base_type.class_type;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
found = true;
|
|
if (extend_iter < p_class->extends_class.size()) {
|
|
// Keep looking at current classes if possible
|
|
base = p_class->extends_class[extend_iter++];
|
|
p = p->subclasses[i];
|
|
} else {
|
|
base_class = p->subclasses[i];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (base_class) {
|
|
break;
|
|
}
|
|
if (found) {
|
|
continue;
|
|
}
|
|
|
|
if (p->constant_expressions.has(base)) {
|
|
if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) {
|
|
_set_error("Couldn't resolve the constant \"" + base + "\".", p_class->line);
|
|
return;
|
|
}
|
|
const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression);
|
|
base_script = cn->value;
|
|
if (base_script.is_null()) {
|
|
_set_error("Constant isn't a class: " + base, p_class->line);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
p = p->owner;
|
|
}
|
|
|
|
if (base_script.is_valid()) {
|
|
String ident = base;
|
|
Ref<CScript> find_subclass = base_script;
|
|
|
|
for (int i = extend_iter; i < p_class->extends_class.size(); i++) {
|
|
String subclass = p_class->extends_class[i];
|
|
|
|
ident += ("." + subclass);
|
|
|
|
if (find_subclass->get_subclasses().has(subclass)) {
|
|
find_subclass = find_subclass->get_subclasses()[subclass];
|
|
} else if (find_subclass->get_constants().has(subclass)) {
|
|
Ref<CScript> new_base_class = find_subclass->get_constants()[subclass];
|
|
if (new_base_class.is_null()) {
|
|
_set_error("Constant isn't a class: " + ident, p_class->line);
|
|
return;
|
|
}
|
|
find_subclass = new_base_class;
|
|
} else {
|
|
_set_error("Couldn't find the subclass: " + ident, p_class->line);
|
|
return;
|
|
}
|
|
}
|
|
|
|
script = find_subclass;
|
|
|
|
} else if (!base_class) {
|
|
if (p_class->extends_class.size() > 1) {
|
|
_set_error("Invalid inheritance (unknown class + subclasses).", p_class->line);
|
|
return;
|
|
}
|
|
//if not found, try engine classes
|
|
if (!CScriptLanguage::get_singleton()->get_global_map().has(base)) {
|
|
_set_error("Unknown class: \"" + base + "\"", p_class->line);
|
|
return;
|
|
}
|
|
|
|
native = base;
|
|
}
|
|
}
|
|
|
|
if (base_class) {
|
|
p_class->base_type.has_type = true;
|
|
p_class->base_type.kind = DataType::CLASS;
|
|
p_class->base_type.class_type = base_class;
|
|
} else if (script.is_valid()) {
|
|
p_class->base_type.has_type = true;
|
|
p_class->base_type.kind = DataType::CSCRIPT;
|
|
p_class->base_type.script_type = script;
|
|
p_class->base_type.native_type = script->get_instance_base_type();
|
|
} else if (native != StringName()) {
|
|
p_class->base_type.has_type = true;
|
|
p_class->base_type.kind = DataType::NATIVE;
|
|
p_class->base_type.native_type = native;
|
|
} else {
|
|
_set_error("Couldn't determine inheritance.", p_class->line);
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
// without extends, implicitly extend Reference
|
|
p_class->base_type.has_type = true;
|
|
p_class->base_type.kind = DataType::NATIVE;
|
|
p_class->base_type.native_type = "Reference";
|
|
}
|
|
|
|
if (p_recursive) {
|
|
// Recursively determine subclasses
|
|
for (int i = 0; i < p_class->subclasses.size(); i++) {
|
|
_determine_inheritance(p_class->subclasses[i], p_recursive);
|
|
}
|
|
}
|
|
}
|
|
|
|
String CScriptParser::DataType::to_string() const {
|
|
if (!has_type) {
|
|
return "var";
|
|
}
|
|
switch (kind) {
|
|
case BUILTIN: {
|
|
if (builtin_type == Variant::NIL) {
|
|
return "null";
|
|
}
|
|
return Variant::get_type_name(builtin_type);
|
|
} break;
|
|
case NATIVE: {
|
|
if (is_meta_type) {
|
|
return "CScriptNativeClass";
|
|
}
|
|
return native_type.operator String();
|
|
} break;
|
|
|
|
case CSCRIPT: {
|
|
Ref<CScript> gds = script_type;
|
|
const String &gds_class = gds->get_script_class_name();
|
|
if (!gds_class.empty()) {
|
|
return gds_class;
|
|
}
|
|
FALLTHROUGH;
|
|
}
|
|
case SCRIPT: {
|
|
if (is_meta_type) {
|
|
return script_type->get_class_name().operator String();
|
|
}
|
|
String name = script_type->get_name();
|
|
if (name != String()) {
|
|
return name;
|
|
}
|
|
name = script_type->get_path().get_file();
|
|
if (name != String()) {
|
|
return name;
|
|
}
|
|
return native_type.operator String();
|
|
} break;
|
|
case CLASS: {
|
|
ERR_FAIL_COND_V(!class_type, String());
|
|
if (is_meta_type) {
|
|
return "CScript";
|
|
}
|
|
if (class_type->name == StringName()) {
|
|
return "self";
|
|
}
|
|
return class_type->name.operator String();
|
|
} break;
|
|
case UNRESOLVED: {
|
|
} break;
|
|
}
|
|
|
|
return "Unresolved";
|
|
}
|
|
|
|
bool CScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) {
|
|
tokenizer->advance();
|
|
r_type.has_type = true;
|
|
|
|
bool finished = false;
|
|
bool can_index = false;
|
|
String full_name;
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CURSOR) {
|
|
completion_cursor = StringName();
|
|
completion_type = COMPLETION_TYPE_HINT;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_argument = 0;
|
|
completion_block = current_block;
|
|
completion_found = true;
|
|
completion_ident_is_call = p_can_be_void;
|
|
tokenizer->advance();
|
|
}
|
|
|
|
switch (tokenizer->get_token()) {
|
|
case CScriptTokenizer::TK_PR_VOID: {
|
|
if (!p_can_be_void) {
|
|
return false;
|
|
}
|
|
r_type.kind = DataType::BUILTIN;
|
|
r_type.builtin_type = Variant::NIL;
|
|
} break;
|
|
case CScriptTokenizer::TK_BUILT_IN_TYPE: {
|
|
r_type.builtin_type = tokenizer->get_token_type();
|
|
if (tokenizer->get_token_type() == Variant::OBJECT) {
|
|
r_type.kind = DataType::NATIVE;
|
|
r_type.native_type = "Object";
|
|
} else {
|
|
r_type.kind = DataType::BUILTIN;
|
|
}
|
|
} break;
|
|
case CScriptTokenizer::TK_IDENTIFIER: {
|
|
r_type.native_type = tokenizer->get_token_identifier();
|
|
if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) {
|
|
r_type.kind = DataType::NATIVE;
|
|
} else {
|
|
r_type.kind = DataType::UNRESOLVED;
|
|
can_index = true;
|
|
full_name = r_type.native_type;
|
|
}
|
|
} break;
|
|
default: {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_CURSOR) {
|
|
completion_cursor = r_type.native_type;
|
|
completion_type = COMPLETION_TYPE_HINT;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_argument = 0;
|
|
completion_block = current_block;
|
|
completion_found = true;
|
|
completion_ident_is_call = p_can_be_void;
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (can_index) {
|
|
while (!finished) {
|
|
switch (tokenizer->get_token()) {
|
|
case CScriptTokenizer::TK_PERIOD: {
|
|
if (!can_index) {
|
|
_set_error("Unexpected \".\".");
|
|
return false;
|
|
}
|
|
can_index = false;
|
|
tokenizer->advance();
|
|
} break;
|
|
case CScriptTokenizer::TK_IDENTIFIER: {
|
|
if (can_index) {
|
|
_set_error("Unexpected identifier.");
|
|
return false;
|
|
}
|
|
|
|
StringName id;
|
|
bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id);
|
|
if (id == StringName()) {
|
|
id = "@temp";
|
|
}
|
|
|
|
full_name += "." + id.operator String();
|
|
can_index = true;
|
|
if (has_completion) {
|
|
completion_cursor = full_name;
|
|
}
|
|
} break;
|
|
default: {
|
|
finished = true;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token(-1) == CScriptTokenizer::TK_PERIOD) {
|
|
_set_error("Expected a subclass identifier.");
|
|
return false;
|
|
}
|
|
|
|
r_type.native_type = full_name;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CScriptParser::DataType CScriptParser::_resolve_type(const DataType &p_source, int p_line) {
|
|
if (!p_source.has_type) {
|
|
return p_source;
|
|
}
|
|
if (p_source.kind != DataType::UNRESOLVED) {
|
|
return p_source;
|
|
}
|
|
|
|
Vector<String> full_name = p_source.native_type.operator String().split(".", false);
|
|
int name_part = 0;
|
|
|
|
DataType result;
|
|
result.has_type = true;
|
|
|
|
while (name_part < full_name.size()) {
|
|
bool found = false;
|
|
StringName id = full_name[name_part];
|
|
DataType base_type = result;
|
|
|
|
ClassNode *p = nullptr;
|
|
if (name_part == 0) {
|
|
if (ScriptServer::is_global_class(id)) {
|
|
String script_path = ScriptServer::get_global_class_path(id);
|
|
if (script_path == self_path) {
|
|
result.kind = DataType::CLASS;
|
|
result.class_type = static_cast<ClassNode *>(head);
|
|
} else {
|
|
Ref<Script> script = ResourceLoader::load(script_path);
|
|
Ref<CScript> gds = script;
|
|
if (gds.is_valid()) {
|
|
if (!gds->is_valid()) {
|
|
_set_error("The class \"" + id + "\" couldn't be fully loaded (script error or cyclic dependency).", p_line);
|
|
return DataType();
|
|
}
|
|
result.kind = DataType::CSCRIPT;
|
|
result.script_type = gds;
|
|
} else if (script.is_valid()) {
|
|
result.kind = DataType::SCRIPT;
|
|
result.script_type = script;
|
|
} else {
|
|
_set_error("The class \"" + id + "\" was found in global scope, but its script couldn't be loaded.", p_line);
|
|
return DataType();
|
|
}
|
|
}
|
|
name_part++;
|
|
continue;
|
|
}
|
|
List<PropertyInfo> props;
|
|
ProjectSettings::get_singleton()->get_property_list(&props);
|
|
String singleton_path;
|
|
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
|
|
String s = E->get().name;
|
|
if (!s.begins_with("autoload/")) {
|
|
continue;
|
|
}
|
|
String name = s.get_slice("/", 1);
|
|
if (name == id) {
|
|
singleton_path = ProjectSettings::get_singleton()->get(s);
|
|
if (singleton_path.begins_with("*")) {
|
|
singleton_path = singleton_path.right(1);
|
|
}
|
|
if (!singleton_path.begins_with("res://")) {
|
|
singleton_path = "res://" + singleton_path;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!singleton_path.empty()) {
|
|
Ref<Script> script = ResourceLoader::load(singleton_path);
|
|
Ref<CScript> gds = script;
|
|
if (gds.is_valid()) {
|
|
if (!gds->is_valid()) {
|
|
_set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line);
|
|
return DataType();
|
|
}
|
|
result.kind = DataType::CSCRIPT;
|
|
result.script_type = gds;
|
|
} else if (script.is_valid()) {
|
|
result.kind = DataType::SCRIPT;
|
|
result.script_type = script;
|
|
} else {
|
|
_set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line);
|
|
return DataType();
|
|
}
|
|
name_part++;
|
|
continue;
|
|
}
|
|
|
|
p = current_class;
|
|
} else if (base_type.kind == DataType::CLASS) {
|
|
p = base_type.class_type;
|
|
}
|
|
while (p) {
|
|
if (p->constant_expressions.has(id)) {
|
|
if (p->constant_expressions[id].expression->type != Node::TYPE_CONSTANT) {
|
|
_set_error("Parser bug: unresolved constant.", p_line);
|
|
ERR_FAIL_V(result);
|
|
}
|
|
const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[id].expression);
|
|
Ref<CScript> gds = cn->value;
|
|
if (gds.is_valid()) {
|
|
result.kind = DataType::CSCRIPT;
|
|
result.script_type = gds;
|
|
found = true;
|
|
} else {
|
|
Ref<Script> scr = cn->value;
|
|
if (scr.is_valid()) {
|
|
result.kind = DataType::SCRIPT;
|
|
result.script_type = scr;
|
|
found = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Inner classes
|
|
ClassNode *outer_class = p;
|
|
while (outer_class) {
|
|
if (outer_class->name == id) {
|
|
found = true;
|
|
result.kind = DataType::CLASS;
|
|
result.class_type = outer_class;
|
|
break;
|
|
}
|
|
for (int i = 0; i < outer_class->subclasses.size(); i++) {
|
|
if (outer_class->subclasses[i] == p) {
|
|
continue;
|
|
}
|
|
if (outer_class->subclasses[i]->name == id) {
|
|
found = true;
|
|
result.kind = DataType::CLASS;
|
|
result.class_type = outer_class->subclasses[i];
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
break;
|
|
}
|
|
outer_class = outer_class->owner;
|
|
}
|
|
|
|
if (!found && p->base_type.kind == DataType::CLASS) {
|
|
p = p->base_type.class_type;
|
|
} else {
|
|
base_type = p->base_type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Still look for class constants in parent scripts
|
|
if (!found && (base_type.kind == DataType::CSCRIPT || base_type.kind == DataType::SCRIPT)) {
|
|
Ref<Script> scr = base_type.script_type;
|
|
ERR_FAIL_COND_V(scr.is_null(), result);
|
|
while (scr.is_valid()) {
|
|
Map<StringName, Variant> constants;
|
|
scr->get_constants(&constants);
|
|
|
|
if (constants.has(id)) {
|
|
Ref<CScript> gds = constants[id];
|
|
|
|
if (gds.is_valid()) {
|
|
result.kind = DataType::CSCRIPT;
|
|
result.script_type = gds;
|
|
found = true;
|
|
} else {
|
|
Ref<Script> scr2 = constants[id];
|
|
if (scr2.is_valid()) {
|
|
result.kind = DataType::SCRIPT;
|
|
result.script_type = scr2;
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
if (found) {
|
|
break;
|
|
} else {
|
|
scr = scr->get_base_script();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found && !for_completion) {
|
|
String base;
|
|
if (name_part == 0) {
|
|
base = "self";
|
|
} else {
|
|
base = result.to_string();
|
|
}
|
|
_set_error("The identifier \"" + String(id) +
|
|
"\" isn't a valid type (not a script or class), or couldn't be found on base \"" + base + "\".",
|
|
p_line);
|
|
return DataType();
|
|
}
|
|
|
|
name_part++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
CScriptParser::DataType CScriptParser::_type_from_variant(const Variant &p_value) const {
|
|
DataType result;
|
|
result.has_type = true;
|
|
result.is_constant = true;
|
|
result.kind = DataType::BUILTIN;
|
|
result.builtin_type = p_value.get_type();
|
|
|
|
if (result.builtin_type == Variant::OBJECT) {
|
|
Object *obj = p_value.operator Object *();
|
|
if (!obj) {
|
|
return DataType();
|
|
}
|
|
result.native_type = obj->get_class_name();
|
|
Ref<Script> scr = p_value;
|
|
if (scr.is_valid()) {
|
|
result.is_meta_type = true;
|
|
} else {
|
|
result.is_meta_type = false;
|
|
scr = obj->get_script();
|
|
}
|
|
if (scr.is_valid()) {
|
|
result.script_type = scr;
|
|
Ref<CScript> gds = scr;
|
|
if (gds.is_valid()) {
|
|
result.kind = DataType::CSCRIPT;
|
|
} else {
|
|
result.kind = DataType::SCRIPT;
|
|
}
|
|
result.native_type = scr->get_instance_base_type();
|
|
} else {
|
|
result.kind = DataType::NATIVE;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
CScriptParser::DataType CScriptParser::_type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant) const {
|
|
DataType ret;
|
|
if (p_property.type == Variant::NIL && (p_nil_is_variant || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) {
|
|
// Variant
|
|
return ret;
|
|
}
|
|
ret.has_type = true;
|
|
ret.builtin_type = p_property.type;
|
|
if (p_property.type == Variant::OBJECT) {
|
|
ret.kind = DataType::NATIVE;
|
|
ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
|
|
} else {
|
|
ret.kind = DataType::BUILTIN;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
CScriptParser::DataType CScriptParser::_type_from_gdtype(const CScriptDataType &p_gdtype) const {
|
|
DataType result;
|
|
if (!p_gdtype.has_type) {
|
|
return result;
|
|
}
|
|
|
|
result.has_type = true;
|
|
result.builtin_type = p_gdtype.builtin_type;
|
|
result.native_type = p_gdtype.native_type;
|
|
result.script_type = Ref<Script>(p_gdtype.script_type);
|
|
|
|
switch (p_gdtype.kind) {
|
|
case CScriptDataType::UNINITIALIZED: {
|
|
ERR_PRINT("Uninitialized datatype. Please report a bug.");
|
|
} break;
|
|
case CScriptDataType::BUILTIN: {
|
|
result.kind = DataType::BUILTIN;
|
|
} break;
|
|
case CScriptDataType::NATIVE: {
|
|
result.kind = DataType::NATIVE;
|
|
} break;
|
|
case CScriptDataType::CSCRIPT: {
|
|
result.kind = DataType::CSCRIPT;
|
|
} break;
|
|
case CScriptDataType::SCRIPT: {
|
|
result.kind = DataType::SCRIPT;
|
|
} break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CScriptParser::DataType CScriptParser::_get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const {
|
|
if (!p_a.has_type || !p_b.has_type) {
|
|
r_valid = true;
|
|
return DataType();
|
|
}
|
|
|
|
Variant::Type a_type = p_a.kind == DataType::BUILTIN ? p_a.builtin_type : Variant::OBJECT;
|
|
Variant::Type b_type = p_b.kind == DataType::BUILTIN ? p_b.builtin_type : Variant::OBJECT;
|
|
|
|
Variant a;
|
|
REF a_ref;
|
|
if (a_type == Variant::OBJECT) {
|
|
a_ref.instance();
|
|
a = a_ref;
|
|
} else {
|
|
Variant::CallError err;
|
|
a = Variant::construct(a_type, nullptr, 0, err);
|
|
if (err.error != Variant::CallError::CALL_OK) {
|
|
r_valid = false;
|
|
return DataType();
|
|
}
|
|
}
|
|
Variant b;
|
|
REF b_ref;
|
|
if (b_type == Variant::OBJECT) {
|
|
b_ref.instance();
|
|
b = b_ref;
|
|
} else {
|
|
Variant::CallError err;
|
|
b = Variant::construct(b_type, nullptr, 0, err);
|
|
if (err.error != Variant::CallError::CALL_OK) {
|
|
r_valid = false;
|
|
return DataType();
|
|
}
|
|
}
|
|
|
|
// Avoid division by zero
|
|
if (a_type == Variant::INT || a_type == Variant::REAL) {
|
|
Variant::evaluate(Variant::OP_ADD, a, 1, a, r_valid);
|
|
}
|
|
if (b_type == Variant::INT || b_type == Variant::REAL) {
|
|
Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid);
|
|
}
|
|
if (a_type == Variant::STRING && b_type != Variant::ARRAY) {
|
|
a = "%s"; // Work around for formatting operator (%)
|
|
}
|
|
|
|
Variant ret;
|
|
Variant::evaluate(p_op, a, b, ret, r_valid);
|
|
|
|
if (r_valid) {
|
|
return _type_from_variant(ret);
|
|
}
|
|
|
|
return DataType();
|
|
}
|
|
|
|
Variant::Operator CScriptParser::_get_variant_operation(const OperatorNode::Operator &p_op) const {
|
|
switch (p_op) {
|
|
case OperatorNode::OP_NEG: {
|
|
return Variant::OP_NEGATE;
|
|
} break;
|
|
case OperatorNode::OP_POS: {
|
|
return Variant::OP_POSITIVE;
|
|
} break;
|
|
case OperatorNode::OP_NOT: {
|
|
return Variant::OP_NOT;
|
|
} break;
|
|
case OperatorNode::OP_BIT_INVERT: {
|
|
return Variant::OP_BIT_NEGATE;
|
|
} break;
|
|
case OperatorNode::OP_IN: {
|
|
return Variant::OP_IN;
|
|
} break;
|
|
case OperatorNode::OP_EQUAL: {
|
|
return Variant::OP_EQUAL;
|
|
} break;
|
|
case OperatorNode::OP_NOT_EQUAL: {
|
|
return Variant::OP_NOT_EQUAL;
|
|
} break;
|
|
case OperatorNode::OP_LESS: {
|
|
return Variant::OP_LESS;
|
|
} break;
|
|
case OperatorNode::OP_LESS_EQUAL: {
|
|
return Variant::OP_LESS_EQUAL;
|
|
} break;
|
|
case OperatorNode::OP_GREATER: {
|
|
return Variant::OP_GREATER;
|
|
} break;
|
|
case OperatorNode::OP_GREATER_EQUAL: {
|
|
return Variant::OP_GREATER_EQUAL;
|
|
} break;
|
|
case OperatorNode::OP_AND: {
|
|
return Variant::OP_AND;
|
|
} break;
|
|
case OperatorNode::OP_OR: {
|
|
return Variant::OP_OR;
|
|
} break;
|
|
case OperatorNode::OP_ASSIGN_ADD:
|
|
case OperatorNode::OP_ADD: {
|
|
return Variant::OP_ADD;
|
|
} break;
|
|
case OperatorNode::OP_ASSIGN_SUB:
|
|
case OperatorNode::OP_SUB: {
|
|
return Variant::OP_SUBTRACT;
|
|
} break;
|
|
case OperatorNode::OP_ASSIGN_MUL:
|
|
case OperatorNode::OP_MUL: {
|
|
return Variant::OP_MULTIPLY;
|
|
} break;
|
|
case OperatorNode::OP_ASSIGN_DIV:
|
|
case OperatorNode::OP_DIV: {
|
|
return Variant::OP_DIVIDE;
|
|
} break;
|
|
case OperatorNode::OP_ASSIGN_MOD:
|
|
case OperatorNode::OP_MOD: {
|
|
return Variant::OP_MODULE;
|
|
} break;
|
|
case OperatorNode::OP_ASSIGN_BIT_AND:
|
|
case OperatorNode::OP_BIT_AND: {
|
|
return Variant::OP_BIT_AND;
|
|
} break;
|
|
case OperatorNode::OP_ASSIGN_BIT_OR:
|
|
case OperatorNode::OP_BIT_OR: {
|
|
return Variant::OP_BIT_OR;
|
|
} break;
|
|
case OperatorNode::OP_ASSIGN_BIT_XOR:
|
|
case OperatorNode::OP_BIT_XOR: {
|
|
return Variant::OP_BIT_XOR;
|
|
} break;
|
|
case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
|
|
case OperatorNode::OP_SHIFT_LEFT: {
|
|
return Variant::OP_SHIFT_LEFT;
|
|
}
|
|
case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
|
|
case OperatorNode::OP_SHIFT_RIGHT: {
|
|
return Variant::OP_SHIFT_RIGHT;
|
|
}
|
|
default: {
|
|
return Variant::OP_MAX;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
bool CScriptParser::_is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion) const {
|
|
// Ignore for completion
|
|
if (!check_types || for_completion) {
|
|
return true;
|
|
}
|
|
// Can't test if not all have type
|
|
if (!p_container.has_type || !p_expression.has_type) {
|
|
return true;
|
|
}
|
|
|
|
// Should never get here unresolved
|
|
ERR_FAIL_COND_V(p_container.kind == DataType::UNRESOLVED, false);
|
|
ERR_FAIL_COND_V(p_expression.kind == DataType::UNRESOLVED, false);
|
|
|
|
if (p_container.kind == DataType::BUILTIN && p_expression.kind == DataType::BUILTIN) {
|
|
bool valid = p_container.builtin_type == p_expression.builtin_type;
|
|
if (p_allow_implicit_conversion) {
|
|
valid = valid || Variant::can_convert_strict(p_expression.builtin_type, p_container.builtin_type);
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
if (p_container.kind == DataType::BUILTIN && p_container.builtin_type == Variant::OBJECT) {
|
|
// Object built-in is a special case, it's compatible with any object and with null
|
|
if (p_expression.kind == DataType::BUILTIN) {
|
|
return p_expression.builtin_type == Variant::NIL;
|
|
}
|
|
// If it's not a built-in, must be an object
|
|
return true;
|
|
}
|
|
|
|
if (p_container.kind == DataType::BUILTIN || (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type != Variant::NIL)) {
|
|
// Can't mix built-ins with objects
|
|
return false;
|
|
}
|
|
|
|
// From now on everything is objects, check polymorphism
|
|
// The container must be the same class or a superclass of the expression
|
|
|
|
if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) {
|
|
// Null can be assigned to object types
|
|
return true;
|
|
}
|
|
|
|
StringName expr_native;
|
|
Ref<Script> expr_script;
|
|
ClassNode *expr_class = nullptr;
|
|
|
|
switch (p_expression.kind) {
|
|
case DataType::NATIVE: {
|
|
if (p_container.kind != DataType::NATIVE) {
|
|
// Non-native type can't be a superclass of a native type
|
|
return false;
|
|
}
|
|
if (p_expression.is_meta_type) {
|
|
expr_native = CScriptNativeClass::get_class_static();
|
|
} else {
|
|
expr_native = p_expression.native_type;
|
|
}
|
|
} break;
|
|
case DataType::SCRIPT:
|
|
case DataType::CSCRIPT: {
|
|
if (p_container.kind == DataType::CLASS) {
|
|
// This cannot be resolved without cyclic dependencies, so just bail out
|
|
return false;
|
|
}
|
|
if (p_expression.is_meta_type) {
|
|
expr_native = p_expression.script_type->get_class_name();
|
|
} else {
|
|
expr_script = p_expression.script_type;
|
|
expr_native = expr_script->get_instance_base_type();
|
|
}
|
|
} break;
|
|
case DataType::CLASS: {
|
|
if (p_expression.is_meta_type) {
|
|
expr_native = CScript::get_class_static();
|
|
} else {
|
|
expr_class = p_expression.class_type;
|
|
ClassNode *base = expr_class;
|
|
while (base->base_type.kind == DataType::CLASS) {
|
|
base = base->base_type.class_type;
|
|
}
|
|
expr_native = base->base_type.native_type;
|
|
expr_script = base->base_type.script_type;
|
|
}
|
|
} break;
|
|
case DataType::BUILTIN: // Already handled above
|
|
case DataType::UNRESOLVED: // Not allowed, see above
|
|
break;
|
|
}
|
|
|
|
// Some classes are prefixed with `_` internally
|
|
if (!ClassDB::class_exists(expr_native)) {
|
|
expr_native = "_" + expr_native;
|
|
}
|
|
|
|
switch (p_container.kind) {
|
|
case DataType::NATIVE: {
|
|
if (p_container.is_meta_type) {
|
|
return ClassDB::is_parent_class(expr_native, CScriptNativeClass::get_class_static());
|
|
} else {
|
|
StringName container_native = ClassDB::class_exists(p_container.native_type) ? p_container.native_type : StringName("_" + p_container.native_type);
|
|
return ClassDB::is_parent_class(expr_native, container_native);
|
|
}
|
|
} break;
|
|
case DataType::SCRIPT:
|
|
case DataType::CSCRIPT: {
|
|
if (p_container.is_meta_type) {
|
|
return ClassDB::is_parent_class(expr_native, CScript::get_class_static());
|
|
}
|
|
if (expr_class == head && p_container.script_type->get_path() == self_path) {
|
|
// Special case: container is self script and expression is self
|
|
return true;
|
|
}
|
|
while (expr_script.is_valid()) {
|
|
if (expr_script == p_container.script_type) {
|
|
return true;
|
|
}
|
|
expr_script = expr_script->get_base_script();
|
|
}
|
|
return false;
|
|
} break;
|
|
case DataType::CLASS: {
|
|
if (p_container.is_meta_type) {
|
|
return ClassDB::is_parent_class(expr_native, CScript::get_class_static());
|
|
}
|
|
if (p_container.class_type == head && expr_script.is_valid() && expr_script->get_path() == self_path) {
|
|
// Special case: container is self and expression is self script
|
|
return true;
|
|
}
|
|
while (expr_class) {
|
|
if (expr_class == p_container.class_type) {
|
|
return true;
|
|
}
|
|
expr_class = expr_class->base_type.class_type;
|
|
}
|
|
return false;
|
|
} break;
|
|
case DataType::BUILTIN: // Already handled above
|
|
case DataType::UNRESOLVED: // Not allowed, see above
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CScriptParser::Node *CScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) {
|
|
Node *result;
|
|
|
|
if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) {
|
|
if (p_type.builtin_type == Variant::ARRAY) {
|
|
result = alloc_node<ArrayNode>();
|
|
} else if (p_type.builtin_type == Variant::DICTIONARY) {
|
|
result = alloc_node<DictionaryNode>();
|
|
} else {
|
|
ConstantNode *c = alloc_node<ConstantNode>();
|
|
Variant::CallError err;
|
|
c->value = Variant::construct(p_type.builtin_type, nullptr, 0, err);
|
|
result = c;
|
|
}
|
|
} else {
|
|
ConstantNode *c = alloc_node<ConstantNode>();
|
|
c->value = Variant();
|
|
result = c;
|
|
}
|
|
|
|
result->line = p_line;
|
|
|
|
return result;
|
|
}
|
|
|
|
CScriptParser::DataType CScriptParser::_reduce_node_type(Node *p_node) {
|
|
#ifdef DEBUG_ENABLED
|
|
if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) {
|
|
#else
|
|
if (p_node->get_datatype().has_type) {
|
|
#endif
|
|
return p_node->get_datatype();
|
|
}
|
|
|
|
DataType node_type;
|
|
|
|
switch (p_node->type) {
|
|
case Node::TYPE_CONSTANT: {
|
|
node_type = _type_from_variant(static_cast<ConstantNode *>(p_node)->value);
|
|
} break;
|
|
case Node::TYPE_TYPE: {
|
|
TypeNode *tn = static_cast<TypeNode *>(p_node);
|
|
node_type.has_type = true;
|
|
node_type.is_meta_type = true;
|
|
node_type.kind = DataType::BUILTIN;
|
|
node_type.builtin_type = tn->vtype;
|
|
} break;
|
|
case Node::TYPE_ARRAY: {
|
|
node_type.has_type = true;
|
|
node_type.kind = DataType::BUILTIN;
|
|
node_type.builtin_type = Variant::ARRAY;
|
|
#ifdef DEBUG_ENABLED
|
|
// Check stuff inside the array
|
|
ArrayNode *an = static_cast<ArrayNode *>(p_node);
|
|
for (int i = 0; i < an->elements.size(); i++) {
|
|
_reduce_node_type(an->elements[i]);
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
} break;
|
|
case Node::TYPE_DICTIONARY: {
|
|
node_type.has_type = true;
|
|
node_type.kind = DataType::BUILTIN;
|
|
node_type.builtin_type = Variant::DICTIONARY;
|
|
#ifdef DEBUG_ENABLED
|
|
// Check stuff inside the dictionarty
|
|
DictionaryNode *dn = static_cast<DictionaryNode *>(p_node);
|
|
for (int i = 0; i < dn->elements.size(); i++) {
|
|
_reduce_node_type(dn->elements[i].key);
|
|
_reduce_node_type(dn->elements[i].value);
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
} break;
|
|
case Node::TYPE_SELF: {
|
|
node_type.has_type = true;
|
|
node_type.kind = DataType::CLASS;
|
|
node_type.class_type = current_class;
|
|
node_type.is_constant = true;
|
|
} break;
|
|
case Node::TYPE_IDENTIFIER: {
|
|
IdentifierNode *id = static_cast<IdentifierNode *>(p_node);
|
|
if (id->declared_block) {
|
|
node_type = id->declared_block->variables[id->name]->get_datatype();
|
|
id->declared_block->variables[id->name]->usages += 1;
|
|
} else if (id->name == "#match_value") {
|
|
// It's a special id just for the match statetement, ignore
|
|
break;
|
|
} else if (current_function && current_function->arguments.find(id->name) >= 0) {
|
|
int idx = current_function->arguments.find(id->name);
|
|
node_type = current_function->argument_types[idx];
|
|
} else {
|
|
node_type = _reduce_identifier_type(nullptr, id->name, id->line, false);
|
|
}
|
|
} break;
|
|
case Node::TYPE_CAST: {
|
|
CastNode *cn = static_cast<CastNode *>(p_node);
|
|
|
|
DataType source_type = _reduce_node_type(cn->source_node);
|
|
cn->cast_type = _resolve_type(cn->cast_type, cn->line);
|
|
if (source_type.has_type) {
|
|
bool valid = false;
|
|
if (check_types) {
|
|
if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) {
|
|
valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type);
|
|
}
|
|
if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) {
|
|
valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type);
|
|
}
|
|
|
|
if (!valid) {
|
|
_set_error("Invalid cast. Cannot convert from \"" + source_type.to_string() +
|
|
"\" to \"" + cn->cast_type.to_string() + "\".",
|
|
cn->line);
|
|
return DataType();
|
|
}
|
|
}
|
|
} else {
|
|
#ifdef DEBUG_ENABLED
|
|
_add_warning(CScriptWarning::UNSAFE_CAST, cn->line, cn->cast_type.to_string());
|
|
#endif // DEBUG_ENABLED
|
|
_mark_line_as_unsafe(cn->line);
|
|
}
|
|
|
|
node_type = cn->cast_type;
|
|
|
|
} break;
|
|
case Node::TYPE_OPERATOR: {
|
|
OperatorNode *op = static_cast<OperatorNode *>(p_node);
|
|
|
|
switch (op->op) {
|
|
case OperatorNode::OP_CALL:
|
|
case OperatorNode::OP_PARENT_CALL: {
|
|
node_type = _reduce_function_call_type(op);
|
|
} break;
|
|
case OperatorNode::OP_IS:
|
|
case OperatorNode::OP_IS_BUILTIN: {
|
|
if (op->arguments.size() != 2) {
|
|
_set_error("Parser bug: binary operation without 2 arguments.", op->line);
|
|
ERR_FAIL_V(DataType());
|
|
}
|
|
|
|
DataType value_type = _reduce_node_type(op->arguments[0]);
|
|
DataType type_type = _reduce_node_type(op->arguments[1]);
|
|
|
|
if (check_types && type_type.has_type) {
|
|
if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) {
|
|
_set_error("Invalid \"is\" test: the right operand isn't a type (neither a native type nor a script).", op->line);
|
|
return DataType();
|
|
}
|
|
type_type.is_meta_type = false; // Test the actual type
|
|
if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) {
|
|
if (op->op == OperatorNode::OP_IS) {
|
|
_set_error("A value of type \"" + value_type.to_string() + "\" will never be an instance of \"" + type_type.to_string() + "\".", op->line);
|
|
} else {
|
|
_set_error("A value of type \"" + value_type.to_string() + "\" will never be of type \"" + type_type.to_string() + "\".", op->line);
|
|
}
|
|
return DataType();
|
|
}
|
|
}
|
|
|
|
node_type.has_type = true;
|
|
node_type.is_constant = true;
|
|
node_type.is_meta_type = false;
|
|
node_type.kind = DataType::BUILTIN;
|
|
node_type.builtin_type = Variant::BOOL;
|
|
} break;
|
|
// Unary operators
|
|
case OperatorNode::OP_NEG:
|
|
case OperatorNode::OP_POS:
|
|
case OperatorNode::OP_NOT:
|
|
case OperatorNode::OP_BIT_INVERT: {
|
|
DataType argument_type = _reduce_node_type(op->arguments[0]);
|
|
if (!argument_type.has_type) {
|
|
break;
|
|
}
|
|
|
|
Variant::Operator var_op = _get_variant_operation(op->op);
|
|
bool valid = false;
|
|
node_type = _get_operation_type(var_op, argument_type, argument_type, valid);
|
|
|
|
if (check_types && !valid) {
|
|
_set_error("Invalid operand type (\"" + argument_type.to_string() +
|
|
"\") to unary operator \"" + Variant::get_operator_name(var_op) + "\".",
|
|
op->line, op->column);
|
|
return DataType();
|
|
}
|
|
|
|
} break;
|
|
// Binary operators
|
|
case OperatorNode::OP_IN:
|
|
case OperatorNode::OP_EQUAL:
|
|
case OperatorNode::OP_NOT_EQUAL:
|
|
case OperatorNode::OP_LESS:
|
|
case OperatorNode::OP_LESS_EQUAL:
|
|
case OperatorNode::OP_GREATER:
|
|
case OperatorNode::OP_GREATER_EQUAL:
|
|
case OperatorNode::OP_AND:
|
|
case OperatorNode::OP_OR:
|
|
case OperatorNode::OP_ADD:
|
|
case OperatorNode::OP_SUB:
|
|
case OperatorNode::OP_MUL:
|
|
case OperatorNode::OP_DIV:
|
|
case OperatorNode::OP_MOD:
|
|
case OperatorNode::OP_SHIFT_LEFT:
|
|
case OperatorNode::OP_SHIFT_RIGHT:
|
|
case OperatorNode::OP_BIT_AND:
|
|
case OperatorNode::OP_BIT_OR:
|
|
case OperatorNode::OP_BIT_XOR: {
|
|
if (op->arguments.size() != 2) {
|
|
_set_error("Parser bug: binary operation without 2 arguments.", op->line);
|
|
ERR_FAIL_V(DataType());
|
|
}
|
|
|
|
DataType argument_a_type = _reduce_node_type(op->arguments[0]);
|
|
DataType argument_b_type = _reduce_node_type(op->arguments[1]);
|
|
if (!argument_a_type.has_type || !argument_b_type.has_type) {
|
|
_mark_line_as_unsafe(op->line);
|
|
break;
|
|
}
|
|
|
|
Variant::Operator var_op = _get_variant_operation(op->op);
|
|
bool valid = false;
|
|
node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid);
|
|
|
|
if (check_types && !valid) {
|
|
_set_error("Invalid operand types (\"" + argument_a_type.to_string() + "\" and \"" +
|
|
argument_b_type.to_string() + "\") to operator \"" + Variant::get_operator_name(var_op) + "\".",
|
|
op->line, op->column);
|
|
return DataType();
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
if (var_op == Variant::OP_DIVIDE && argument_a_type.kind == DataType::BUILTIN && argument_a_type.builtin_type == Variant::INT &&
|
|
argument_b_type.kind == DataType::BUILTIN && argument_b_type.builtin_type == Variant::INT) {
|
|
_add_warning(CScriptWarning::INTEGER_DIVISION, op->line);
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
} break;
|
|
// Ternary operators
|
|
case OperatorNode::OP_TERNARY_IF: {
|
|
if (op->arguments.size() != 3) {
|
|
_set_error("Parser bug: ternary operation without 3 arguments.");
|
|
ERR_FAIL_V(DataType());
|
|
}
|
|
|
|
DataType true_type = _reduce_node_type(op->arguments[1]);
|
|
DataType false_type = _reduce_node_type(op->arguments[2]);
|
|
// Check arguments[0] errors.
|
|
_reduce_node_type(op->arguments[0]);
|
|
|
|
// If types are equal, then the expression is of the same type
|
|
// If they are compatible, return the broader type
|
|
if (true_type == false_type || _is_type_compatible(true_type, false_type)) {
|
|
node_type = true_type;
|
|
} else if (_is_type_compatible(false_type, true_type)) {
|
|
node_type = false_type;
|
|
} else {
|
|
#ifdef DEBUG_ENABLED
|
|
_add_warning(CScriptWarning::INCOMPATIBLE_TERNARY, op->line);
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
} break;
|
|
// Assignment should never happen within an expression
|
|
case OperatorNode::OP_ASSIGN:
|
|
case OperatorNode::OP_ASSIGN_ADD:
|
|
case OperatorNode::OP_ASSIGN_SUB:
|
|
case OperatorNode::OP_ASSIGN_MUL:
|
|
case OperatorNode::OP_ASSIGN_DIV:
|
|
case OperatorNode::OP_ASSIGN_MOD:
|
|
case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
|
|
case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
|
|
case OperatorNode::OP_ASSIGN_BIT_AND:
|
|
case OperatorNode::OP_ASSIGN_BIT_OR:
|
|
case OperatorNode::OP_ASSIGN_BIT_XOR:
|
|
case OperatorNode::OP_INIT_ASSIGN: {
|
|
_set_error("Assignment inside an expression isn't allowed (parser bug?).", op->line);
|
|
return DataType();
|
|
|
|
} break;
|
|
case OperatorNode::OP_INDEX_NAMED: {
|
|
if (op->arguments.size() != 2) {
|
|
_set_error("Parser bug: named index with invalid arguments.", op->line);
|
|
ERR_FAIL_V(DataType());
|
|
}
|
|
if (op->arguments[1]->type != Node::TYPE_IDENTIFIER) {
|
|
_set_error("Parser bug: named index without identifier argument.", op->line);
|
|
ERR_FAIL_V(DataType());
|
|
}
|
|
|
|
DataType base_type = _reduce_node_type(op->arguments[0]);
|
|
IdentifierNode *member_id = static_cast<IdentifierNode *>(op->arguments[1]);
|
|
|
|
if (base_type.has_type) {
|
|
if (check_types && base_type.kind == DataType::BUILTIN) {
|
|
// Variant type, just test if it's possible
|
|
DataType result;
|
|
switch (base_type.builtin_type) {
|
|
case Variant::NIL:
|
|
case Variant::DICTIONARY: {
|
|
result.has_type = false;
|
|
} break;
|
|
default: {
|
|
Variant::CallError err;
|
|
Variant temp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
|
|
|
|
bool valid = false;
|
|
Variant res = temp.get(member_id->name.operator String(), &valid);
|
|
|
|
if (valid) {
|
|
result = _type_from_variant(res);
|
|
} else if (check_types) {
|
|
_set_error("Can't get index \"" + String(member_id->name.operator String()) + "\" on base \"" +
|
|
base_type.to_string() + "\".",
|
|
op->line);
|
|
return DataType();
|
|
}
|
|
} break;
|
|
}
|
|
result.is_constant = false;
|
|
node_type = result;
|
|
} else {
|
|
node_type = _reduce_identifier_type(&base_type, member_id->name, op->line, true);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!node_type.has_type) {
|
|
_mark_line_as_unsafe(op->line);
|
|
_add_warning(CScriptWarning::UNSAFE_PROPERTY_ACCESS, op->line, member_id->name.operator String(), base_type.to_string());
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
} else {
|
|
_mark_line_as_unsafe(op->line);
|
|
}
|
|
if (error_set) {
|
|
return DataType();
|
|
}
|
|
} break;
|
|
case OperatorNode::OP_INDEX: {
|
|
if (op->arguments[1]->type == Node::TYPE_CONSTANT) {
|
|
ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
|
|
if (cn->value.get_type() == Variant::STRING) {
|
|
// Treat this as named indexing
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = cn->value.operator StringName();
|
|
id->datatype = cn->datatype;
|
|
|
|
op->op = OperatorNode::OP_INDEX_NAMED;
|
|
op->arguments.write[1] = id;
|
|
|
|
return _reduce_node_type(op);
|
|
}
|
|
}
|
|
|
|
DataType base_type = _reduce_node_type(op->arguments[0]);
|
|
DataType index_type = _reduce_node_type(op->arguments[1]);
|
|
|
|
if (!base_type.has_type) {
|
|
_mark_line_as_unsafe(op->line);
|
|
break;
|
|
}
|
|
|
|
if (check_types && index_type.has_type) {
|
|
if (base_type.kind == DataType::BUILTIN) {
|
|
// Check if indexing is valid
|
|
bool error = index_type.kind != DataType::BUILTIN && base_type.builtin_type != Variant::DICTIONARY;
|
|
if (!error) {
|
|
switch (base_type.builtin_type) {
|
|
// Expect int or real as index
|
|
case Variant::POOL_BYTE_ARRAY:
|
|
case Variant::POOL_COLOR_ARRAY:
|
|
case Variant::POOL_INT_ARRAY:
|
|
case Variant::POOL_REAL_ARRAY:
|
|
case Variant::POOL_STRING_ARRAY:
|
|
case Variant::POOL_VECTOR2_ARRAY:
|
|
case Variant::POOL_VECTOR2I_ARRAY:
|
|
case Variant::POOL_VECTOR3_ARRAY:
|
|
case Variant::POOL_VECTOR3I_ARRAY:
|
|
case Variant::ARRAY:
|
|
case Variant::STRING: {
|
|
error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL;
|
|
} break;
|
|
// Expect String only
|
|
case Variant::RECT2:
|
|
case Variant::RECT2I:
|
|
case Variant::PLANE:
|
|
case Variant::QUAT:
|
|
case Variant::AABB:
|
|
case Variant::OBJECT: {
|
|
error = index_type.builtin_type != Variant::STRING;
|
|
} break;
|
|
// Expect String or number
|
|
case Variant::VECTOR2:
|
|
case Variant::VECTOR2I:
|
|
case Variant::VECTOR3:
|
|
case Variant::VECTOR3I:
|
|
case Variant::TRANSFORM2D:
|
|
case Variant::BASIS:
|
|
case Variant::TRANSFORM: {
|
|
error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL &&
|
|
index_type.builtin_type != Variant::STRING;
|
|
} break;
|
|
// Expect String or int
|
|
case Variant::COLOR: {
|
|
error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING;
|
|
} break;
|
|
default: {
|
|
}
|
|
}
|
|
}
|
|
if (error) {
|
|
_set_error("Invalid index type (" + index_type.to_string() + ") for base \"" + base_type.to_string() + "\".",
|
|
op->line);
|
|
return DataType();
|
|
}
|
|
|
|
if (op->arguments[1]->type == CScriptParser::Node::TYPE_CONSTANT) {
|
|
ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
|
|
// Index is a constant, just try it if possible
|
|
switch (base_type.builtin_type) {
|
|
// Arrays/string have variable indexing, can't test directly
|
|
case Variant::STRING:
|
|
case Variant::ARRAY:
|
|
case Variant::DICTIONARY:
|
|
case Variant::POOL_BYTE_ARRAY:
|
|
case Variant::POOL_COLOR_ARRAY:
|
|
case Variant::POOL_INT_ARRAY:
|
|
case Variant::POOL_REAL_ARRAY:
|
|
case Variant::POOL_STRING_ARRAY:
|
|
case Variant::POOL_VECTOR2_ARRAY:
|
|
case Variant::POOL_VECTOR3_ARRAY: {
|
|
break;
|
|
}
|
|
default: {
|
|
Variant::CallError err;
|
|
Variant temp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
|
|
|
|
bool valid = false;
|
|
Variant res = temp.get(cn->value, &valid);
|
|
|
|
if (valid) {
|
|
node_type = _type_from_variant(res);
|
|
node_type.is_constant = false;
|
|
} else if (check_types) {
|
|
_set_error("Can't get index \"" + String(cn->value) + "\" on base \"" +
|
|
base_type.to_string() + "\".",
|
|
op->line);
|
|
return DataType();
|
|
}
|
|
} break;
|
|
}
|
|
} else {
|
|
_mark_line_as_unsafe(op->line);
|
|
}
|
|
} else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) {
|
|
_set_error("Only strings can be used as an index in the base type \"" + base_type.to_string() + "\".", op->line);
|
|
return DataType();
|
|
}
|
|
}
|
|
if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) {
|
|
// Can infer indexing type for some variant types
|
|
DataType result;
|
|
result.has_type = true;
|
|
result.kind = DataType::BUILTIN;
|
|
switch (base_type.builtin_type) {
|
|
// Can't index at all
|
|
case Variant::NIL:
|
|
case Variant::BOOL:
|
|
case Variant::INT:
|
|
case Variant::REAL:
|
|
case Variant::NODE_PATH:
|
|
case Variant::_RID: {
|
|
_set_error("Can't index on a value of type \"" + base_type.to_string() + "\".", op->line);
|
|
return DataType();
|
|
} break;
|
|
// Return int
|
|
case Variant::POOL_BYTE_ARRAY:
|
|
case Variant::POOL_INT_ARRAY: {
|
|
result.builtin_type = Variant::INT;
|
|
} break;
|
|
// Return real
|
|
case Variant::POOL_REAL_ARRAY:
|
|
case Variant::VECTOR2:
|
|
case Variant::VECTOR3:
|
|
case Variant::QUAT: {
|
|
result.builtin_type = Variant::REAL;
|
|
} break;
|
|
// Return color
|
|
case Variant::POOL_COLOR_ARRAY: {
|
|
result.builtin_type = Variant::COLOR;
|
|
} break;
|
|
// Return string
|
|
case Variant::POOL_STRING_ARRAY:
|
|
case Variant::STRING: {
|
|
result.builtin_type = Variant::STRING;
|
|
} break;
|
|
// Return Vector2
|
|
case Variant::POOL_VECTOR2_ARRAY:
|
|
case Variant::TRANSFORM2D:
|
|
case Variant::RECT2: {
|
|
result.builtin_type = Variant::VECTOR2;
|
|
} break;
|
|
// Return Vector3
|
|
case Variant::POOL_VECTOR3_ARRAY:
|
|
case Variant::AABB:
|
|
case Variant::BASIS: {
|
|
result.builtin_type = Variant::VECTOR3;
|
|
} break;
|
|
// Depends on the index
|
|
case Variant::TRANSFORM:
|
|
case Variant::PLANE:
|
|
case Variant::COLOR:
|
|
default: {
|
|
result.has_type = false;
|
|
} break;
|
|
}
|
|
node_type = result;
|
|
}
|
|
} break;
|
|
default: {
|
|
_set_error("Parser bug: unhandled operation.", op->line);
|
|
ERR_FAIL_V(DataType());
|
|
}
|
|
}
|
|
} break;
|
|
default: {
|
|
}
|
|
}
|
|
|
|
node_type = _resolve_type(node_type, p_node->line);
|
|
p_node->set_datatype(node_type);
|
|
return node_type;
|
|
}
|
|
|
|
bool CScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const {
|
|
r_static = false;
|
|
r_default_arg_count = 0;
|
|
|
|
DataType original_type = p_base_type;
|
|
ClassNode *base = nullptr;
|
|
FunctionNode *callee = nullptr;
|
|
|
|
if (p_base_type.kind == DataType::CLASS) {
|
|
base = p_base_type.class_type;
|
|
}
|
|
|
|
// Look up the current file (parse tree)
|
|
while (!callee && base) {
|
|
for (int i = 0; i < base->static_functions.size(); i++) {
|
|
FunctionNode *func = base->static_functions[i];
|
|
if (p_function == func->name) {
|
|
r_static = true;
|
|
callee = func;
|
|
break;
|
|
}
|
|
}
|
|
if (!callee && !p_base_type.is_meta_type) {
|
|
for (int i = 0; i < base->functions.size(); i++) {
|
|
FunctionNode *func = base->functions[i];
|
|
if (p_function == func->name) {
|
|
callee = func;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
p_base_type = base->base_type;
|
|
if (p_base_type.kind == DataType::CLASS) {
|
|
base = p_base_type.class_type;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (callee) {
|
|
r_return_type = callee->get_datatype();
|
|
for (int i = 0; i < callee->argument_types.size(); i++) {
|
|
r_arg_types.push_back(callee->argument_types[i]);
|
|
}
|
|
r_default_arg_count = callee->default_values.size();
|
|
return true;
|
|
}
|
|
|
|
// Nothing in current file, check parent script
|
|
Ref<CScript> base_gdscript;
|
|
Ref<Script> base_script;
|
|
StringName native;
|
|
if (p_base_type.kind == DataType::CSCRIPT) {
|
|
base_gdscript = p_base_type.script_type;
|
|
if (base_gdscript.is_null() || !base_gdscript->is_valid()) {
|
|
// CScript wasn't properly compíled, don't bother trying
|
|
return false;
|
|
}
|
|
} else if (p_base_type.kind == DataType::SCRIPT) {
|
|
base_script = p_base_type.script_type;
|
|
} else if (p_base_type.kind == DataType::NATIVE) {
|
|
native = p_base_type.native_type;
|
|
}
|
|
|
|
while (base_gdscript.is_valid()) {
|
|
native = base_gdscript->get_instance_base_type();
|
|
|
|
Map<StringName, CScriptFunction *> funcs = base_gdscript->get_member_functions();
|
|
|
|
if (funcs.has(p_function)) {
|
|
CScriptFunction *f = funcs[p_function];
|
|
r_static = f->is_static();
|
|
r_default_arg_count = f->get_default_argument_count();
|
|
r_return_type = _type_from_gdtype(f->get_return_type());
|
|
for (int i = 0; i < f->get_argument_count(); i++) {
|
|
r_arg_types.push_back(_type_from_gdtype(f->get_argument_type(i)));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
base_gdscript = base_gdscript->get_base_script();
|
|
}
|
|
|
|
while (base_script.is_valid()) {
|
|
native = base_script->get_instance_base_type();
|
|
MethodInfo mi = base_script->get_method_info(p_function);
|
|
|
|
if (!(mi == MethodInfo())) {
|
|
r_return_type = _type_from_property(mi.return_val, false);
|
|
r_default_arg_count = mi.default_arguments.size();
|
|
for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
|
|
r_arg_types.push_back(_type_from_property(E->get()));
|
|
}
|
|
return true;
|
|
}
|
|
base_script = base_script->get_base_script();
|
|
}
|
|
|
|
if (native == StringName()) {
|
|
// Empty native class, might happen in some Script implementations
|
|
// Just ignore it
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEBUG_METHODS_ENABLED
|
|
|
|
// Only native remains
|
|
if (!ClassDB::class_exists(native)) {
|
|
native = "_" + native.operator String();
|
|
}
|
|
if (!ClassDB::class_exists(native)) {
|
|
if (!check_types) {
|
|
return false;
|
|
}
|
|
ERR_FAIL_V_MSG(false, "Parser bug: Class '" + String(native) + "' not found.");
|
|
}
|
|
|
|
MethodBind *method = ClassDB::get_method(native, p_function);
|
|
|
|
if (!method) {
|
|
// Try virtual methods
|
|
List<MethodInfo> virtuals;
|
|
ClassDB::get_virtual_methods(native, &virtuals);
|
|
|
|
for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
|
|
const MethodInfo &mi = E->get();
|
|
if (mi.name == p_function) {
|
|
r_default_arg_count = mi.default_arguments.size();
|
|
for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
|
|
r_arg_types.push_back(_type_from_property(pi->get()));
|
|
}
|
|
r_return_type = _type_from_property(mi.return_val, false);
|
|
r_vararg = mi.flags & METHOD_FLAG_VARARG;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If the base is a script, it might be trying to access members of the Script class itself
|
|
if (original_type.is_meta_type && !(p_function == "new") && (original_type.kind == DataType::SCRIPT || original_type.kind == DataType::CSCRIPT)) {
|
|
method = ClassDB::get_method(original_type.script_type->get_class_name(), p_function);
|
|
|
|
if (method) {
|
|
r_static = true;
|
|
} else {
|
|
// Try virtual methods of the script type
|
|
virtuals.clear();
|
|
ClassDB::get_virtual_methods(original_type.script_type->get_class_name(), &virtuals);
|
|
for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
|
|
const MethodInfo &mi = E->get();
|
|
if (mi.name == p_function) {
|
|
r_default_arg_count = mi.default_arguments.size();
|
|
for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
|
|
r_arg_types.push_back(_type_from_property(pi->get()));
|
|
}
|
|
r_return_type = _type_from_property(mi.return_val, false);
|
|
r_static = true;
|
|
r_vararg = mi.flags & METHOD_FLAG_VARARG;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
r_default_arg_count = method->get_default_argument_count();
|
|
if (method->get_name() == "get_script") {
|
|
r_return_type = DataType(); // Variant for now and let runtime decide.
|
|
} else {
|
|
r_return_type = _type_from_property(method->get_return_info(), false);
|
|
}
|
|
r_vararg = method->is_vararg();
|
|
|
|
for (int i = 0; i < method->get_argument_count(); i++) {
|
|
r_arg_types.push_back(_type_from_property(method->get_argument_info(i)));
|
|
}
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
CScriptParser::DataType CScriptParser::_reduce_function_call_type(const OperatorNode *p_call) {
|
|
if (p_call->arguments.size() < 1) {
|
|
_set_error("Parser bug: function call without enough arguments.", p_call->line);
|
|
ERR_FAIL_V(DataType());
|
|
}
|
|
|
|
DataType return_type;
|
|
List<DataType> arg_types;
|
|
int default_args_count = 0;
|
|
String callee_name;
|
|
bool is_vararg = false;
|
|
#ifdef DEBUG_ENABLED
|
|
int arg_count = p_call->arguments.size();
|
|
#endif
|
|
|
|
switch (p_call->arguments[0]->type) {
|
|
case CScriptParser::Node::TYPE_TYPE: {
|
|
// Built-in constructor, special case
|
|
TypeNode *tn = static_cast<TypeNode *>(p_call->arguments[0]);
|
|
|
|
Vector<DataType> par_types;
|
|
par_types.resize(p_call->arguments.size() - 1);
|
|
for (int i = 1; i < p_call->arguments.size(); i++) {
|
|
par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]);
|
|
}
|
|
|
|
if (error_set) {
|
|
return DataType();
|
|
}
|
|
|
|
// Special case: check copy constructor. Those are defined implicitly in Variant.
|
|
if (par_types.size() == 1) {
|
|
if (!par_types[0].has_type || (par_types[0].kind == DataType::BUILTIN && par_types[0].builtin_type == tn->vtype)) {
|
|
DataType result;
|
|
result.has_type = true;
|
|
result.kind = DataType::BUILTIN;
|
|
result.builtin_type = tn->vtype;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
bool match = false;
|
|
List<MethodInfo> constructors;
|
|
Variant::get_constructor_list(tn->vtype, &constructors);
|
|
PropertyInfo return_type2;
|
|
|
|
for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) {
|
|
MethodInfo &mi = E->get();
|
|
|
|
if (p_call->arguments.size() - 1 < mi.arguments.size() - mi.default_arguments.size()) {
|
|
continue;
|
|
}
|
|
if (p_call->arguments.size() - 1 > mi.arguments.size()) {
|
|
continue;
|
|
}
|
|
|
|
bool types_match = true;
|
|
for (int i = 0; i < par_types.size(); i++) {
|
|
DataType arg_type;
|
|
if (mi.arguments[i].type != Variant::NIL) {
|
|
arg_type.has_type = true;
|
|
arg_type.kind = mi.arguments[i].type == Variant::OBJECT ? DataType::NATIVE : DataType::BUILTIN;
|
|
arg_type.builtin_type = mi.arguments[i].type;
|
|
arg_type.native_type = mi.arguments[i].class_name;
|
|
}
|
|
|
|
if (!_is_type_compatible(arg_type, par_types[i], true)) {
|
|
types_match = false;
|
|
break;
|
|
} else {
|
|
#ifdef DEBUG_ENABLED
|
|
if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_types[i].kind == DataType::BUILTIN && par_types[i].builtin_type == Variant::REAL) {
|
|
_add_warning(CScriptWarning::NARROWING_CONVERSION, p_call->line, Variant::get_type_name(tn->vtype));
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
}
|
|
|
|
if (types_match) {
|
|
match = true;
|
|
return_type2 = mi.return_val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match) {
|
|
return _type_from_property(return_type2, false);
|
|
} else if (check_types) {
|
|
String err = "No constructor of '";
|
|
err += Variant::get_type_name(tn->vtype);
|
|
err += "' matches the signature '";
|
|
err += Variant::get_type_name(tn->vtype) + "(";
|
|
for (int i = 0; i < par_types.size(); i++) {
|
|
if (i > 0) {
|
|
err += ", ";
|
|
}
|
|
err += par_types[i].to_string();
|
|
}
|
|
err += ")'.";
|
|
_set_error(err, p_call->line, p_call->column);
|
|
return DataType();
|
|
}
|
|
return DataType();
|
|
} break;
|
|
case CScriptParser::Node::TYPE_BUILT_IN_FUNCTION: {
|
|
BuiltInFunctionNode *func = static_cast<BuiltInFunctionNode *>(p_call->arguments[0]);
|
|
MethodInfo mi = CScriptFunctions::get_info(func->function);
|
|
|
|
return_type = _type_from_property(mi.return_val, false);
|
|
|
|
// Check all arguments beforehand to solve warnings
|
|
for (int i = 1; i < p_call->arguments.size(); i++) {
|
|
_reduce_node_type(p_call->arguments[i]);
|
|
}
|
|
|
|
// Check arguments
|
|
|
|
is_vararg = mi.flags & METHOD_FLAG_VARARG;
|
|
|
|
default_args_count = mi.default_arguments.size();
|
|
callee_name = mi.name;
|
|
#ifdef DEBUG_ENABLED
|
|
arg_count -= 1;
|
|
#endif
|
|
|
|
// Check each argument type
|
|
for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
|
|
arg_types.push_back(_type_from_property(E->get()));
|
|
}
|
|
} break;
|
|
default: {
|
|
if (p_call->op == OperatorNode::OP_CALL && p_call->arguments.size() < 2) {
|
|
_set_error("Parser bug: self method call without enough arguments.", p_call->line);
|
|
ERR_FAIL_V(DataType());
|
|
}
|
|
|
|
int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0;
|
|
|
|
if (p_call->arguments[arg_id]->type != Node::TYPE_IDENTIFIER) {
|
|
_set_error("Parser bug: invalid function call argument.", p_call->line);
|
|
ERR_FAIL_V(DataType());
|
|
}
|
|
|
|
// Check all arguments beforehand to solve warnings
|
|
for (int i = arg_id + 1; i < p_call->arguments.size(); i++) {
|
|
_reduce_node_type(p_call->arguments[i]);
|
|
}
|
|
|
|
IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]);
|
|
callee_name = func_id->name;
|
|
#ifdef DEBUG_ENABLED
|
|
arg_count -= 1 + arg_id;
|
|
#endif
|
|
|
|
DataType base_type;
|
|
if (p_call->op == OperatorNode::OP_PARENT_CALL) {
|
|
base_type = current_class->base_type;
|
|
} else {
|
|
base_type = _reduce_node_type(p_call->arguments[0]);
|
|
}
|
|
|
|
if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) {
|
|
_mark_line_as_unsafe(p_call->line);
|
|
return DataType();
|
|
}
|
|
|
|
if (base_type.kind == DataType::BUILTIN) {
|
|
Variant::CallError err;
|
|
Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
|
|
|
|
if (check_types) {
|
|
if (!tmp.has_method(callee_name)) {
|
|
_set_error("The method \"" + callee_name + "\" isn't declared on base \"" + base_type.to_string() + "\".", p_call->line);
|
|
return DataType();
|
|
}
|
|
|
|
default_args_count = Variant::get_method_default_arguments(base_type.builtin_type, callee_name).size();
|
|
const Vector<Variant::Type> &var_arg_types = Variant::get_method_argument_types(base_type.builtin_type, callee_name);
|
|
|
|
for (int i = 0; i < var_arg_types.size(); i++) {
|
|
DataType argtype;
|
|
if (var_arg_types[i] != Variant::NIL) {
|
|
argtype.has_type = true;
|
|
argtype.kind = DataType::BUILTIN;
|
|
argtype.builtin_type = var_arg_types[i];
|
|
}
|
|
arg_types.push_back(argtype);
|
|
}
|
|
}
|
|
|
|
bool rets = false;
|
|
return_type.has_type = true;
|
|
return_type.kind = DataType::BUILTIN;
|
|
return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name, &rets);
|
|
// If the method returns, but it might return any type, (Variant::NIL), pretend we don't know the type.
|
|
// At least make sure we know that it returns
|
|
if (rets && return_type.builtin_type == Variant::NIL) {
|
|
return_type.has_type = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
DataType original_type = base_type;
|
|
bool is_initializer = callee_name == "new";
|
|
bool is_get_script = p_call->arguments[0]->type == Node::TYPE_SELF && callee_name == "get_script";
|
|
bool is_static = false;
|
|
bool valid = false;
|
|
|
|
if (is_initializer && original_type.is_meta_type) {
|
|
// Try to check it as initializer
|
|
base_type = original_type;
|
|
callee_name = "_init";
|
|
base_type.is_meta_type = false;
|
|
|
|
valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
|
|
default_args_count, is_static, is_vararg);
|
|
|
|
return_type = original_type;
|
|
return_type.is_meta_type = false;
|
|
|
|
valid = true; // There's always an initializer, we can assume this is true
|
|
}
|
|
|
|
if (is_get_script) {
|
|
// get_script() can be considered a meta-type.
|
|
return_type.kind = DataType::CLASS;
|
|
return_type.class_type = static_cast<ClassNode *>(head);
|
|
return_type.is_meta_type = true;
|
|
valid = true;
|
|
}
|
|
|
|
if (!valid) {
|
|
base_type = original_type;
|
|
return_type = DataType();
|
|
valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
|
|
default_args_count, is_static, is_vararg);
|
|
}
|
|
|
|
if (!valid) {
|
|
#ifdef DEBUG_ENABLED
|
|
if (p_call->arguments[0]->type == Node::TYPE_SELF) {
|
|
_set_error("The method \"" + callee_name + "\" isn't declared in the current class.", p_call->line);
|
|
return DataType();
|
|
}
|
|
DataType tmp_type;
|
|
valid = _get_member_type(original_type, func_id->name, tmp_type);
|
|
if (valid) {
|
|
if (tmp_type.is_constant) {
|
|
_add_warning(CScriptWarning::CONSTANT_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string());
|
|
} else {
|
|
_add_warning(CScriptWarning::PROPERTY_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string());
|
|
}
|
|
}
|
|
_add_warning(CScriptWarning::UNSAFE_METHOD_ACCESS, p_call->line, callee_name, original_type.to_string());
|
|
_mark_line_as_unsafe(p_call->line);
|
|
#endif // DEBUG_ENABLED
|
|
return DataType();
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) {
|
|
_set_error("Can't call non-static function from a static function.", p_call->line);
|
|
return DataType();
|
|
}
|
|
|
|
if (check_types && !is_static && !is_initializer && base_type.is_meta_type) {
|
|
_set_error("Non-static function \"" + String(callee_name) + "\" can only be called from an instance.", p_call->line);
|
|
return DataType();
|
|
}
|
|
|
|
// Check signal emission for warnings
|
|
if (callee_name == "emit_signal" && p_call->op == OperatorNode::OP_CALL && p_call->arguments[0]->type == Node::TYPE_SELF && p_call->arguments.size() >= 3 && p_call->arguments[2]->type == Node::TYPE_CONSTANT) {
|
|
ConstantNode *sig = static_cast<ConstantNode *>(p_call->arguments[2]);
|
|
String emitted = sig->value.get_type() == Variant::STRING ? sig->value.operator String() : "";
|
|
for (int i = 0; i < current_class->_signals.size(); i++) {
|
|
if (current_class->_signals[i].name == emitted) {
|
|
current_class->_signals.write[i].emissions += 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
} break;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
if (!check_types) {
|
|
return return_type;
|
|
}
|
|
|
|
if (arg_count < arg_types.size() - default_args_count) {
|
|
_set_error("Too few arguments for \"" + callee_name + "()\" call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line);
|
|
return return_type;
|
|
}
|
|
if (!is_vararg && arg_count > arg_types.size()) {
|
|
_set_error("Too many arguments for \"" + callee_name + "()\" call. Expected at most " + itos(arg_types.size()) + ".", p_call->line);
|
|
return return_type;
|
|
}
|
|
|
|
int arg_diff = p_call->arguments.size() - arg_count;
|
|
for (int i = arg_diff; i < p_call->arguments.size(); i++) {
|
|
DataType par_type = _reduce_node_type(p_call->arguments[i]);
|
|
|
|
if ((i - arg_diff) >= arg_types.size()) {
|
|
continue;
|
|
}
|
|
|
|
DataType arg_type = arg_types[i - arg_diff];
|
|
|
|
if (!par_type.has_type) {
|
|
_mark_line_as_unsafe(p_call->line);
|
|
} else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
|
|
// Supertypes are acceptable for dynamic compliance
|
|
if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) {
|
|
_set_error("At \"" + callee_name + "()\" call, argument " + itos(i - arg_diff + 1) + ". The passed argument's type (" +
|
|
par_type.to_string() + ") doesn't match the function's expected argument type (" +
|
|
arg_types[i - arg_diff].to_string() + ").",
|
|
p_call->line);
|
|
return DataType();
|
|
} else {
|
|
_mark_line_as_unsafe(p_call->line);
|
|
}
|
|
} else {
|
|
if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_type.kind == DataType::BUILTIN && par_type.builtin_type == Variant::REAL) {
|
|
_add_warning(CScriptWarning::NARROWING_CONVERSION, p_call->line, callee_name);
|
|
}
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
return return_type;
|
|
}
|
|
|
|
bool CScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type, bool *r_is_const) const {
|
|
DataType base_type = p_base_type;
|
|
|
|
// Check classes in current file
|
|
ClassNode *base = nullptr;
|
|
if (base_type.kind == DataType::CLASS) {
|
|
base = base_type.class_type;
|
|
}
|
|
|
|
while (base) {
|
|
if (base->constant_expressions.has(p_member)) {
|
|
if (r_is_const) {
|
|
*r_is_const = true;
|
|
}
|
|
r_member_type = base->constant_expressions[p_member].expression->get_datatype();
|
|
return true;
|
|
}
|
|
|
|
if (!base_type.is_meta_type) {
|
|
for (int i = 0; i < base->variables.size(); i++) {
|
|
if (base->variables[i].identifier == p_member) {
|
|
r_member_type = base->variables[i].data_type;
|
|
base->variables.write[i].usages += 1;
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
for (int i = 0; i < base->subclasses.size(); i++) {
|
|
ClassNode *c = base->subclasses[i];
|
|
if (c->name == p_member) {
|
|
DataType class_type;
|
|
class_type.has_type = true;
|
|
class_type.is_constant = true;
|
|
class_type.is_meta_type = true;
|
|
class_type.kind = DataType::CLASS;
|
|
class_type.class_type = c;
|
|
r_member_type = class_type;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
base_type = base->base_type;
|
|
if (base_type.kind == DataType::CLASS) {
|
|
base = base_type.class_type;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ref<CScript> gds;
|
|
if (base_type.kind == DataType::CSCRIPT) {
|
|
gds = base_type.script_type;
|
|
if (gds.is_null() || !gds->is_valid()) {
|
|
// CScript wasn't properly compíled, don't bother trying
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Ref<Script> scr;
|
|
if (base_type.kind == DataType::SCRIPT) {
|
|
scr = base_type.script_type;
|
|
}
|
|
|
|
StringName native;
|
|
if (base_type.kind == DataType::NATIVE) {
|
|
native = base_type.native_type;
|
|
}
|
|
|
|
// Check CScripts
|
|
while (gds.is_valid()) {
|
|
if (gds->get_constants().has(p_member)) {
|
|
Variant c = gds->get_constants()[p_member];
|
|
r_member_type = _type_from_variant(c);
|
|
return true;
|
|
}
|
|
|
|
if (!base_type.is_meta_type) {
|
|
if (gds->get_members().has(p_member)) {
|
|
r_member_type = _type_from_gdtype(gds->get_member_type(p_member));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
native = gds->get_instance_base_type();
|
|
if (gds->get_base_script().is_valid()) {
|
|
gds = gds->get_base_script();
|
|
scr = gds->get_base_script();
|
|
bool is_meta = base_type.is_meta_type;
|
|
base_type = _type_from_variant(scr.operator Variant());
|
|
base_type.is_meta_type = is_meta;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define IS_USAGE_MEMBER(m_usage) (!(m_usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)))
|
|
|
|
// Check other script types
|
|
while (scr.is_valid()) {
|
|
Map<StringName, Variant> constants;
|
|
scr->get_constants(&constants);
|
|
if (constants.has(p_member)) {
|
|
r_member_type = _type_from_variant(constants[p_member]);
|
|
return true;
|
|
}
|
|
|
|
List<PropertyInfo> properties;
|
|
scr->get_script_property_list(&properties);
|
|
for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
|
|
if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) {
|
|
r_member_type = _type_from_property(E->get());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
base_type = _type_from_variant(scr.operator Variant());
|
|
native = scr->get_instance_base_type();
|
|
scr = scr->get_base_script();
|
|
}
|
|
|
|
if (native == StringName()) {
|
|
// Empty native class, might happen in some Script implementations
|
|
// Just ignore it
|
|
return false;
|
|
}
|
|
|
|
// Check ClassDB
|
|
if (!ClassDB::class_exists(native)) {
|
|
native = "_" + native.operator String();
|
|
}
|
|
if (!ClassDB::class_exists(native)) {
|
|
if (!check_types) {
|
|
return false;
|
|
}
|
|
ERR_FAIL_V_MSG(false, "Parser bug: Class \"" + String(native) + "\" not found.");
|
|
}
|
|
|
|
bool valid = false;
|
|
ClassDB::get_integer_constant(native, p_member, &valid);
|
|
if (valid) {
|
|
DataType ct;
|
|
ct.has_type = true;
|
|
ct.is_constant = true;
|
|
ct.kind = DataType::BUILTIN;
|
|
ct.builtin_type = Variant::INT;
|
|
r_member_type = ct;
|
|
return true;
|
|
}
|
|
|
|
if (!base_type.is_meta_type) {
|
|
List<PropertyInfo> properties;
|
|
ClassDB::get_property_list(native, &properties);
|
|
for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
|
|
if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) {
|
|
// Check if a getter exists
|
|
StringName getter_name = ClassDB::get_property_getter(native, p_member);
|
|
if (getter_name != StringName()) {
|
|
// Use the getter return type
|
|
#ifdef DEBUG_METHODS_ENABLED
|
|
MethodBind *getter_method = ClassDB::get_method(native, getter_name);
|
|
if (getter_method) {
|
|
r_member_type = _type_from_property(getter_method->get_return_info());
|
|
} else {
|
|
r_member_type = DataType();
|
|
}
|
|
#else
|
|
r_member_type = DataType();
|
|
#endif
|
|
} else {
|
|
r_member_type = _type_from_property(E->get());
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the base is a script, it might be trying to access members of the Script class itself
|
|
if (p_base_type.is_meta_type && (p_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::CSCRIPT)) {
|
|
native = p_base_type.script_type->get_class_name();
|
|
ClassDB::get_integer_constant(native, p_member, &valid);
|
|
if (valid) {
|
|
DataType ct;
|
|
ct.has_type = true;
|
|
ct.is_constant = true;
|
|
ct.kind = DataType::BUILTIN;
|
|
ct.builtin_type = Variant::INT;
|
|
r_member_type = ct;
|
|
return true;
|
|
}
|
|
|
|
List<PropertyInfo> properties;
|
|
ClassDB::get_property_list(native, &properties);
|
|
for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
|
|
if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) {
|
|
// Check if a getter exists
|
|
StringName getter_name = ClassDB::get_property_getter(native, p_member);
|
|
if (getter_name != StringName()) {
|
|
// Use the getter return type
|
|
#ifdef DEBUG_METHODS_ENABLED
|
|
MethodBind *getter_method = ClassDB::get_method(native, getter_name);
|
|
if (getter_method) {
|
|
r_member_type = _type_from_property(getter_method->get_return_info());
|
|
} else {
|
|
r_member_type = DataType();
|
|
}
|
|
#else
|
|
r_member_type = DataType();
|
|
#endif
|
|
} else {
|
|
r_member_type = _type_from_property(E->get());
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
#undef IS_USAGE_MEMBER
|
|
|
|
return false;
|
|
}
|
|
|
|
CScriptParser::DataType CScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line, bool p_is_indexing) {
|
|
if (p_base_type && !p_base_type->has_type) {
|
|
return DataType();
|
|
}
|
|
|
|
DataType base_type;
|
|
DataType member_type;
|
|
|
|
if (!p_base_type) {
|
|
base_type.has_type = true;
|
|
base_type.is_constant = true;
|
|
base_type.kind = DataType::CLASS;
|
|
base_type.class_type = current_class;
|
|
} else {
|
|
base_type = DataType(*p_base_type);
|
|
}
|
|
|
|
bool is_const = false;
|
|
if (_get_member_type(base_type, p_identifier, member_type, &is_const)) {
|
|
if (!p_base_type && current_function && current_function->_static && !is_const) {
|
|
_set_error("Can't access member variable (\"" + p_identifier.operator String() + "\") from a static function.", p_line);
|
|
return DataType();
|
|
}
|
|
return member_type;
|
|
}
|
|
|
|
if (p_is_indexing) {
|
|
// Don't look for globals since this is an indexed identifier
|
|
return DataType();
|
|
}
|
|
|
|
if (!p_base_type) {
|
|
// Possibly this is a global, check before failing
|
|
|
|
if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) {
|
|
DataType result;
|
|
result.has_type = true;
|
|
result.is_constant = true;
|
|
result.is_meta_type = true;
|
|
if (Engine::get_singleton()->has_singleton(p_identifier) || Engine::get_singleton()->has_singleton("_" + p_identifier.operator String())) {
|
|
result.is_meta_type = false;
|
|
}
|
|
result.kind = DataType::NATIVE;
|
|
result.native_type = p_identifier;
|
|
return result;
|
|
}
|
|
|
|
ClassNode *outer_class = current_class;
|
|
while (outer_class) {
|
|
if (outer_class->name == p_identifier) {
|
|
DataType result;
|
|
result.has_type = true;
|
|
result.is_constant = true;
|
|
result.is_meta_type = true;
|
|
result.kind = DataType::CLASS;
|
|
result.class_type = outer_class;
|
|
return result;
|
|
}
|
|
if (outer_class->constant_expressions.has(p_identifier)) {
|
|
return outer_class->constant_expressions[p_identifier].type;
|
|
}
|
|
for (int i = 0; i < outer_class->subclasses.size(); i++) {
|
|
if (outer_class->subclasses[i] == current_class) {
|
|
continue;
|
|
}
|
|
if (outer_class->subclasses[i]->name == p_identifier) {
|
|
DataType result;
|
|
result.has_type = true;
|
|
result.is_constant = true;
|
|
result.is_meta_type = true;
|
|
result.kind = DataType::CLASS;
|
|
result.class_type = outer_class->subclasses[i];
|
|
return result;
|
|
}
|
|
}
|
|
outer_class = outer_class->owner;
|
|
}
|
|
|
|
if (ScriptServer::is_global_class(p_identifier)) {
|
|
Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
|
|
if (scr.is_valid()) {
|
|
DataType result;
|
|
result.has_type = true;
|
|
result.script_type = scr;
|
|
result.is_constant = true;
|
|
result.is_meta_type = true;
|
|
Ref<CScript> gds = scr;
|
|
if (gds.is_valid()) {
|
|
if (!gds->is_valid()) {
|
|
_set_error("The class \"" + p_identifier + "\" couldn't be fully loaded (script error or cyclic dependency).");
|
|
return DataType();
|
|
}
|
|
result.kind = DataType::CSCRIPT;
|
|
} else {
|
|
result.kind = DataType::SCRIPT;
|
|
}
|
|
return result;
|
|
}
|
|
_set_error("The class \"" + p_identifier + "\" was found in global scope, but its script couldn't be loaded.");
|
|
return DataType();
|
|
}
|
|
|
|
if (CScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) {
|
|
int idx = CScriptLanguage::get_singleton()->get_global_map()[p_identifier];
|
|
Variant g = CScriptLanguage::get_singleton()->get_global_array()[idx];
|
|
return _type_from_variant(g);
|
|
}
|
|
|
|
if (CScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) {
|
|
Variant g = CScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier];
|
|
return _type_from_variant(g);
|
|
}
|
|
|
|
// Non-tool singletons aren't loaded, check project settings
|
|
List<PropertyInfo> props;
|
|
ProjectSettings::get_singleton()->get_property_list(&props);
|
|
|
|
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
|
|
String s = E->get().name;
|
|
if (!s.begins_with("autoload/")) {
|
|
continue;
|
|
}
|
|
String name = s.get_slice("/", 1);
|
|
if (name == p_identifier) {
|
|
String script = ProjectSettings::get_singleton()->get(s);
|
|
if (script.begins_with("*")) {
|
|
script = script.right(1);
|
|
}
|
|
if (!script.begins_with("res://")) {
|
|
script = "res://" + script;
|
|
}
|
|
Ref<Script> singleton = ResourceLoader::load(script);
|
|
if (singleton.is_valid()) {
|
|
DataType result;
|
|
result.has_type = true;
|
|
result.is_constant = true;
|
|
result.script_type = singleton;
|
|
|
|
Ref<CScript> gds = singleton;
|
|
if (gds.is_valid()) {
|
|
if (!gds->is_valid()) {
|
|
_set_error("Couldn't fully load the singleton script \"" + p_identifier + "\" (possible cyclic reference or parse error).", p_line);
|
|
return DataType();
|
|
}
|
|
result.kind = DataType::CSCRIPT;
|
|
} else {
|
|
result.kind = DataType::SCRIPT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This means looking in the current class, which type is always known
|
|
_set_error("The identifier \"" + p_identifier.operator String() + "\" isn't declared in the current scope.", p_line);
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
{
|
|
DataType tmp_type;
|
|
List<DataType> arg_types;
|
|
int argcount;
|
|
bool _static;
|
|
bool vararg;
|
|
if (_get_function_signature(base_type, p_identifier, tmp_type, arg_types, argcount, _static, vararg)) {
|
|
_add_warning(CScriptWarning::FUNCTION_USED_AS_PROPERTY, p_line, p_identifier.operator String(), base_type.to_string());
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
_mark_line_as_unsafe(p_line);
|
|
return DataType();
|
|
}
|
|
|
|
void CScriptParser::_check_class_level_types(ClassNode *p_class) {
|
|
// Names of internal object properties that we check to avoid overriding them.
|
|
// "__meta__" could also be in here, but since it doesn't really affect object metadata,
|
|
// it is okay to override it on script.
|
|
StringName script_name = CoreStringNames::get_singleton()->_script;
|
|
|
|
_mark_line_as_safe(p_class->line);
|
|
|
|
// Constants
|
|
for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
|
|
ClassNode::Constant &c = E->get();
|
|
_mark_line_as_safe(c.expression->line);
|
|
DataType cont = _resolve_type(c.type, c.expression->line);
|
|
DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line);
|
|
|
|
if (check_types && !_is_type_compatible(cont, expr)) {
|
|
_set_error("The constant value type (" + expr.to_string() + ") isn't compatible with declared type (" + cont.to_string() + ").",
|
|
c.expression->line);
|
|
return;
|
|
}
|
|
|
|
expr.is_constant = true;
|
|
c.type = expr;
|
|
c.expression->set_datatype(expr);
|
|
|
|
DataType tmp;
|
|
const StringName &constant_name = E->key();
|
|
if (constant_name == script_name || _get_member_type(p_class->base_type, constant_name, tmp)) {
|
|
_set_error("The member \"" + String(constant_name) + "\" already exists in a parent class.", c.expression->line);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Function declarations
|
|
for (int i = 0; i < p_class->static_functions.size(); i++) {
|
|
_check_function_types(p_class->static_functions[i]);
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < p_class->functions.size(); i++) {
|
|
_check_function_types(p_class->functions[i]);
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Class variables
|
|
for (int i = 0; i < p_class->variables.size(); i++) {
|
|
ClassNode::Member &v = p_class->variables.write[i];
|
|
|
|
DataType tmp;
|
|
if (v.identifier == script_name || _get_member_type(p_class->base_type, v.identifier, tmp)) {
|
|
_set_error("The member \"" + String(v.identifier) + "\" already exists in a parent class.", v.line);
|
|
return;
|
|
}
|
|
|
|
_mark_line_as_safe(v.line);
|
|
v.data_type = _resolve_type(v.data_type, v.line);
|
|
v.initial_assignment->arguments[0]->set_datatype(v.data_type);
|
|
|
|
if (v.expression) {
|
|
DataType expr_type = _reduce_node_type(v.expression);
|
|
|
|
if (check_types && !_is_type_compatible(v.data_type, expr_type)) {
|
|
// Try supertype test
|
|
if (_is_type_compatible(expr_type, v.data_type)) {
|
|
_mark_line_as_unsafe(v.line);
|
|
} else {
|
|
// Try with implicit conversion
|
|
if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) {
|
|
_set_error("The assigned expression's type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
|
|
v.data_type.to_string() + ").",
|
|
v.line);
|
|
return;
|
|
}
|
|
|
|
// Replace assignment with implicit conversion
|
|
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
|
|
convert->line = v.line;
|
|
convert->function = CScriptFunctions::TYPE_CONVERT;
|
|
|
|
ConstantNode *tgt_type = alloc_node<ConstantNode>();
|
|
tgt_type->line = v.line;
|
|
tgt_type->value = (int)v.data_type.builtin_type;
|
|
|
|
OperatorNode *convert_call = alloc_node<OperatorNode>();
|
|
convert_call->line = v.line;
|
|
convert_call->op = OperatorNode::OP_CALL;
|
|
convert_call->arguments.push_back(convert);
|
|
convert_call->arguments.push_back(v.expression);
|
|
convert_call->arguments.push_back(tgt_type);
|
|
|
|
v.expression = convert_call;
|
|
v.initial_assignment->arguments.write[1] = convert_call;
|
|
}
|
|
}
|
|
|
|
if (v.data_type.infer_type) {
|
|
if (!expr_type.has_type) {
|
|
_set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", v.line);
|
|
return;
|
|
}
|
|
if (expr_type.kind == DataType::BUILTIN && expr_type.builtin_type == Variant::NIL) {
|
|
_set_error("The variable type cannot be inferred because its value is \"null\".", v.line);
|
|
return;
|
|
}
|
|
v.data_type = expr_type;
|
|
v.data_type.is_constant = false;
|
|
}
|
|
}
|
|
|
|
// Check export hint
|
|
if (v.data_type.has_type && v._export.type != Variant::NIL) {
|
|
DataType export_type = _type_from_property(v._export);
|
|
if (!_is_type_compatible(v.data_type, export_type, true)) {
|
|
_set_error("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" +
|
|
v.data_type.to_string() + ").",
|
|
v.line);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Setter and getter
|
|
if (v.setter == StringName() && v.getter == StringName()) {
|
|
continue;
|
|
}
|
|
|
|
bool found_getter = false;
|
|
bool found_setter = false;
|
|
for (int j = 0; j < p_class->functions.size(); j++) {
|
|
if (v.setter == p_class->functions[j]->name) {
|
|
found_setter = true;
|
|
FunctionNode *setter = p_class->functions[j];
|
|
|
|
if (setter->get_required_argument_count() != 1 &&
|
|
!(setter->get_required_argument_count() == 0 && setter->default_values.size() > 0)) {
|
|
_set_error("The setter function needs to receive exactly 1 argument. See \"" + setter->name +
|
|
"()\" definition at line " + itos(setter->line) + ".",
|
|
v.line);
|
|
return;
|
|
}
|
|
if (!_is_type_compatible(v.data_type, setter->argument_types[0])) {
|
|
_set_error("The setter argument's type (" + setter->argument_types[0].to_string() +
|
|
") doesn't match the variable's type (" + v.data_type.to_string() + "). See \"" +
|
|
setter->name + "()\" definition at line " + itos(setter->line) + ".",
|
|
v.line);
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
if (v.getter == p_class->functions[j]->name) {
|
|
found_getter = true;
|
|
FunctionNode *getter = p_class->functions[j];
|
|
|
|
if (getter->get_required_argument_count() != 0) {
|
|
_set_error("The getter function can't receive arguments. See \"" + getter->name +
|
|
"()\" definition at line " + itos(getter->line) + ".",
|
|
v.line);
|
|
return;
|
|
}
|
|
if (!_is_type_compatible(v.data_type, getter->get_datatype())) {
|
|
_set_error("The getter return type (" + getter->get_datatype().to_string() +
|
|
") doesn't match the variable's type (" + v.data_type.to_string() +
|
|
"). See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".",
|
|
v.line);
|
|
return;
|
|
}
|
|
}
|
|
if (found_getter && found_setter) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) {
|
|
continue;
|
|
}
|
|
|
|
// Check for static functions
|
|
for (int j = 0; j < p_class->static_functions.size(); j++) {
|
|
if (v.setter == p_class->static_functions[j]->name) {
|
|
FunctionNode *setter = p_class->static_functions[j];
|
|
_set_error("The setter can't be a static function. See \"" + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line);
|
|
return;
|
|
}
|
|
if (v.getter == p_class->static_functions[j]->name) {
|
|
FunctionNode *getter = p_class->static_functions[j];
|
|
_set_error("The getter can't be a static function. See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!found_setter && v.setter != StringName()) {
|
|
_set_error("The setter function isn't defined.", v.line);
|
|
return;
|
|
}
|
|
|
|
if (!found_getter && v.getter != StringName()) {
|
|
_set_error("The getter function isn't defined.", v.line);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Signals
|
|
DataType base = p_class->base_type;
|
|
|
|
while (base.kind == DataType::CLASS) {
|
|
ClassNode *base_class = base.class_type;
|
|
for (int i = 0; i < p_class->_signals.size(); i++) {
|
|
for (int j = 0; j < base_class->_signals.size(); j++) {
|
|
if (p_class->_signals[i].name == base_class->_signals[j].name) {
|
|
_set_error("The signal \"" + p_class->_signals[i].name + "\" already exists in a parent class.", p_class->_signals[i].line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
base = base_class->base_type;
|
|
}
|
|
|
|
StringName native;
|
|
if (base.kind == DataType::CSCRIPT || base.kind == DataType::SCRIPT) {
|
|
Ref<Script> scr = base.script_type;
|
|
if (scr.is_valid() && scr->is_valid()) {
|
|
native = scr->get_instance_base_type();
|
|
for (int i = 0; i < p_class->_signals.size(); i++) {
|
|
if (scr->has_script_signal(p_class->_signals[i].name)) {
|
|
_set_error("The signal \"" + p_class->_signals[i].name + "\" already exists in a parent class.", p_class->_signals[i].line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
} else if (base.kind == DataType::NATIVE) {
|
|
native = base.native_type;
|
|
}
|
|
|
|
if (native != StringName()) {
|
|
for (int i = 0; i < p_class->_signals.size(); i++) {
|
|
if (ClassDB::has_signal(native, p_class->_signals[i].name)) {
|
|
_set_error("The signal \"" + p_class->_signals[i].name + "\" already exists in a parent class.", p_class->_signals[i].line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inner classes
|
|
for (int i = 0; i < p_class->subclasses.size(); i++) {
|
|
current_class = p_class->subclasses[i];
|
|
_check_class_level_types(current_class);
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
current_class = p_class;
|
|
}
|
|
}
|
|
|
|
void CScriptParser::_check_function_types(FunctionNode *p_function) {
|
|
p_function->return_type = _resolve_type(p_function->return_type, p_function->line);
|
|
|
|
// Arguments
|
|
int defaults_ofs = p_function->arguments.size() - p_function->default_values.size();
|
|
for (int i = 0; i < p_function->arguments.size(); i++) {
|
|
if (i < defaults_ofs) {
|
|
p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
|
|
} else {
|
|
if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) {
|
|
_set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column);
|
|
return;
|
|
}
|
|
|
|
OperatorNode *op = static_cast<OperatorNode *>(p_function->default_values[i - defaults_ofs]);
|
|
|
|
if (op->op != OperatorNode::OP_ASSIGN || op->arguments.size() != 2) {
|
|
_set_error("Parser bug: invalid argument default value operation.", p_function->line);
|
|
return;
|
|
}
|
|
|
|
DataType def_type = _reduce_node_type(op->arguments[1]);
|
|
|
|
if (p_function->argument_types[i].infer_type) {
|
|
def_type.is_constant = false;
|
|
p_function->argument_types.write[i] = def_type;
|
|
} else {
|
|
p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
|
|
|
|
if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
|
|
String arg_name = p_function->arguments[i];
|
|
_set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
|
|
arg_name + "' (" + p_function->argument_types[i].to_string() + ").",
|
|
p_function->line);
|
|
}
|
|
}
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
if (p_function->arguments_usage[i] == 0 && !p_function->arguments[i].operator String().begins_with("_")) {
|
|
_add_warning(CScriptWarning::UNUSED_ARGUMENT, p_function->line, p_function->name, p_function->arguments[i].operator String());
|
|
}
|
|
for (int j = 0; j < current_class->variables.size(); j++) {
|
|
if (current_class->variables[j].identifier == p_function->arguments[i]) {
|
|
_add_warning(CScriptWarning::SHADOWED_VARIABLE, p_function->line, p_function->arguments[i], itos(current_class->variables[j].line));
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
|
|
if (!(p_function->name == "_init")) {
|
|
// Signature for the initializer may vary
|
|
#ifdef DEBUG_ENABLED
|
|
DataType return_type;
|
|
List<DataType> arg_types;
|
|
int default_arg_count = 0;
|
|
bool _static = false;
|
|
bool vararg = false;
|
|
|
|
DataType base_type = current_class->base_type;
|
|
if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) {
|
|
bool valid = _static == p_function->_static;
|
|
valid = valid && return_type == p_function->return_type;
|
|
int argsize_diff = p_function->arguments.size() - arg_types.size();
|
|
valid = valid && argsize_diff >= 0;
|
|
valid = valid && p_function->default_values.size() >= default_arg_count + argsize_diff;
|
|
int i = 0;
|
|
for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) {
|
|
valid = valid && E->get() == p_function->argument_types[i++];
|
|
}
|
|
|
|
if (!valid) {
|
|
String parent_signature = return_type.has_type ? return_type.to_string() : "Variant";
|
|
if (parent_signature == "null") {
|
|
parent_signature = "void";
|
|
}
|
|
parent_signature += " " + p_function->name + "(";
|
|
if (arg_types.size()) {
|
|
int j = 0;
|
|
for (List<DataType>::Element *E = arg_types.front(); E; E = E->next()) {
|
|
if (E != arg_types.front()) {
|
|
parent_signature += ", ";
|
|
}
|
|
String arg = E->get().to_string();
|
|
if (arg == "null" || arg == "var") {
|
|
arg = "Variant";
|
|
}
|
|
parent_signature += arg;
|
|
if (j == arg_types.size() - default_arg_count) {
|
|
parent_signature += "=default";
|
|
}
|
|
|
|
j++;
|
|
}
|
|
}
|
|
parent_signature += ")";
|
|
_set_error("The function signature doesn't match the parent. Parent signature is: \"" + parent_signature + "\".", p_function->line);
|
|
return;
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
} else {
|
|
if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
|
|
_set_error("The constructor can't return a value.", p_function->line);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
|
|
if (!p_function->body->has_return) {
|
|
_set_error("A non-void function must return a value in all possible paths.", p_function->line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CScriptParser::_check_class_blocks_types(ClassNode *p_class) {
|
|
// Function blocks
|
|
for (int i = 0; i < p_class->static_functions.size(); i++) {
|
|
current_function = p_class->static_functions[i];
|
|
current_block = current_function->body;
|
|
_mark_line_as_safe(current_function->line);
|
|
_check_block_types(current_block);
|
|
current_block = nullptr;
|
|
current_function = nullptr;
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < p_class->functions.size(); i++) {
|
|
current_function = p_class->functions[i];
|
|
current_block = current_function->body;
|
|
_mark_line_as_safe(current_function->line);
|
|
_check_block_types(current_block);
|
|
current_block = nullptr;
|
|
current_function = nullptr;
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
// Warnings
|
|
for (int i = 0; i < p_class->variables.size(); i++) {
|
|
if (p_class->variables[i].usages == 0) {
|
|
_add_warning(CScriptWarning::UNUSED_CLASS_VARIABLE, p_class->variables[i].line, p_class->variables[i].identifier);
|
|
}
|
|
}
|
|
for (int i = 0; i < p_class->_signals.size(); i++) {
|
|
if (p_class->_signals[i].emissions == 0) {
|
|
_add_warning(CScriptWarning::UNUSED_SIGNAL, p_class->_signals[i].line, p_class->_signals[i].name);
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
// Inner classes
|
|
for (int i = 0; i < p_class->subclasses.size(); i++) {
|
|
current_class = p_class->subclasses[i];
|
|
_check_class_blocks_types(current_class);
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
current_class = p_class;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
static String _find_function_name(const CScriptParser::OperatorNode *p_call) {
|
|
switch (p_call->arguments[0]->type) {
|
|
case CScriptParser::Node::TYPE_TYPE: {
|
|
return Variant::get_type_name(static_cast<CScriptParser::TypeNode *>(p_call->arguments[0])->vtype);
|
|
} break;
|
|
case CScriptParser::Node::TYPE_BUILT_IN_FUNCTION: {
|
|
return CScriptFunctions::get_func_name(static_cast<CScriptParser::BuiltInFunctionNode *>(p_call->arguments[0])->function);
|
|
} break;
|
|
default: {
|
|
int id_index = p_call->op == CScriptParser::OperatorNode::OP_PARENT_CALL ? 0 : 1;
|
|
if (p_call->arguments.size() > id_index && p_call->arguments[id_index]->type == CScriptParser::Node::TYPE_IDENTIFIER) {
|
|
return static_cast<CScriptParser::IdentifierNode *>(p_call->arguments[id_index])->name;
|
|
}
|
|
} break;
|
|
}
|
|
return String();
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
void CScriptParser::_check_block_types(BlockNode *p_block) {
|
|
Node *last_var_assign = nullptr;
|
|
|
|
// Check each statement
|
|
for (int z = 0; z < p_block->statements.size(); z++) {
|
|
Node *statement = p_block->statements[z];
|
|
switch (statement->type) {
|
|
case Node::TYPE_NEWLINE:
|
|
case Node::TYPE_BREAKPOINT: {
|
|
// Nothing to do
|
|
} break;
|
|
case Node::TYPE_ASSERT: {
|
|
AssertNode *an = static_cast<AssertNode *>(statement);
|
|
_mark_line_as_safe(an->line);
|
|
_reduce_node_type(an->condition);
|
|
_reduce_node_type(an->message);
|
|
} break;
|
|
case Node::TYPE_LOCAL_VAR: {
|
|
LocalVarNode *lv = static_cast<LocalVarNode *>(statement);
|
|
lv->datatype = _resolve_type(lv->datatype, lv->line);
|
|
_mark_line_as_safe(lv->line);
|
|
|
|
last_var_assign = lv->assign;
|
|
if (lv->assign) {
|
|
lv->assign_op->arguments[0]->set_datatype(lv->datatype);
|
|
DataType assign_type = _reduce_node_type(lv->assign);
|
|
#ifdef DEBUG_ENABLED
|
|
if (assign_type.has_type && assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) {
|
|
if (lv->assign->type == Node::TYPE_OPERATOR) {
|
|
OperatorNode *call = static_cast<OperatorNode *>(lv->assign);
|
|
if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) {
|
|
_add_warning(CScriptWarning::VOID_ASSIGNMENT, lv->line, _find_function_name(call));
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < current_class->variables.size(); i++) {
|
|
if (current_class->variables[i].identifier == lv->name) {
|
|
_add_warning(CScriptWarning::SHADOWED_VARIABLE, lv->line, lv->name, itos(current_class->variables[i].line));
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
if (!_is_type_compatible(lv->datatype, assign_type)) {
|
|
// Try supertype test
|
|
if (_is_type_compatible(assign_type, lv->datatype)) {
|
|
_mark_line_as_unsafe(lv->line);
|
|
} else {
|
|
// Try implicit conversion
|
|
if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
|
|
_set_error("The assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
|
|
lv->datatype.to_string() + ").",
|
|
lv->line);
|
|
return;
|
|
}
|
|
// Replace assignment with implicit conversion
|
|
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
|
|
convert->line = lv->line;
|
|
convert->function = CScriptFunctions::TYPE_CONVERT;
|
|
|
|
ConstantNode *tgt_type = alloc_node<ConstantNode>();
|
|
tgt_type->line = lv->line;
|
|
tgt_type->value = (int)lv->datatype.builtin_type;
|
|
|
|
OperatorNode *convert_call = alloc_node<OperatorNode>();
|
|
convert_call->line = lv->line;
|
|
convert_call->op = OperatorNode::OP_CALL;
|
|
convert_call->arguments.push_back(convert);
|
|
convert_call->arguments.push_back(lv->assign);
|
|
convert_call->arguments.push_back(tgt_type);
|
|
|
|
lv->assign = convert_call;
|
|
lv->assign_op->arguments.write[1] = convert_call;
|
|
#ifdef DEBUG_ENABLED
|
|
if (lv->datatype.builtin_type == Variant::INT && assign_type.builtin_type == Variant::REAL) {
|
|
_add_warning(CScriptWarning::NARROWING_CONVERSION, lv->line);
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
}
|
|
if (lv->datatype.infer_type) {
|
|
if (!assign_type.has_type) {
|
|
_set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", lv->line);
|
|
return;
|
|
}
|
|
if (assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) {
|
|
_set_error("The variable type cannot be inferred because its value is \"null\".", lv->line);
|
|
return;
|
|
}
|
|
lv->datatype = assign_type;
|
|
lv->datatype.is_constant = false;
|
|
}
|
|
if (lv->datatype.has_type && !assign_type.has_type) {
|
|
_mark_line_as_unsafe(lv->line);
|
|
}
|
|
}
|
|
} break;
|
|
case Node::TYPE_OPERATOR: {
|
|
OperatorNode *op = static_cast<OperatorNode *>(statement);
|
|
|
|
switch (op->op) {
|
|
case OperatorNode::OP_ASSIGN:
|
|
case OperatorNode::OP_ASSIGN_ADD:
|
|
case OperatorNode::OP_ASSIGN_SUB:
|
|
case OperatorNode::OP_ASSIGN_MUL:
|
|
case OperatorNode::OP_ASSIGN_DIV:
|
|
case OperatorNode::OP_ASSIGN_MOD:
|
|
case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
|
|
case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
|
|
case OperatorNode::OP_ASSIGN_BIT_AND:
|
|
case OperatorNode::OP_ASSIGN_BIT_OR:
|
|
case OperatorNode::OP_ASSIGN_BIT_XOR: {
|
|
if (op->arguments.size() < 2) {
|
|
_set_error("Parser bug: operation without enough arguments.", op->line, op->column);
|
|
return;
|
|
}
|
|
|
|
if (op->arguments[1] == last_var_assign) {
|
|
// Assignment was already checked
|
|
break;
|
|
}
|
|
|
|
_mark_line_as_safe(op->line);
|
|
|
|
DataType lh_type = _reduce_node_type(op->arguments[0]);
|
|
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
|
|
if (check_types) {
|
|
if (!lh_type.has_type) {
|
|
if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
|
|
_mark_line_as_unsafe(op->line);
|
|
}
|
|
}
|
|
if (lh_type.is_constant) {
|
|
_set_error("Can't assign a new value to a constant.", op->line);
|
|
return;
|
|
}
|
|
}
|
|
|
|
DataType rh_type;
|
|
if (op->op != OperatorNode::OP_ASSIGN) {
|
|
// Validate operation
|
|
DataType arg_type = _reduce_node_type(op->arguments[1]);
|
|
if (!arg_type.has_type) {
|
|
_mark_line_as_unsafe(op->line);
|
|
break;
|
|
}
|
|
|
|
Variant::Operator oper = _get_variant_operation(op->op);
|
|
bool valid = false;
|
|
rh_type = _get_operation_type(oper, lh_type, arg_type, valid);
|
|
|
|
if (check_types && !valid) {
|
|
_set_error("Invalid operand types (\"" + lh_type.to_string() + "\" and \"" + arg_type.to_string() +
|
|
"\") to assignment operator \"" + Variant::get_operator_name(oper) + "\".",
|
|
op->line);
|
|
return;
|
|
}
|
|
} else {
|
|
rh_type = _reduce_node_type(op->arguments[1]);
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
if (rh_type.has_type && rh_type.kind == DataType::BUILTIN && rh_type.builtin_type == Variant::NIL) {
|
|
if (op->arguments[1]->type == Node::TYPE_OPERATOR) {
|
|
OperatorNode *call = static_cast<OperatorNode *>(op->arguments[1]);
|
|
if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) {
|
|
_add_warning(CScriptWarning::VOID_ASSIGNMENT, op->line, _find_function_name(call));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // DEBUG_ENABLED
|
|
bool type_match = lh_type.has_type && rh_type.has_type;
|
|
if (check_types && !_is_type_compatible(lh_type, rh_type)) {
|
|
type_match = false;
|
|
|
|
// Try supertype test
|
|
if (_is_type_compatible(rh_type, lh_type)) {
|
|
_mark_line_as_unsafe(op->line);
|
|
} else {
|
|
// Try implicit conversion
|
|
if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
|
|
_set_error("The assigned value's type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
|
|
lh_type.to_string() + ").",
|
|
op->line);
|
|
return;
|
|
}
|
|
if (op->op == OperatorNode::OP_ASSIGN) {
|
|
// Replace assignment with implicit conversion
|
|
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
|
|
convert->line = op->line;
|
|
convert->function = CScriptFunctions::TYPE_CONVERT;
|
|
|
|
ConstantNode *tgt_type = alloc_node<ConstantNode>();
|
|
tgt_type->line = op->line;
|
|
tgt_type->value = (int)lh_type.builtin_type;
|
|
|
|
OperatorNode *convert_call = alloc_node<OperatorNode>();
|
|
convert_call->line = op->line;
|
|
convert_call->op = OperatorNode::OP_CALL;
|
|
convert_call->arguments.push_back(convert);
|
|
convert_call->arguments.push_back(op->arguments[1]);
|
|
convert_call->arguments.push_back(tgt_type);
|
|
|
|
op->arguments.write[1] = convert_call;
|
|
|
|
type_match = true; // Since we are converting, the type is matching
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
if (lh_type.builtin_type == Variant::INT && rh_type.builtin_type == Variant::REAL) {
|
|
_add_warning(CScriptWarning::NARROWING_CONVERSION, op->line);
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
}
|
|
#ifdef DEBUG_ENABLED
|
|
if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) {
|
|
_mark_line_as_unsafe(op->line);
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
op->datatype.has_type = type_match;
|
|
} break;
|
|
case OperatorNode::OP_CALL:
|
|
case OperatorNode::OP_PARENT_CALL: {
|
|
_mark_line_as_safe(op->line);
|
|
DataType func_type = _reduce_function_call_type(op);
|
|
#ifdef DEBUG_ENABLED
|
|
if (func_type.has_type && (func_type.kind != DataType::BUILTIN || func_type.builtin_type != Variant::NIL)) {
|
|
// Figure out function name for warning
|
|
String func_name = _find_function_name(op);
|
|
if (func_name.empty()) {
|
|
func_name = "<undetected name>";
|
|
}
|
|
_add_warning(CScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name);
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
} break;
|
|
default: {
|
|
_mark_line_as_safe(op->line);
|
|
_reduce_node_type(op); // Test for safety anyway
|
|
#ifdef DEBUG_ENABLED
|
|
if (op->op == OperatorNode::OP_TERNARY_IF) {
|
|
_add_warning(CScriptWarning::STANDALONE_TERNARY, statement->line);
|
|
} else {
|
|
_add_warning(CScriptWarning::STANDALONE_EXPRESSION, statement->line);
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
}
|
|
} break;
|
|
case Node::TYPE_CONTROL_FLOW: {
|
|
ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement);
|
|
_mark_line_as_safe(cf->line);
|
|
|
|
switch (cf->cf_type) {
|
|
case ControlFlowNode::CF_RETURN: {
|
|
DataType function_type = current_function->get_datatype();
|
|
|
|
DataType ret_type;
|
|
if (cf->arguments.size() > 0) {
|
|
ret_type = _reduce_node_type(cf->arguments[0]);
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!function_type.has_type) {
|
|
break;
|
|
}
|
|
|
|
if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) {
|
|
// Return void, should not have arguments
|
|
if (cf->arguments.size() > 0) {
|
|
_set_error("A void function cannot return a value.", cf->line, cf->column);
|
|
return;
|
|
}
|
|
} else {
|
|
// Return something, cannot be empty
|
|
if (cf->arguments.size() == 0) {
|
|
_set_error("A non-void function must return a value.", cf->line, cf->column);
|
|
return;
|
|
}
|
|
|
|
if (!_is_type_compatible(function_type, ret_type)) {
|
|
_set_error("The returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" +
|
|
function_type.to_string() + ").",
|
|
cf->line, cf->column);
|
|
return;
|
|
}
|
|
}
|
|
} break;
|
|
case ControlFlowNode::CF_MATCH: {
|
|
MatchNode *match_node = cf->match;
|
|
_transform_match_statment(match_node);
|
|
} break;
|
|
default: {
|
|
if (cf->body_else) {
|
|
_mark_line_as_safe(cf->body_else->line);
|
|
}
|
|
for (int i = 0; i < cf->arguments.size(); i++) {
|
|
_reduce_node_type(cf->arguments[i]);
|
|
}
|
|
} break;
|
|
}
|
|
} break;
|
|
case Node::TYPE_CONSTANT: {
|
|
ConstantNode *cn = static_cast<ConstantNode *>(statement);
|
|
// Strings are fine since they can be multiline comments
|
|
if (cn->value.get_type() == Variant::STRING) {
|
|
break;
|
|
}
|
|
FALLTHROUGH;
|
|
}
|
|
default: {
|
|
_mark_line_as_safe(statement->line);
|
|
_reduce_node_type(statement); // Test for safety anyway
|
|
#ifdef DEBUG_ENABLED
|
|
_add_warning(CScriptWarning::STANDALONE_EXPRESSION, statement->line);
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse sub blocks
|
|
for (int i = 0; i < p_block->sub_blocks.size(); i++) {
|
|
current_block = p_block->sub_blocks[i];
|
|
_check_block_types(current_block);
|
|
current_block = p_block;
|
|
if (error_set) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
// Warnings check
|
|
for (Map<StringName, LocalVarNode *>::Element *E = p_block->variables.front(); E; E = E->next()) {
|
|
LocalVarNode *lv = E->get();
|
|
if (!lv->name.operator String().begins_with("_")) {
|
|
if (lv->usages == 0) {
|
|
_add_warning(CScriptWarning::UNUSED_VARIABLE, lv->line, lv->name);
|
|
} else if (lv->assignments == 0) {
|
|
_add_warning(CScriptWarning::UNASSIGNED_VARIABLE, lv->line, lv->name);
|
|
}
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
|
|
void CScriptParser::_set_error(const String &p_error, int p_line, int p_column) {
|
|
if (error_set) {
|
|
return; //allow no further errors
|
|
}
|
|
|
|
error = p_error;
|
|
error_line = p_line < 0 ? tokenizer->get_token_line() : p_line;
|
|
error_column = p_column < 0 ? tokenizer->get_token_column() : p_column;
|
|
error_set = true;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
void CScriptParser::_add_warning(int p_code, int p_line, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) {
|
|
Vector<String> symbols;
|
|
if (!p_symbol1.empty()) {
|
|
symbols.push_back(p_symbol1);
|
|
}
|
|
if (!p_symbol2.empty()) {
|
|
symbols.push_back(p_symbol2);
|
|
}
|
|
if (!p_symbol3.empty()) {
|
|
symbols.push_back(p_symbol3);
|
|
}
|
|
if (!p_symbol4.empty()) {
|
|
symbols.push_back(p_symbol4);
|
|
}
|
|
_add_warning(p_code, p_line, symbols);
|
|
}
|
|
|
|
void CScriptParser::_add_warning(int p_code, int p_line, const Vector<String> &p_symbols) {
|
|
if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && base_path.begins_with("res://addons/")) {
|
|
return;
|
|
}
|
|
if (tokenizer->is_ignoring_warnings() || !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) {
|
|
return;
|
|
}
|
|
String warn_name = CScriptWarning::get_name_from_code((CScriptWarning::Code)p_code).to_lower();
|
|
if (tokenizer->get_warning_global_skips().has(warn_name)) {
|
|
return;
|
|
}
|
|
if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) {
|
|
return;
|
|
}
|
|
|
|
CScriptWarning warn;
|
|
warn.code = (CScriptWarning::Code)p_code;
|
|
warn.symbols = p_symbols;
|
|
warn.line = p_line == -1 ? tokenizer->get_token_line() : p_line;
|
|
|
|
List<CScriptWarning>::Element *before = nullptr;
|
|
for (List<CScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {
|
|
if (E->get().line > warn.line) {
|
|
break;
|
|
}
|
|
before = E;
|
|
}
|
|
if (before) {
|
|
warnings.insert_after(before, warn);
|
|
} else {
|
|
warnings.push_front(warn);
|
|
}
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
String CScriptParser::get_error() const {
|
|
return error;
|
|
}
|
|
|
|
int CScriptParser::get_error_line() const {
|
|
return error_line;
|
|
}
|
|
int CScriptParser::get_error_column() const {
|
|
return error_column;
|
|
}
|
|
|
|
bool CScriptParser::has_error() const {
|
|
return error_set;
|
|
}
|
|
|
|
Error CScriptParser::_parse(const String &p_base_path) {
|
|
base_path = p_base_path;
|
|
|
|
//assume class
|
|
ClassNode *main_class = alloc_node<ClassNode>();
|
|
main_class->initializer = alloc_node<BlockNode>();
|
|
main_class->initializer->parent_class = main_class;
|
|
main_class->ready = alloc_node<BlockNode>();
|
|
main_class->ready->parent_class = main_class;
|
|
current_class = main_class;
|
|
|
|
_parse_class(main_class);
|
|
|
|
if (tokenizer->get_token() == CScriptTokenizer::TK_ERROR) {
|
|
error_set = false;
|
|
_set_error("Parse error: " + tokenizer->get_token_error());
|
|
}
|
|
|
|
bool for_completion_error_set = false;
|
|
if (error_set && for_completion) {
|
|
for_completion_error_set = true;
|
|
error_set = false;
|
|
}
|
|
|
|
if (error_set) {
|
|
return ERR_PARSE_ERROR;
|
|
}
|
|
|
|
if (dependencies_only) {
|
|
return OK;
|
|
}
|
|
|
|
_determine_inheritance(main_class);
|
|
|
|
if (error_set) {
|
|
return ERR_PARSE_ERROR;
|
|
}
|
|
|
|
current_class = main_class;
|
|
current_function = nullptr;
|
|
current_block = nullptr;
|
|
|
|
if (for_completion) {
|
|
check_types = false;
|
|
}
|
|
|
|
// Resolve all class-level stuff before getting into function blocks
|
|
_check_class_level_types(main_class);
|
|
|
|
if (error_set) {
|
|
return ERR_PARSE_ERROR;
|
|
}
|
|
|
|
// Resolve the function blocks
|
|
_check_class_blocks_types(main_class);
|
|
|
|
if (for_completion_error_set) {
|
|
error_set = true;
|
|
}
|
|
|
|
if (error_set) {
|
|
return ERR_PARSE_ERROR;
|
|
}
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
// Resolve warning ignores
|
|
Vector<Pair<int, String>> warning_skips = tokenizer->get_warning_skips();
|
|
bool warning_is_error = GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors").booleanize();
|
|
for (List<CScriptWarning>::Element *E = warnings.front(); E;) {
|
|
CScriptWarning &w = E->get();
|
|
int skip_index = -1;
|
|
for (int i = 0; i < warning_skips.size(); i++) {
|
|
if (warning_skips[i].first > w.line) {
|
|
break;
|
|
}
|
|
skip_index = i;
|
|
}
|
|
List<CScriptWarning>::Element *next = E->next();
|
|
bool erase = false;
|
|
if (skip_index != -1) {
|
|
if (warning_skips[skip_index].second == CScriptWarning::get_name_from_code(w.code).to_lower()) {
|
|
erase = true;
|
|
}
|
|
warning_skips.remove(skip_index);
|
|
}
|
|
if (erase) {
|
|
warnings.erase(E);
|
|
} else if (warning_is_error) {
|
|
_set_error(w.get_message() + " (warning treated as error)", w.line);
|
|
return ERR_PARSE_ERROR;
|
|
}
|
|
E = next;
|
|
}
|
|
#endif // DEBUG_ENABLED
|
|
|
|
return OK;
|
|
}
|
|
|
|
Error CScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path, const String &p_self_path) {
|
|
clear();
|
|
|
|
self_path = p_self_path;
|
|
CScriptTokenizerBuffer *tb = memnew(CScriptTokenizerBuffer);
|
|
tb->set_code_buffer(p_bytecode);
|
|
tokenizer = tb;
|
|
Error ret = _parse(p_base_path);
|
|
memdelete(tb);
|
|
tokenizer = nullptr;
|
|
return ret;
|
|
}
|
|
|
|
Error CScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines, bool p_dependencies_only) {
|
|
clear();
|
|
|
|
self_path = p_self_path;
|
|
CScriptTokenizerText *tt = memnew(CScriptTokenizerText);
|
|
tt->set_code(p_code);
|
|
|
|
validating = p_just_validate;
|
|
for_completion = p_for_completion;
|
|
dependencies_only = p_dependencies_only;
|
|
#ifdef DEBUG_ENABLED
|
|
safe_lines = r_safe_lines;
|
|
#endif // DEBUG_ENABLED
|
|
tokenizer = tt;
|
|
Error ret = _parse(p_base_path);
|
|
memdelete(tt);
|
|
tokenizer = nullptr;
|
|
return ret;
|
|
}
|
|
|
|
bool CScriptParser::is_tool_script() const {
|
|
return (head && head->type == Node::TYPE_CLASS && static_cast<const ClassNode *>(head)->tool);
|
|
}
|
|
|
|
const CScriptParser::Node *CScriptParser::get_parse_tree() const {
|
|
return head;
|
|
}
|
|
|
|
void CScriptParser::clear() {
|
|
while (list) {
|
|
Node *l = list;
|
|
list = list->next;
|
|
memdelete(l);
|
|
}
|
|
|
|
head = nullptr;
|
|
list = nullptr;
|
|
|
|
completion_type = COMPLETION_NONE;
|
|
completion_node = nullptr;
|
|
completion_class = nullptr;
|
|
completion_function = nullptr;
|
|
completion_block = nullptr;
|
|
current_block = nullptr;
|
|
current_class = nullptr;
|
|
|
|
completion_found = false;
|
|
|
|
current_function = nullptr;
|
|
|
|
validating = false;
|
|
for_completion = false;
|
|
error_set = false;
|
|
indent_level.clear();
|
|
indent_level.push_back(IndentLevel(0, 0));
|
|
error_line = 0;
|
|
error_column = 0;
|
|
pending_newline = -1;
|
|
parenthesis = 0;
|
|
current_export.type = Variant::NIL;
|
|
check_types = true;
|
|
dependencies_only = false;
|
|
dependencies.clear();
|
|
error = "";
|
|
#ifdef DEBUG_ENABLED
|
|
safe_lines = nullptr;
|
|
#endif // DEBUG_ENABLED
|
|
}
|
|
|
|
CScriptParser::CompletionType CScriptParser::get_completion_type() {
|
|
return completion_type;
|
|
}
|
|
|
|
StringName CScriptParser::get_completion_cursor() {
|
|
return completion_cursor;
|
|
}
|
|
|
|
int CScriptParser::get_completion_line() {
|
|
return completion_line;
|
|
}
|
|
|
|
Variant::Type CScriptParser::get_completion_built_in_constant() {
|
|
return completion_built_in_constant;
|
|
}
|
|
|
|
CScriptParser::Node *CScriptParser::get_completion_node() {
|
|
return completion_node;
|
|
}
|
|
|
|
CScriptParser::BlockNode *CScriptParser::get_completion_block() {
|
|
return completion_block;
|
|
}
|
|
|
|
CScriptParser::ClassNode *CScriptParser::get_completion_class() {
|
|
return completion_class;
|
|
}
|
|
|
|
CScriptParser::FunctionNode *CScriptParser::get_completion_function() {
|
|
return completion_function;
|
|
}
|
|
|
|
int CScriptParser::get_completion_argument_index() {
|
|
return completion_argument;
|
|
}
|
|
|
|
int CScriptParser::get_completion_identifier_is_function() {
|
|
return completion_ident_is_call;
|
|
}
|
|
|
|
CScriptParser::CScriptParser() {
|
|
head = nullptr;
|
|
list = nullptr;
|
|
tokenizer = nullptr;
|
|
pending_newline = -1;
|
|
clear();
|
|
}
|
|
|
|
CScriptParser::~CScriptParser() {
|
|
clear();
|
|
}
|