57654d4b95
The initial version of the pattern matcher in GDScript does not allow matching on nested identifiers, only one identifiers available in the current scope. With the introduction of enums to GDScript that's a huge missing feature. This commit makes the parser accept indexed constants and variables to properly support enums.
4408 lines
126 KiB
C++
4408 lines
126 KiB
C++
/*************************************************************************/
|
|
/* gd_parser.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* http://www.godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2017 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 "gd_parser.h"
|
|
#include "gd_script.h"
|
|
#include "io/resource_loader.h"
|
|
#include "os/file_access.h"
|
|
#include "print_string.h"
|
|
#include "script_language.h"
|
|
|
|
template <class T>
|
|
T *GDParser::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;
|
|
}
|
|
|
|
bool GDParser::_end_statement() {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_SEMICOLON) {
|
|
tokenizer->advance();
|
|
return true; //handle next
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_NEWLINE || tokenizer->get_token() == GDTokenizer::TK_EOF) {
|
|
return true; //will be handled properly
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GDParser::_enter_indent_block(BlockNode *p_block) {
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::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() != GDTokenizer::TK_NEWLINE) {
|
|
|
|
// be more python-like
|
|
int current = tab_level.back()->get();
|
|
tab_level.push_back(current);
|
|
return true;
|
|
//_set_error("newline expected after ':'.");
|
|
//return false;
|
|
}
|
|
|
|
while (true) {
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_NEWLINE) {
|
|
|
|
return false; //wtf
|
|
} else if (tokenizer->get_token(1) != GDTokenizer::TK_NEWLINE) {
|
|
|
|
int indent = tokenizer->get_token_line_indent();
|
|
int current = tab_level.back()->get();
|
|
if (indent <= current) {
|
|
print_line("current: " + itos(current) + " indent: " + itos(indent));
|
|
print_line("less than current");
|
|
return false;
|
|
}
|
|
|
|
tab_level.push_back(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 GDParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete) {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
tokenizer->advance();
|
|
} else {
|
|
|
|
parenthesis++;
|
|
int argidx = 0;
|
|
|
|
while (true) {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_CURSOR) {
|
|
_make_completable_call(argidx);
|
|
completion_node = p_parent;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING && tokenizer->get_token(1) == GDTokenizer::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);
|
|
if (!arg)
|
|
return false;
|
|
|
|
p_args.push_back(arg);
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
|
|
if (tokenizer->get_token(1) == GDTokenizer::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 GDParser::_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 GDParser::_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() == GDTokenizer::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() == GDTokenizer::TK_PARENTHESIS_OPEN) {
|
|
completion_ident_is_call = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
GDParser::Node *GDParser::_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 = NULL;
|
|
|
|
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() == GDTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::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 NULL;
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
|
|
_set_error("Expected ')' in expression");
|
|
return NULL;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
expr = subexpr;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_DOLLAR) {
|
|
tokenizer->advance();
|
|
|
|
String path;
|
|
|
|
bool need_identifier = true;
|
|
bool done = false;
|
|
|
|
while (!done) {
|
|
|
|
switch (tokenizer->get_token()) {
|
|
case GDTokenizer::TK_CURSOR: {
|
|
completion_cursor = StringName();
|
|
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 GDTokenizer::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 NULL;
|
|
}
|
|
|
|
path += String(tokenizer->get_token_constant());
|
|
tokenizer->advance();
|
|
need_identifier = false;
|
|
|
|
} break;
|
|
case GDTokenizer::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 NULL;
|
|
}
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_CALL;
|
|
|
|
op->arguments.push_back(alloc_node<SelfNode>());
|
|
|
|
IdentifierNode *funcname = alloc_node<IdentifierNode>();
|
|
funcname->name = "get_node";
|
|
|
|
op->arguments.push_back(funcname);
|
|
|
|
ConstantNode *nodepath = alloc_node<ConstantNode>();
|
|
nodepath->value = NodePath(StringName(path));
|
|
op->arguments.push_back(nodepath);
|
|
|
|
expr = op;
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CURSOR) {
|
|
tokenizer->advance();
|
|
continue; //no point in cursor in the middle of expression
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CONSTANT) {
|
|
|
|
//constant defined by tokenizer
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = tokenizer->get_token_constant();
|
|
tokenizer->advance();
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CONST_PI) {
|
|
|
|
//constant defined by tokenizer
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = Math_PI;
|
|
tokenizer->advance();
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CONST_INF) {
|
|
|
|
//constant defined by tokenizer
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = Math_INF;
|
|
tokenizer->advance();
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CONST_NAN) {
|
|
|
|
//constant defined by tokenizer
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = Math_NAN;
|
|
tokenizer->advance();
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_PR_PRELOAD) {
|
|
|
|
//constant defined by tokenizer
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_OPEN) {
|
|
_set_error("Expected '(' after 'preload'");
|
|
return NULL;
|
|
}
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::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;
|
|
|
|
Node *subexpr = _parse_and_reduce_expression(p_parent, p_static);
|
|
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);
|
|
Vector<ClassNode::Constant> ce = current_class->constant_expressions;
|
|
|
|
// Try to find the constant expression by the identifier
|
|
for (int i = 0; i < ce.size(); ++i) {
|
|
if (ce[i].identifier == in->name) {
|
|
if (ce[i].expression->type == Node::TYPE_CONSTANT) {
|
|
cn = static_cast<ConstantNode *>(ce[i].expression);
|
|
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 NULL;
|
|
}
|
|
|
|
if (!path.is_abs_path() && base_path != "")
|
|
path = base_path + "/" + path;
|
|
path = path.replace("///", "//").simplify_path();
|
|
if (path == self_path) {
|
|
|
|
_set_error("Can't preload itself (use 'get_script()').");
|
|
return NULL;
|
|
}
|
|
|
|
Ref<Resource> res;
|
|
if (!validating) {
|
|
|
|
//this can be too slow for just validating code
|
|
if (for_completion && ScriptCodeCompletionCache::get_sigleton()) {
|
|
res = ScriptCodeCompletionCache::get_sigleton()->get_cached_resource(path);
|
|
} else {
|
|
res = ResourceLoader::load(path);
|
|
}
|
|
if (!res.is_valid()) {
|
|
_set_error("Can't preload resource at path: " + path);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
|
|
if (!FileAccess::exists(path)) {
|
|
_set_error("Can't preload resource at path: " + path);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected ')' after 'preload' path");
|
|
return NULL;
|
|
}
|
|
tokenizer->advance();
|
|
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = res;
|
|
|
|
expr = constant;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_PR_YIELD) {
|
|
|
|
//constant defined by tokenizer
|
|
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_OPEN) {
|
|
_set_error("Expected '(' after 'yield'");
|
|
return NULL;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
OperatorNode *yield = alloc_node<OperatorNode>();
|
|
yield->op = OperatorNode::OP_YIELD;
|
|
|
|
while (tokenizer->get_token() == GDTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
expr = yield;
|
|
tokenizer->advance();
|
|
} else {
|
|
|
|
parenthesis++;
|
|
|
|
Node *object = _parse_and_reduce_expression(p_parent, p_static);
|
|
if (!object)
|
|
return NULL;
|
|
yield->arguments.push_back(object);
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_COMMA) {
|
|
_set_error("Expected ',' after first argument of 'yield'");
|
|
return NULL;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_CURSOR) {
|
|
|
|
completion_cursor = StringName();
|
|
completion_node = object;
|
|
completion_type = COMPLETION_YIELD;
|
|
completion_class = current_class;
|
|
completion_function = current_function;
|
|
completion_line = tokenizer->get_token_line();
|
|
completion_argument = 0;
|
|
completion_block = current_block;
|
|
completion_found = true;
|
|
tokenizer->advance();
|
|
}
|
|
|
|
Node *signal = _parse_and_reduce_expression(p_parent, p_static);
|
|
if (!signal)
|
|
return NULL;
|
|
yield->arguments.push_back(signal);
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected ')' after second argument of 'yield'");
|
|
return NULL;
|
|
}
|
|
|
|
parenthesis--;
|
|
|
|
tokenizer->advance();
|
|
|
|
expr = yield;
|
|
}
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_SELF) {
|
|
|
|
if (p_static) {
|
|
_set_error("'self'' not allowed in static function or constant expression");
|
|
return NULL;
|
|
}
|
|
//constant defined by tokenizer
|
|
SelfNode *self = alloc_node<SelfNode>();
|
|
tokenizer->advance();
|
|
expr = self;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token(1) == GDTokenizer::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 expected after '.'");
|
|
return NULL;
|
|
}
|
|
if (!Variant::has_numeric_constant(bi_type, identifier)) {
|
|
|
|
_set_error("Static constant '" + identifier.operator String() + "' not present in built-in type " + Variant::get_type_name(bi_type) + ".");
|
|
return NULL;
|
|
}
|
|
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = Variant::get_numeric_constant_value(bi_type, identifier);
|
|
expr = cn;
|
|
|
|
} else if (tokenizer->get_token(1) == GDTokenizer::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;
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_BUILT_IN_TYPE) {
|
|
|
|
TypeNode *tn = alloc_node<TypeNode>();
|
|
tn->vtype = tokenizer->get_token_type();
|
|
op->arguments.push_back(tn);
|
|
tokenizer->advance(2);
|
|
} else if (tokenizer->get_token() == GDTokenizer::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() == GDTokenizer::TK_CURSOR) {
|
|
_make_completable_call(0);
|
|
completion_node = op;
|
|
}
|
|
if (!_parse_arguments(op, op->arguments, p_static, true))
|
|
return NULL;
|
|
|
|
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;
|
|
if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) {
|
|
}
|
|
|
|
if (p_parsing_constant) {
|
|
for (int i = 0; i < cln->constant_expressions.size(); ++i) {
|
|
|
|
if (cln->constant_expressions[i].identifier == identifier) {
|
|
|
|
expr = cln->constant_expressions[i].expression;
|
|
bfn = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
|
|
//check from constants
|
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
|
constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]];
|
|
expr = constant;
|
|
bfn = true;
|
|
}
|
|
}
|
|
|
|
if (!bfn) {
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = identifier;
|
|
expr = id;
|
|
}
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_OP_ADD || tokenizer->get_token() == GDTokenizer::TK_OP_SUB || tokenizer->get_token() == GDTokenizer::TK_OP_NOT || tokenizer->get_token() == GDTokenizer::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 GDTokenizer::TK_OP_ADD: e.op = OperatorNode::OP_POS; break;
|
|
case GDTokenizer::TK_OP_SUB: e.op = OperatorNode::OP_NEG; break;
|
|
case GDTokenizer::TK_OP_NOT: e.op = OperatorNode::OP_NOT; break;
|
|
case GDTokenizer::TK_OP_BIT_INVERT: e.op = OperatorNode::OP_BIT_INVERT; break;
|
|
default: {}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
if (e.op != OperatorNode::OP_NOT && tokenizer->get_token() == GDTokenizer::TK_OP_NOT) {
|
|
_set_error("Misplaced 'not'.");
|
|
return NULL;
|
|
}
|
|
|
|
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() == GDTokenizer::TK_BRACKET_OPEN) {
|
|
// array
|
|
tokenizer->advance();
|
|
|
|
ArrayNode *arr = alloc_node<ArrayNode>();
|
|
bool expecting_comma = false;
|
|
|
|
while (true) {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_EOF) {
|
|
|
|
_set_error("Unterminated array");
|
|
return NULL;
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_NEWLINE) {
|
|
|
|
tokenizer->advance(); //ignore newline
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
if (!expecting_comma) {
|
|
_set_error("expression or ']' expected");
|
|
return NULL;
|
|
}
|
|
|
|
expecting_comma = false;
|
|
tokenizer->advance(); //ignore newline
|
|
} else {
|
|
//parse expression
|
|
if (expecting_comma) {
|
|
_set_error("',' or ']' expected");
|
|
return NULL;
|
|
}
|
|
Node *n = _parse_expression(arr, p_static, p_allow_assign, p_parsing_constant);
|
|
if (!n)
|
|
return NULL;
|
|
arr->elements.push_back(n);
|
|
expecting_comma = true;
|
|
}
|
|
}
|
|
|
|
expr = arr;
|
|
} else if (tokenizer->get_token() == GDTokenizer::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 = NULL;
|
|
Set<Variant> keys;
|
|
|
|
DictExpect expecting = DICT_EXPECT_KEY;
|
|
|
|
while (true) {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_EOF) {
|
|
|
|
_set_error("Unterminated dictionary");
|
|
return NULL;
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
|
|
if (expecting == DICT_EXPECT_COLON) {
|
|
_set_error("':' expected");
|
|
return NULL;
|
|
}
|
|
if (expecting == DICT_EXPECT_VALUE) {
|
|
_set_error("value expected");
|
|
return NULL;
|
|
}
|
|
tokenizer->advance();
|
|
break;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_NEWLINE) {
|
|
|
|
tokenizer->advance(); //ignore newline
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
|
|
if (expecting == DICT_EXPECT_KEY) {
|
|
_set_error("key or '}' expected");
|
|
return NULL;
|
|
}
|
|
if (expecting == DICT_EXPECT_VALUE) {
|
|
_set_error("value expected");
|
|
return NULL;
|
|
}
|
|
if (expecting == DICT_EXPECT_COLON) {
|
|
_set_error("':' expected");
|
|
return NULL;
|
|
}
|
|
|
|
expecting = DICT_EXPECT_KEY;
|
|
tokenizer->advance(); //ignore newline
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_COLON) {
|
|
|
|
if (expecting == DICT_EXPECT_KEY) {
|
|
_set_error("key or '}' expected");
|
|
return NULL;
|
|
}
|
|
if (expecting == DICT_EXPECT_VALUE) {
|
|
_set_error("value expected");
|
|
return NULL;
|
|
}
|
|
if (expecting == DICT_EXPECT_COMMA) {
|
|
_set_error("',' or '}' expected");
|
|
return NULL;
|
|
}
|
|
|
|
expecting = DICT_EXPECT_VALUE;
|
|
tokenizer->advance(); //ignore newline
|
|
} else {
|
|
|
|
if (expecting == DICT_EXPECT_COMMA) {
|
|
_set_error("',' or '}' expected");
|
|
return NULL;
|
|
}
|
|
if (expecting == DICT_EXPECT_COLON) {
|
|
_set_error("':' expected");
|
|
return NULL;
|
|
}
|
|
|
|
if (expecting == DICT_EXPECT_KEY) {
|
|
|
|
if (tokenizer->is_token_literal() && tokenizer->get_token(1) == GDTokenizer::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();
|
|
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 NULL;
|
|
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 NULL;
|
|
expecting = DICT_EXPECT_COMMA;
|
|
|
|
if (key->type == GDParser::Node::TYPE_CONSTANT) {
|
|
Variant const &keyName = static_cast<const GDParser::ConstantNode *>(key)->value;
|
|
|
|
if (keys.has(keyName)) {
|
|
_set_error("Duplicate key found in Dictionary literal");
|
|
return NULL;
|
|
}
|
|
keys.insert(keyName);
|
|
}
|
|
|
|
DictionaryNode::Pair pair;
|
|
pair.key = key;
|
|
pair.value = value;
|
|
dict->elements.push_back(pair);
|
|
key = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
expr = dict;
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDTokenizer::TK_CURSOR) && tokenizer->get_token(2) == GDTokenizer::TK_PARENTHESIS_OPEN) {
|
|
// 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;
|
|
if (_get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier)) {
|
|
//indexing stuff
|
|
}
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = identifier;
|
|
op->arguments.push_back(id);
|
|
|
|
tokenizer->advance(1);
|
|
if (!_parse_arguments(op, op->arguments, p_static))
|
|
return NULL;
|
|
|
|
expr = op;
|
|
|
|
} else {
|
|
|
|
//find list [ or find dictionary {
|
|
|
|
//print_line("found bug?");
|
|
|
|
_set_error("Error parsing expression, misplaced: " + String(tokenizer->get_token_name(tokenizer->get_token())));
|
|
return NULL; //nothing
|
|
}
|
|
|
|
if (!expr) {
|
|
ERR_EXPLAIN("GDParser bug, couldn't figure out what expression is..");
|
|
ERR_FAIL_COND_V(!expr, NULL);
|
|
}
|
|
|
|
/******************/
|
|
/* Parse Indexing */
|
|
/******************/
|
|
|
|
while (true) {
|
|
|
|
//expressions can be indexed any number of times
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PERIOD) {
|
|
|
|
//indexing using "."
|
|
|
|
if (tokenizer->get_token(1) != GDTokenizer::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 NULL;
|
|
} else if (tokenizer->get_token(2) == GDTokenizer::TK_PARENTHESIS_OPEN) {
|
|
//call!!
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_CALL;
|
|
|
|
tokenizer->advance();
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
if (tokenizer->get_token() == GDTokenizer::TK_BUILT_IN_FUNC) {
|
|
//small hack so built in funcs don't obfuscate methods
|
|
|
|
id->name = GDFunctions::get_func_name(tokenizer->get_token_built_in_func());
|
|
tokenizer->advance();
|
|
|
|
} else {
|
|
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() == GDTokenizer::TK_CURSOR) {
|
|
_make_completable_call(0);
|
|
completion_node = op;
|
|
}
|
|
if (!_parse_arguments(op, op->arguments, p_static, true))
|
|
return NULL;
|
|
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 allright
|
|
}
|
|
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() == GDTokenizer::TK_BRACKET_OPEN) {
|
|
//indexing using "[]"
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_INDEX;
|
|
|
|
tokenizer->advance(1);
|
|
|
|
Node *subexpr = _parse_expression(op, p_static, p_allow_assign, p_parsing_constant);
|
|
if (!subexpr) {
|
|
return NULL;
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_BRACKET_CLOSE) {
|
|
_set_error("Expected ']'");
|
|
return NULL;
|
|
}
|
|
|
|
op->arguments.push_back(expr);
|
|
op->arguments.push_back(subexpr);
|
|
tokenizer->advance(1);
|
|
expr = op;
|
|
|
|
} else
|
|
break;
|
|
}
|
|
|
|
/******************/
|
|
/* Parse Operator */
|
|
/******************/
|
|
|
|
if (parenthesis > 0) {
|
|
//remove empty space (only allowed if inside parenthesis
|
|
while (tokenizer->get_token() == GDTokenizer::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) { \
|
|
_set_error("Unexpected assign."); \
|
|
return NULL; \
|
|
} \
|
|
p_allow_assign = false;
|
|
switch (tokenizer->get_token()) { //see operator
|
|
|
|
case GDTokenizer::TK_OP_IN: op = OperatorNode::OP_IN; break;
|
|
case GDTokenizer::TK_OP_EQUAL: op = OperatorNode::OP_EQUAL; break;
|
|
case GDTokenizer::TK_OP_NOT_EQUAL: op = OperatorNode::OP_NOT_EQUAL; break;
|
|
case GDTokenizer::TK_OP_LESS: op = OperatorNode::OP_LESS; break;
|
|
case GDTokenizer::TK_OP_LESS_EQUAL: op = OperatorNode::OP_LESS_EQUAL; break;
|
|
case GDTokenizer::TK_OP_GREATER: op = OperatorNode::OP_GREATER; break;
|
|
case GDTokenizer::TK_OP_GREATER_EQUAL: op = OperatorNode::OP_GREATER_EQUAL; break;
|
|
case GDTokenizer::TK_OP_AND: op = OperatorNode::OP_AND; break;
|
|
case GDTokenizer::TK_OP_OR: op = OperatorNode::OP_OR; break;
|
|
case GDTokenizer::TK_OP_ADD: op = OperatorNode::OP_ADD; break;
|
|
case GDTokenizer::TK_OP_SUB: op = OperatorNode::OP_SUB; break;
|
|
case GDTokenizer::TK_OP_MUL: op = OperatorNode::OP_MUL; break;
|
|
case GDTokenizer::TK_OP_DIV: op = OperatorNode::OP_DIV; break;
|
|
case GDTokenizer::TK_OP_MOD:
|
|
op = OperatorNode::OP_MOD;
|
|
break;
|
|
//case GDTokenizer::TK_OP_NEG: op=OperatorNode::OP_NEG ; break;
|
|
case GDTokenizer::TK_OP_SHIFT_LEFT: op = OperatorNode::OP_SHIFT_LEFT; break;
|
|
case GDTokenizer::TK_OP_SHIFT_RIGHT: op = OperatorNode::OP_SHIFT_RIGHT; break;
|
|
case GDTokenizer::TK_OP_ASSIGN: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_ADD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_SUB: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_MUL: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_DIV: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_MOD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_BIT_AND: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_BIT_OR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR; break;
|
|
case GDTokenizer::TK_OP_ASSIGN_BIT_XOR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR; break;
|
|
case GDTokenizer::TK_OP_BIT_AND: op = OperatorNode::OP_BIT_AND; break;
|
|
case GDTokenizer::TK_OP_BIT_OR: op = OperatorNode::OP_BIT_OR; break;
|
|
case GDTokenizer::TK_OP_BIT_XOR: op = OperatorNode::OP_BIT_XOR; break;
|
|
case GDTokenizer::TK_PR_IS: op = OperatorNode::OP_IS; break;
|
|
case GDTokenizer::TK_CF_IF: op = OperatorNode::OP_TERNARY_IF; break;
|
|
case GDTokenizer::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;
|
|
|
|
switch (expression[i].op) {
|
|
|
|
case OperatorNode::OP_IS:
|
|
priority = -1;
|
|
break; //before anything
|
|
|
|
case OperatorNode::OP_BIT_INVERT:
|
|
priority = 0;
|
|
unary = true;
|
|
break;
|
|
case OperatorNode::OP_NEG:
|
|
priority = 1;
|
|
unary = true;
|
|
break;
|
|
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;
|
|
break;
|
|
case OperatorNode::OP_TERNARY_ELSE:
|
|
priority = 14;
|
|
error = true;
|
|
break; // Errors out when found without IF (since IF would consume it)
|
|
|
|
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("GDParser bug, invalid operator in expression: " + itos(expression[i].op));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (priority < min_priority) {
|
|
if (error) {
|
|
_set_error("Unexpected operator");
|
|
return NULL;
|
|
}
|
|
// < is used for left to right (default)
|
|
// <= is used for right to left
|
|
next_op = i;
|
|
min_priority = priority;
|
|
is_unary = unary;
|
|
is_ternary = ternary;
|
|
}
|
|
}
|
|
|
|
if (next_op == -1) {
|
|
|
|
_set_error("Yet another parser bug....");
|
|
ERR_FAIL_COND_V(next_op == -1, NULL);
|
|
}
|
|
|
|
// 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 NULL;
|
|
}
|
|
}
|
|
|
|
//consecutively do unary opeators
|
|
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[i].is_op = false;
|
|
expression[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(NULL);
|
|
}
|
|
|
|
if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) {
|
|
_set_error("Expected else after ternary if.");
|
|
ERR_FAIL_V(NULL);
|
|
}
|
|
if (next_op >= (expression.size() - 3)) {
|
|
_set_error("Expected value after ternary else.");
|
|
ERR_FAIL_V(NULL);
|
|
}
|
|
|
|
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(NULL);
|
|
}
|
|
|
|
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 an 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 NULL;
|
|
}
|
|
|
|
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 an 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 NULL;
|
|
}
|
|
|
|
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[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(NULL);
|
|
}
|
|
|
|
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(NULL);
|
|
}
|
|
|
|
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 an unary op in a valid combination,
|
|
// due to how precedence works, unaries will always disappear first
|
|
|
|
_set_error("Unexpected two consecutive operators.");
|
|
return NULL;
|
|
}
|
|
|
|
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[next_op - 1].node = op;
|
|
expression.remove(next_op);
|
|
expression.remove(next_op);
|
|
}
|
|
}
|
|
|
|
return expression[0].node;
|
|
}
|
|
|
|
GDParser::Node *GDParser::_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[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;
|
|
//print_line("mk array "+itos(!p_to_const));
|
|
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;
|
|
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[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[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;
|
|
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[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 && GDFunctions::is_deterministic(static_cast<BuiltInFunctionNode *>(op->arguments[0])->function))) && last_not_constant == 0) {
|
|
|
|
//native type constructor or intrinsic function
|
|
const Variant **vptr = NULL;
|
|
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[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 {
|
|
GDFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function;
|
|
GDFunctions::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 {
|
|
GDFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function;
|
|
errwhere = String("'") + GDFunctions::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;
|
|
return cn;
|
|
|
|
} else if (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && last_not_constant == 0) {
|
|
}
|
|
|
|
return op; //don't reduce yet
|
|
|
|
} else if (op->op == OperatorNode::OP_YIELD) {
|
|
return op;
|
|
|
|
} 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;
|
|
return cn;
|
|
|
|
} /*else 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");
|
|
return op;
|
|
}
|
|
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value=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;
|
|
return cn;
|
|
}
|
|
|
|
return op;
|
|
}
|
|
|
|
//validate assignment (don't assign to cosntant 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;
|
|
}
|
|
|
|
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; \
|
|
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; \
|
|
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_SUBSTRACT);
|
|
} 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;
|
|
default: { ERR_FAIL_V(op); }
|
|
}
|
|
|
|
ERR_FAIL_V(op);
|
|
} break;
|
|
default: {
|
|
return p_node;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
GDParser::Node *GDParser::_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 NULL;
|
|
expr = _reduce_expression(expr, p_reduce_const);
|
|
if (!expr || error_set)
|
|
return NULL;
|
|
return expr;
|
|
}
|
|
|
|
bool GDParser::_recover_from_completion() {
|
|
|
|
if (!completion_found) {
|
|
return false; //can't recover if no completion
|
|
}
|
|
//skip stuff until newline
|
|
while (tokenizer->get_token() != GDTokenizer::TK_NEWLINE && tokenizer->get_token() != GDTokenizer::TK_EOF && tokenizer->get_token() != GDTokenizer::TK_ERROR) {
|
|
tokenizer->advance();
|
|
}
|
|
completion_found = false;
|
|
error_set = false;
|
|
if (tokenizer->get_token() == GDTokenizer::TK_ERROR) {
|
|
error_set = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
GDParser::PatternNode *GDParser::_parse_pattern(bool p_static) {
|
|
|
|
PatternNode *pattern = alloc_node<PatternNode>();
|
|
|
|
GDTokenizer::Token token = tokenizer->get_token();
|
|
if (error_set)
|
|
return NULL;
|
|
|
|
if (token == GDTokenizer::TK_EOF) {
|
|
return NULL;
|
|
}
|
|
|
|
switch (token) {
|
|
// array
|
|
case GDTokenizer::TK_BRACKET_OPEN: {
|
|
tokenizer->advance();
|
|
pattern->pt_type = GDParser::PatternNode::PT_ARRAY;
|
|
while (true) {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDTokenizer::TK_PERIOD) {
|
|
// match everything
|
|
tokenizer->advance(2);
|
|
PatternNode *sub_pattern = alloc_node<PatternNode>();
|
|
sub_pattern->pt_type = GDParser::PatternNode::PT_IGNORE_REST;
|
|
pattern->array.push_back(sub_pattern);
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA && tokenizer->get_token(1) == GDTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance(2);
|
|
break;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance(1);
|
|
break;
|
|
} else {
|
|
_set_error("'..' pattern only allowed at the end of an array pattern");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
PatternNode *sub_pattern = _parse_pattern(p_static);
|
|
if (!sub_pattern) {
|
|
return NULL;
|
|
}
|
|
|
|
pattern->array.push_back(sub_pattern);
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
continue;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
} else {
|
|
_set_error("Not a valid pattern");
|
|
return NULL;
|
|
}
|
|
}
|
|
} break;
|
|
// bind
|
|
case GDTokenizer::TK_PR_VAR: {
|
|
tokenizer->advance();
|
|
pattern->pt_type = GDParser::PatternNode::PT_BIND;
|
|
pattern->bind = tokenizer->get_token_identifier();
|
|
tokenizer->advance();
|
|
} break;
|
|
// dictionary
|
|
case GDTokenizer::TK_CURLY_BRACKET_OPEN: {
|
|
tokenizer->advance();
|
|
pattern->pt_type = GDParser::PatternNode::PT_DICTIONARY;
|
|
while (true) {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDTokenizer::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() == GDTokenizer::TK_COMMA && tokenizer->get_token(1) == GDTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
tokenizer->advance(2);
|
|
break;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
tokenizer->advance(1);
|
|
break;
|
|
} else {
|
|
_set_error("'..' pattern only allowed at the end of an dictionary pattern");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
Node *key = _parse_and_reduce_expression(pattern, p_static);
|
|
if (!key) {
|
|
_set_error("Not a valid key in pattern");
|
|
return NULL;
|
|
}
|
|
|
|
if (key->type != GDParser::Node::TYPE_CONSTANT) {
|
|
_set_error("Not a constant expression as key");
|
|
return NULL;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COLON) {
|
|
tokenizer->advance();
|
|
|
|
PatternNode *value = _parse_pattern(p_static);
|
|
if (!value) {
|
|
_set_error("Expected pattern in dictionary value");
|
|
return NULL;
|
|
}
|
|
|
|
pattern->dictionary.insert(static_cast<ConstantNode *>(key), value);
|
|
} else {
|
|
pattern->dictionary.insert(static_cast<ConstantNode *>(key), NULL);
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
continue;
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
} else {
|
|
_set_error("Not a valid pattern");
|
|
return NULL;
|
|
}
|
|
}
|
|
} break;
|
|
case GDTokenizer::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 (error_set) {
|
|
return NULL;
|
|
}
|
|
|
|
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 NULL;
|
|
}
|
|
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 NULL;
|
|
}
|
|
|
|
} else if (value->type != Node::TYPE_IDENTIFIER && value->type != Node::TYPE_CONSTANT) {
|
|
_set_error("Only constant expressions or variables allowed in a pattern");
|
|
return NULL;
|
|
}
|
|
|
|
pattern->pt_type = PatternNode::PT_CONSTANT;
|
|
pattern->constant = value;
|
|
} break;
|
|
}
|
|
|
|
return pattern;
|
|
}
|
|
|
|
void GDParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) {
|
|
int indent_level = tab_level.back()->get();
|
|
|
|
while (true) {
|
|
|
|
while (tokenizer->get_token() == GDTokenizer::TK_NEWLINE && _parse_newline())
|
|
;
|
|
|
|
// GDTokenizer::Token token = tokenizer->get_token();
|
|
if (error_set)
|
|
return;
|
|
|
|
if (indent_level > tab_level.back()->get()) {
|
|
return; // go back a level
|
|
}
|
|
|
|
if (pending_newline != -1) {
|
|
pending_newline = -1;
|
|
}
|
|
|
|
PatternBranchNode *branch = alloc_node<PatternBranchNode>();
|
|
|
|
branch->patterns.push_back(_parse_pattern(p_static));
|
|
if (!branch->patterns[0]) {
|
|
return;
|
|
}
|
|
|
|
while (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
branch->patterns.push_back(_parse_pattern(p_static));
|
|
if (!branch->patterns[branch->patterns.size() - 1]) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!_enter_indent_block()) {
|
|
_set_error("Expected block in pattern branch");
|
|
return;
|
|
}
|
|
|
|
branch->body = alloc_node<BlockNode>();
|
|
branch->body->parent_block = p_block;
|
|
p_block->sub_blocks.push_back(branch->body);
|
|
current_block = branch->body;
|
|
|
|
_parse_block(branch->body, p_static);
|
|
|
|
current_block = p_block;
|
|
|
|
p_branches.push_back(branch);
|
|
}
|
|
}
|
|
|
|
void GDParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) {
|
|
switch (p_pattern->pt_type) {
|
|
case PatternNode::PT_CONSTANT: {
|
|
|
|
// typecheck
|
|
BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
|
|
typeof_node->function = GDFunctions::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);
|
|
|
|
OperatorNode *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);
|
|
|
|
// comare 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);
|
|
|
|
OperatorNode *comparison = alloc_node<OperatorNode>();
|
|
comparison->op = OperatorNode::OP_AND;
|
|
comparison->arguments.push_back(type_comp);
|
|
comparison->arguments.push_back(value_comp);
|
|
|
|
p_resulting_node = comparison;
|
|
|
|
} 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
|
|
|
|
{
|
|
// typecheck
|
|
BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
|
|
typeof_node->function = GDFunctions::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";
|
|
|
|
OperatorNode *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);
|
|
|
|
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;
|
|
}
|
|
|
|
for (int i = 0; i < p_pattern->array.size(); i++) {
|
|
PatternNode *pattern = p_pattern->array[i];
|
|
|
|
Node *condition = NULL;
|
|
|
|
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
|
|
|
|
{
|
|
// typecheck
|
|
BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
|
|
typeof_node->function = GDFunctions::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";
|
|
|
|
OperatorNode *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);
|
|
|
|
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;
|
|
}
|
|
|
|
for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) {
|
|
|
|
Node *condition = NULL;
|
|
|
|
// chech 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 GDParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement) {
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = "#match_value";
|
|
|
|
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 = NULL;
|
|
|
|
Map<StringName, Node *> binding;
|
|
|
|
for (int j = 0; j < branch->patterns.size(); j++) {
|
|
PatternNode *pattern = branch->patterns[j];
|
|
|
|
Map<StringName, Node *> bindings;
|
|
Node *resulting_node;
|
|
_generate_pattern(pattern, id, resulting_node, bindings);
|
|
|
|
if (!binding.empty() && !bindings.empty()) {
|
|
_set_error("Multipatterns can't contain bindings");
|
|
return;
|
|
} else {
|
|
binding = bindings;
|
|
}
|
|
|
|
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()) {
|
|
LocalVarNode *local_var = alloc_node<LocalVarNode>();
|
|
local_var->name = e->key();
|
|
local_var->assign = e->value();
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = local_var->name;
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_ASSIGN;
|
|
op->arguments.push_back(id);
|
|
op->arguments.push_back(local_var->assign);
|
|
|
|
branch->body->statements.push_front(op);
|
|
branch->body->statements.push_front(local_var);
|
|
}
|
|
|
|
compiled_branch.body = branch->body;
|
|
|
|
p_match_statement->compiled_pattern_branches.push_back(compiled_branch);
|
|
}
|
|
}
|
|
|
|
void GDParser::_parse_block(BlockNode *p_block, bool p_static) {
|
|
|
|
int indent_level = tab_level.back()->get();
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
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 && tab_level.back()->prev() && tab_level.back()->prev()->get() == indent_level) {
|
|
// pythonic single-line expression, don't parse future lines
|
|
tab_level.pop_back();
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return;
|
|
}
|
|
is_first_line = false;
|
|
|
|
GDTokenizer::Token token = tokenizer->get_token();
|
|
if (error_set)
|
|
return;
|
|
|
|
if (indent_level > tab_level.back()->get()) {
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return; //go back a level
|
|
}
|
|
|
|
if (pending_newline != -1) {
|
|
|
|
NewLineNode *nl = alloc_node<NewLineNode>();
|
|
nl->line = pending_newline;
|
|
p_block->statements.push_back(nl);
|
|
pending_newline = -1;
|
|
}
|
|
|
|
switch (token) {
|
|
|
|
case GDTokenizer::TK_EOF:
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
case GDTokenizer::TK_ERROR: {
|
|
return; //go back
|
|
|
|
//end of file!
|
|
|
|
} break;
|
|
case GDTokenizer::TK_NEWLINE: {
|
|
|
|
if (!_parse_newline()) {
|
|
if (!error_set) {
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
pending_newline = p_block->end_line;
|
|
}
|
|
return;
|
|
}
|
|
|
|
NewLineNode *nl = alloc_node<NewLineNode>();
|
|
nl->line = tokenizer->get_token_line();
|
|
p_block->statements.push_back(nl);
|
|
|
|
} break;
|
|
case GDTokenizer::TK_CF_PASS: {
|
|
if (tokenizer->get_token(1) != GDTokenizer::TK_SEMICOLON && tokenizer->get_token(1) != GDTokenizer::TK_NEWLINE && tokenizer->get_token(1) != GDTokenizer::TK_EOF) {
|
|
|
|
_set_error("Expected ';' or <NewLine>.");
|
|
return;
|
|
}
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() == GDTokenizer::TK_SEMICOLON) {
|
|
// Ignore semicolon after 'pass'
|
|
tokenizer->advance();
|
|
}
|
|
} break;
|
|
case GDTokenizer::TK_PR_VAR: {
|
|
//variale declaration and (eventual) initialization
|
|
|
|
tokenizer->advance();
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
|
|
_set_error("Expected identifier for local variable name.");
|
|
return;
|
|
}
|
|
StringName n = tokenizer->get_token_literal();
|
|
tokenizer->advance();
|
|
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) {
|
|
for (int i = 0; i < check_block->variables.size(); i++) {
|
|
if (n == check_block->variables[i]) {
|
|
_set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variable_lines[i]) + ").");
|
|
return;
|
|
}
|
|
}
|
|
check_block = check_block->parent_block;
|
|
}
|
|
|
|
int var_line = tokenizer->get_token_line();
|
|
|
|
//must know when the local variable is declared
|
|
LocalVarNode *lv = alloc_node<LocalVarNode>();
|
|
lv->name = n;
|
|
p_block->statements.push_back(lv);
|
|
|
|
Node *assigned = NULL;
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_OP_ASSIGN) {
|
|
|
|
tokenizer->advance();
|
|
Node *subexpr = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!subexpr) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
lv->assign = subexpr;
|
|
assigned = subexpr;
|
|
} else {
|
|
|
|
ConstantNode *c = alloc_node<ConstantNode>();
|
|
c->value = Variant();
|
|
assigned = c;
|
|
}
|
|
//must be added later, to avoid self-referencing.
|
|
p_block->variables.push_back(n); //line?
|
|
p_block->variable_lines.push_back(var_line);
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = n;
|
|
|
|
OperatorNode *op = alloc_node<OperatorNode>();
|
|
op->op = OperatorNode::OP_ASSIGN;
|
|
op->arguments.push_back(id);
|
|
op->arguments.push_back(assigned);
|
|
p_block->statements.push_back(op);
|
|
|
|
if (!_end_statement()) {
|
|
_set_error("Expected end of statement (var)");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
case GDTokenizer::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;
|
|
p_block->sub_blocks.push_back(cf_if->body);
|
|
|
|
if (!_enter_indent_block(cf_if->body)) {
|
|
_set_error("Expected 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);
|
|
|
|
while (true) {
|
|
|
|
while (tokenizer->get_token() == GDTokenizer::TK_NEWLINE && _parse_newline())
|
|
;
|
|
|
|
if (tab_level.back()->get() < indent_level) { //not at current indent level
|
|
p_block->end_line = tokenizer->get_token_line();
|
|
return;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_CF_ELIF) {
|
|
|
|
if (tab_level.back()->get() > indent_level) {
|
|
|
|
_set_error("Invalid indent");
|
|
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 *condition = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!condition) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
cf_else->arguments.push_back(condition);
|
|
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 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;
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CF_ELSE) {
|
|
|
|
if (tab_level.back()->get() > indent_level) {
|
|
_set_error("Invalid indent");
|
|
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 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;
|
|
|
|
break; //after else, exit
|
|
|
|
} else
|
|
break;
|
|
}
|
|
|
|
} break;
|
|
case GDTokenizer::TK_CF_WHILE: {
|
|
|
|
tokenizer->advance();
|
|
Node *condition = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!condition) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
ControlFlowNode *cf_while = alloc_node<ControlFlowNode>();
|
|
|
|
cf_while->cf_type = ControlFlowNode::CF_WHILE;
|
|
cf_while->arguments.push_back(condition);
|
|
|
|
cf_while->body = alloc_node<BlockNode>();
|
|
cf_while->body->parent_block = p_block;
|
|
p_block->sub_blocks.push_back(cf_while->body);
|
|
|
|
if (!_enter_indent_block(cf_while->body)) {
|
|
_set_error("Expected 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 GDTokenizer::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();
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::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;
|
|
}
|
|
|
|
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 == GDFunctions::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 = false;
|
|
|
|
for (int i = 1; i < op->arguments.size(); i++) {
|
|
args.push_back(op->arguments[i]);
|
|
if (constant && 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);
|
|
constant = true;
|
|
}
|
|
} 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;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
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
|
|
cf_for->body->variables.push_back(id->name);
|
|
cf_for->body->variable_lines.push_back(id->line);
|
|
_parse_block(cf_for->body, p_static);
|
|
cf_for->body->variables.remove(0);
|
|
cf_for->body->variable_lines.remove(0);
|
|
current_block = p_block;
|
|
|
|
if (error_set)
|
|
return;
|
|
p_block->statements.push_back(cf_for);
|
|
} break;
|
|
case GDTokenizer::TK_CF_CONTINUE: {
|
|
|
|
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_error("Expected end of statement (continue)");
|
|
return;
|
|
}
|
|
} break;
|
|
case GDTokenizer::TK_CF_BREAK: {
|
|
|
|
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_error("Expected end of statement (break)");
|
|
return;
|
|
}
|
|
} break;
|
|
case GDTokenizer::TK_CF_RETURN: {
|
|
|
|
tokenizer->advance();
|
|
ControlFlowNode *cf_return = alloc_node<ControlFlowNode>();
|
|
cf_return->cf_type = ControlFlowNode::CF_RETURN;
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_SEMICOLON || tokenizer->get_token() == GDTokenizer::TK_NEWLINE || tokenizer->get_token() == GDTokenizer::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_error("Expected end of statement after return expression.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
} break;
|
|
case GDTokenizer::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;
|
|
|
|
p_block->sub_blocks.push_back(compiled_branches);
|
|
|
|
_parse_pattern_block(compiled_branches, match_node->branches, p_static);
|
|
|
|
_transform_match_statment(compiled_branches, match_node);
|
|
|
|
ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>();
|
|
match_cf_node->cf_type = ControlFlowNode::CF_MATCH;
|
|
match_cf_node->match = match_node;
|
|
|
|
p_block->statements.push_back(match_cf_node);
|
|
|
|
_end_statement();
|
|
} break;
|
|
case GDTokenizer::TK_PR_ASSERT: {
|
|
|
|
tokenizer->advance();
|
|
Node *condition = _parse_and_reduce_expression(p_block, p_static);
|
|
if (!condition) {
|
|
if (_recover_from_completion()) {
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
AssertNode *an = alloc_node<AssertNode>();
|
|
an->condition = condition;
|
|
p_block->statements.push_back(an);
|
|
|
|
if (!_end_statement()) {
|
|
_set_error("Expected end of statement after assert.");
|
|
return;
|
|
}
|
|
} break;
|
|
case GDTokenizer::TK_PR_BREAKPOINT: {
|
|
|
|
tokenizer->advance();
|
|
BreakpointNode *bn = alloc_node<BreakpointNode>();
|
|
p_block->statements.push_back(bn);
|
|
|
|
if (!_end_statement()) {
|
|
_set_error("Expected end of statement after 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()) {
|
|
_set_error("Expected end of statement after expression.");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
/*
|
|
case GDTokenizer::TK_CF_LOCAL: {
|
|
|
|
if (tokenizer->get_token(1)!=GDTokenizer::TK_SEMICOLON && tokenizer->get_token(1)!=GDTokenizer::TK_NEWLINE ) {
|
|
|
|
_set_error("Expected ';' or <NewLine>.");
|
|
}
|
|
tokenizer->advance();
|
|
} break;
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GDParser::_parse_newline() {
|
|
|
|
if (tokenizer->get_token(1) != GDTokenizer::TK_EOF && tokenizer->get_token(1) != GDTokenizer::TK_NEWLINE) {
|
|
|
|
int indent = tokenizer->get_token_line_indent();
|
|
int current_indent = tab_level.back()->get();
|
|
|
|
if (indent > current_indent) {
|
|
_set_error("Unexpected indent.");
|
|
return false;
|
|
}
|
|
|
|
if (indent < current_indent) {
|
|
|
|
while (indent < current_indent) {
|
|
|
|
//exit block
|
|
if (tab_level.size() == 1) {
|
|
_set_error("Invalid indent. BUG?");
|
|
return false;
|
|
}
|
|
|
|
tab_level.pop_back();
|
|
|
|
if (tab_level.back()->get() < indent) {
|
|
|
|
_set_error("Unindent does not match any outer indentation level.");
|
|
return false;
|
|
}
|
|
current_indent = tab_level.back()->get();
|
|
}
|
|
|
|
tokenizer->advance();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
return true;
|
|
}
|
|
|
|
void GDParser::_parse_extends(ClassNode *p_class) {
|
|
|
|
if (p_class->extends_used) {
|
|
|
|
_set_error("'extends' already used for this class.");
|
|
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() == GDTokenizer::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() == GDTokenizer::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();
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PERIOD) {
|
|
return;
|
|
} else
|
|
tokenizer->advance();
|
|
}
|
|
|
|
while (true) {
|
|
if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER) {
|
|
|
|
_set_error("Invalid 'extends' syntax, expected string constant (path) and/or identifier (parent class).");
|
|
return;
|
|
}
|
|
|
|
StringName identifier = tokenizer->get_token_identifier();
|
|
p_class->extends_class.push_back(identifier);
|
|
|
|
tokenizer->advance(1);
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PERIOD)
|
|
return;
|
|
}
|
|
}
|
|
|
|
void GDParser::_parse_class(ClassNode *p_class) {
|
|
|
|
int indent_level = tab_level.back()->get();
|
|
|
|
while (true) {
|
|
|
|
GDTokenizer::Token token = tokenizer->get_token();
|
|
if (error_set)
|
|
return;
|
|
|
|
if (indent_level > tab_level.back()->get()) {
|
|
p_class->end_line = tokenizer->get_token_line();
|
|
return; //go back a level
|
|
}
|
|
|
|
switch (token) {
|
|
|
|
case GDTokenizer::TK_EOF:
|
|
p_class->end_line = tokenizer->get_token_line();
|
|
case GDTokenizer::TK_ERROR: {
|
|
return; //go back
|
|
//end of file!
|
|
} break;
|
|
case GDTokenizer::TK_NEWLINE: {
|
|
if (!_parse_newline()) {
|
|
if (!error_set) {
|
|
p_class->end_line = tokenizer->get_token_line();
|
|
}
|
|
return;
|
|
}
|
|
} break;
|
|
case GDTokenizer::TK_PR_EXTENDS: {
|
|
|
|
_parse_extends(p_class);
|
|
if (error_set)
|
|
return;
|
|
if (!_end_statement()) {
|
|
_set_error("Expected end of statement after extends");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
case GDTokenizer::TK_PR_TOOL: {
|
|
|
|
if (p_class->tool) {
|
|
|
|
_set_error("tool used more than once");
|
|
return;
|
|
}
|
|
|
|
p_class->tool = true;
|
|
tokenizer->advance();
|
|
|
|
} break;
|
|
case GDTokenizer::TK_PR_CLASS: {
|
|
//class inside class :D
|
|
|
|
StringName name;
|
|
StringName extends;
|
|
|
|
if (tokenizer->get_token(1) != GDTokenizer::TK_IDENTIFIER) {
|
|
|
|
_set_error("'class' syntax: 'class <Name>:' or 'class <Name> extends <BaseClass>:'");
|
|
return;
|
|
}
|
|
name = tokenizer->get_token_identifier(1);
|
|
tokenizer->advance(2);
|
|
|
|
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() == GDTokenizer::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 GDTokenizer::TK_CF_PASS: {
|
|
|
|
tokenizer->advance(1);
|
|
} break;
|
|
*/
|
|
case GDTokenizer::TK_PR_STATIC: {
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_FUNCTION) {
|
|
|
|
_set_error("Expected 'func'.");
|
|
return;
|
|
}
|
|
|
|
}; //fallthrough to function
|
|
case GDTokenizer::TK_PR_FUNCTION: {
|
|
|
|
bool _static = false;
|
|
pending_newline = -1;
|
|
|
|
if (tokenizer->get_token(-1) == GDTokenizer::TK_PR_STATIC) {
|
|
|
|
_static = true;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
StringName name;
|
|
|
|
if (_get_completable_identifier(COMPLETION_VIRTUAL_FUNC, name)) {
|
|
}
|
|
|
|
if (name == StringName()) {
|
|
|
|
_set_error("Expected 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("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("Function '" + String(name) + "' already exists in this class (at line: " + itos(p_class->static_functions[i]->line) + ").");
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_OPEN) {
|
|
|
|
_set_error("Expected '(' after identifier (syntax: 'func <identifier>([arguments]):' ).");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
Vector<StringName> arguments;
|
|
Vector<Node *> default_values;
|
|
|
|
int fnline = tokenizer->get_token_line();
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
//has arguments
|
|
bool defaulting = false;
|
|
while (true) {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
continue;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PR_VAR) {
|
|
|
|
tokenizer->advance(); //var before the identifier is allowed
|
|
}
|
|
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
|
|
_set_error("Expected identifier for argument.");
|
|
return;
|
|
}
|
|
|
|
StringName argname = tokenizer->get_token_identifier();
|
|
arguments.push_back(argname);
|
|
|
|
tokenizer->advance();
|
|
|
|
if (defaulting && tokenizer->get_token() != GDTokenizer::TK_OP_ASSIGN) {
|
|
|
|
_set_error("Default parameter expected.");
|
|
return;
|
|
}
|
|
|
|
//tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::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;
|
|
|
|
IdentifierNode *in = alloc_node<IdentifierNode>();
|
|
in->name = argname;
|
|
|
|
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() == GDTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
continue;
|
|
} else if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
|
|
_set_error("Expected ',' or ')'.");
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
BlockNode *block = alloc_node<BlockNode>();
|
|
block->parent_class = p_class;
|
|
|
|
if (name == "_init") {
|
|
|
|
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() == GDTokenizer::TK_PERIOD) {
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_OPEN) {
|
|
_set_error("expected '(' for parent constructor arguments.");
|
|
}
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
//has arguments
|
|
parenthesis++;
|
|
while (true) {
|
|
|
|
Node *arg = _parse_and_reduce_expression(p_class, _static);
|
|
cparent->arguments.push_back(arg);
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
continue;
|
|
} else if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
|
|
_set_error("Expected ',' or ')'.");
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
parenthesis--;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
}
|
|
} else {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PERIOD) {
|
|
|
|
_set_error("Parent constructor call found for a class without inheritance.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!_enter_indent_block(block)) {
|
|
|
|
_set_error("Indented block expected.");
|
|
return;
|
|
}
|
|
|
|
FunctionNode *function = alloc_node<FunctionNode>();
|
|
function->name = name;
|
|
function->arguments = arguments;
|
|
function->default_values = default_values;
|
|
function->_static = _static;
|
|
function->line = fnline;
|
|
|
|
function->rpc_mode = rpc_mode;
|
|
rpc_mode = ScriptInstance::RPC_MODE_DISABLED;
|
|
|
|
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 = NULL;
|
|
|
|
//arguments
|
|
} break;
|
|
case GDTokenizer::TK_PR_SIGNAL: {
|
|
tokenizer->advance();
|
|
|
|
if (!tokenizer->is_token_literal()) {
|
|
_set_error("Expected identifier after 'signal'.");
|
|
return;
|
|
}
|
|
|
|
ClassNode::Signal sig;
|
|
sig.name = tokenizer->get_token_identifier();
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_OPEN) {
|
|
tokenizer->advance();
|
|
while (true) {
|
|
if (tokenizer->get_token() == GDTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
continue;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
tokenizer->advance();
|
|
break;
|
|
}
|
|
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
_set_error("Expected identifier in signal argument.");
|
|
return;
|
|
}
|
|
|
|
sig.arguments.push_back(tokenizer->get_token_identifier());
|
|
tokenizer->advance();
|
|
|
|
while (tokenizer->get_token() == GDTokenizer::TK_NEWLINE) {
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
} else if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected ',' or ')' after signal parameter identifier.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
p_class->_signals.push_back(sig);
|
|
|
|
if (!_end_statement()) {
|
|
_set_error("Expected end of statement (signal)");
|
|
return;
|
|
}
|
|
} break;
|
|
case GDTokenizer::TK_PR_EXPORT: {
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_OPEN) {
|
|
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() == GDTokenizer::TK_BUILT_IN_TYPE) {
|
|
|
|
Variant::Type type = tokenizer->get_token_type();
|
|
if (type == Variant::NIL) {
|
|
_set_error("Can't export null type.");
|
|
return;
|
|
}
|
|
current_export.type = type;
|
|
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
|
tokenizer->advance();
|
|
|
|
String hint_prefix = "";
|
|
|
|
if (type == Variant::ARRAY && tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
|
|
while (tokenizer->get_token() == GDTokenizer::TK_BUILT_IN_TYPE) {
|
|
type = tokenizer->get_token_type();
|
|
|
|
tokenizer->advance();
|
|
|
|
if (type == Variant::ARRAY) {
|
|
hint_prefix += itos(Variant::ARRAY) + ":";
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
}
|
|
} else {
|
|
hint_prefix += itos(type);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
// hint expected next!
|
|
tokenizer->advance();
|
|
|
|
switch (type) {
|
|
|
|
case Variant::INT: {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") {
|
|
|
|
//current_export.hint=PROPERTY_HINT_ALL_FLAGS;
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
break;
|
|
}
|
|
if (tokenizer->get_token() != GDTokenizer::TK_COMMA) {
|
|
_set_error("Expected ')' or ',' in bit flags hint.");
|
|
return;
|
|
}
|
|
|
|
current_export.hint = PROPERTY_HINT_FLAGS;
|
|
tokenizer->advance();
|
|
|
|
bool first = true;
|
|
while (true) {
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a string constant in 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();
|
|
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE)
|
|
break;
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_COMMA) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected ')' or ',' in named bit flags hint.");
|
|
return;
|
|
}
|
|
tokenizer->advance();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::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() != GDTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a string constant in 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();
|
|
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE)
|
|
break;
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_COMMA) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected ')' or ',' in enumeration hint.");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
}; //fallthrough to use the same
|
|
case Variant::REAL: {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EASE") {
|
|
current_export.hint = PROPERTY_HINT_EXP_EASING;
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected ')' in hint.");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// range
|
|
if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EXP") {
|
|
|
|
current_export.hint = PROPERTY_HINT_EXP_RANGE;
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE)
|
|
break;
|
|
else if (tokenizer->get_token() != GDTokenizer::TK_COMMA) {
|
|
_set_error("Expected ')' or ',' in exponential range hint.");
|
|
return;
|
|
}
|
|
tokenizer->advance();
|
|
} else
|
|
current_export.hint = PROPERTY_HINT_RANGE;
|
|
|
|
float sign = 1.0;
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_OP_SUB) {
|
|
sign = -1;
|
|
tokenizer->advance();
|
|
}
|
|
if (tokenizer->get_token() != GDTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a range in numeric hint.");
|
|
return;
|
|
}
|
|
|
|
current_export.hint_string = rtos(sign * double(tokenizer->get_token_constant()));
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
current_export.hint_string = "0," + current_export.hint_string;
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_COMMA) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected ',' or ')' in numeric range hint.");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
|
|
sign = 1.0;
|
|
if (tokenizer->get_token() == GDTokenizer::TK_OP_SUB) {
|
|
sign = -1;
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a number as upper bound in numeric range hint.");
|
|
return;
|
|
}
|
|
|
|
current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant()));
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE)
|
|
break;
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_COMMA) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected ',' or ')' in numeric range hint.");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
sign = 1.0;
|
|
if (tokenizer->get_token() == GDTokenizer::TK_OP_SUB) {
|
|
sign = -1;
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a number as step in numeric range hint.");
|
|
return;
|
|
}
|
|
|
|
current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant()));
|
|
tokenizer->advance();
|
|
|
|
} break;
|
|
case Variant::STRING: {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::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() != GDTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected a string constant in 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();
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE)
|
|
break;
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_COMMA) {
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected ')' or ',' in enumeration hint.");
|
|
return;
|
|
}
|
|
tokenizer->advance();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "DIR") {
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE)
|
|
current_export.hint = PROPERTY_HINT_DIR;
|
|
else if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_IDENTIFIER || !(tokenizer->get_token_identifier() == "GLOBAL")) {
|
|
_set_error("Expected 'GLOBAL' after comma in 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;
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected ')' in hint.");
|
|
return;
|
|
}
|
|
} else {
|
|
_set_error("Expected ')' or ',' in hint.");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FILE") {
|
|
|
|
current_export.hint = PROPERTY_HINT_FILE;
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::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;
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PARENTHESIS_CLOSE)
|
|
break;
|
|
else if (tokenizer->get_token() == GDTokenizer::TK_COMMA)
|
|
tokenizer->advance();
|
|
else {
|
|
_set_error("Expected ')' or ',' in hint.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::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();
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected ')' in hint.");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "MULTILINE") {
|
|
|
|
current_export.hint = PROPERTY_HINT_MULTILINE_TEXT;
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
_set_error("Expected ')' in hint.");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
} break;
|
|
case Variant::COLOR: {
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::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;
|
|
}
|
|
tokenizer->advance();
|
|
|
|
} break;
|
|
default: {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Type '" + Variant::get_type_name(type) + "' can't take hints.");
|
|
return;
|
|
} break;
|
|
}
|
|
}
|
|
if (current_export.type == Variant::ARRAY && !hint_prefix.empty()) {
|
|
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_NONE;
|
|
}
|
|
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_IDENTIFIER) {
|
|
|
|
String identifier = tokenizer->get_token_identifier();
|
|
if (!ClassDB::is_parent_class(identifier, "Resource")) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Export hint not a type or resource.");
|
|
}
|
|
|
|
current_export.type = Variant::OBJECT;
|
|
current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
|
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
|
|
|
current_export.hint_string = identifier;
|
|
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PARENTHESIS_CLOSE) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected ')' or ',' after export hint.");
|
|
return;
|
|
}
|
|
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_VAR && tokenizer->get_token() != GDTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDTokenizer::TK_PR_SLAVE && tokenizer->get_token() != GDTokenizer::TK_PR_SYNC) {
|
|
|
|
current_export = PropertyInfo();
|
|
_set_error("Expected 'var', 'onready', 'remote', 'master', 'slave' or 'sync'.");
|
|
return;
|
|
}
|
|
|
|
continue;
|
|
} break;
|
|
case GDTokenizer::TK_PR_ONREADY: {
|
|
|
|
//may be fallthrough from export, ignore if so
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_VAR) {
|
|
_set_error("Expected 'var'.");
|
|
return;
|
|
}
|
|
|
|
continue;
|
|
} break;
|
|
case GDTokenizer::TK_PR_REMOTE: {
|
|
|
|
//may be fallthrough from export, ignore if so
|
|
tokenizer->advance();
|
|
if (current_export.type) {
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_VAR) {
|
|
_set_error("Expected 'var'.");
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_VAR && tokenizer->get_token() != GDTokenizer::TK_PR_FUNCTION) {
|
|
_set_error("Expected 'var' or 'func'.");
|
|
return;
|
|
}
|
|
}
|
|
rpc_mode = ScriptInstance::RPC_MODE_REMOTE;
|
|
|
|
continue;
|
|
} break;
|
|
case GDTokenizer::TK_PR_MASTER: {
|
|
|
|
//may be fallthrough from export, ignore if so
|
|
tokenizer->advance();
|
|
if (current_export.type) {
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_VAR) {
|
|
_set_error("Expected 'var'.");
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_VAR && tokenizer->get_token() != GDTokenizer::TK_PR_FUNCTION) {
|
|
_set_error("Expected 'var' or 'func'.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
rpc_mode = ScriptInstance::RPC_MODE_MASTER;
|
|
continue;
|
|
} break;
|
|
case GDTokenizer::TK_PR_SLAVE: {
|
|
|
|
//may be fallthrough from export, ignore if so
|
|
tokenizer->advance();
|
|
if (current_export.type) {
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_VAR) {
|
|
_set_error("Expected 'var'.");
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_VAR && tokenizer->get_token() != GDTokenizer::TK_PR_FUNCTION) {
|
|
_set_error("Expected 'var' or 'func'.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
rpc_mode = ScriptInstance::RPC_MODE_SLAVE;
|
|
continue;
|
|
} break;
|
|
case GDTokenizer::TK_PR_SYNC: {
|
|
|
|
//may be fallthrough from export, ignore if so
|
|
tokenizer->advance();
|
|
if (tokenizer->get_token() != GDTokenizer::TK_PR_VAR && tokenizer->get_token() != GDTokenizer::TK_PR_FUNCTION) {
|
|
if (current_export.type)
|
|
_set_error("Expected 'var'.");
|
|
else
|
|
_set_error("Expected 'var' or 'func'.");
|
|
return;
|
|
}
|
|
|
|
rpc_mode = ScriptInstance::RPC_MODE_SYNC;
|
|
continue;
|
|
} break;
|
|
case GDTokenizer::TK_PR_VAR: {
|
|
//variale declaration and (eventual) initialization
|
|
|
|
ClassNode::Member member;
|
|
bool autoexport = tokenizer->get_token(-1) == GDTokenizer::TK_PR_EXPORT;
|
|
if (current_export.type != Variant::NIL) {
|
|
member._export = current_export;
|
|
current_export = PropertyInfo();
|
|
}
|
|
|
|
bool onready = tokenizer->get_token(-1) == GDTokenizer::TK_PR_ONREADY;
|
|
|
|
tokenizer->advance();
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
|
|
_set_error("Expected identifier for member variable name.");
|
|
return;
|
|
}
|
|
|
|
member.identifier = tokenizer->get_token_literal();
|
|
member.expression = NULL;
|
|
member._export.name = member.identifier;
|
|
member.line = tokenizer->get_token_line();
|
|
member.rpc_mode = rpc_mode;
|
|
|
|
tokenizer->advance();
|
|
|
|
rpc_mode = ScriptInstance::RPC_MODE_DISABLED;
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::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);
|
|
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) {
|
|
if (1) /*(subexpr->type==Node::TYPE_ARRAY) {
|
|
|
|
member._export.type=Variant::ARRAY;
|
|
|
|
} else if (subexpr->type==Node::TYPE_DICTIONARY) {
|
|
|
|
member._export.type=Variant::DICTIONARY;
|
|
|
|
} else*/ {
|
|
|
|
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 infering export type.");
|
|
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 = obj->cast_to<Resource>();
|
|
if (res == NULL) {
|
|
_set_error("Exported constant not a type or resource.");
|
|
return;
|
|
}
|
|
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
|
member._export.hint_string = res->get_class();
|
|
}
|
|
}
|
|
}
|
|
#ifdef TOOLS_ENABLED
|
|
if (subexpr->type == Node::TYPE_CONSTANT && member._export.type != Variant::NIL) {
|
|
|
|
ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
|
|
if (cn->value.get_type() != Variant::NIL) {
|
|
member.default_value = cn->value;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
IdentifierNode *id = alloc_node<IdentifierNode>();
|
|
id->name = member.identifier;
|
|
|
|
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 *nl = alloc_node<NewLineNode>();
|
|
nl->line = line;
|
|
if (onready)
|
|
p_class->ready->statements.push_back(nl);
|
|
else
|
|
p_class->initializer->statements.push_back(nl);
|
|
#endif
|
|
if (onready)
|
|
p_class->ready->statements.push_back(op);
|
|
else
|
|
p_class->initializer->statements.push_back(op);
|
|
|
|
} else {
|
|
|
|
if (autoexport) {
|
|
|
|
_set_error("Type-less export needs a constant expression assigned to infer type.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_PR_SETGET) {
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_COMMA) {
|
|
//just comma means using only getter
|
|
if (!tokenizer->is_token_literal()) {
|
|
_set_error("Expected identifier for setter function after 'setget'.");
|
|
}
|
|
|
|
member.setter = tokenizer->get_token_literal();
|
|
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
//there is a getter
|
|
tokenizer->advance();
|
|
|
|
if (!tokenizer->is_token_literal()) {
|
|
_set_error("Expected identifier for getter function after ','.");
|
|
}
|
|
|
|
member.getter = tokenizer->get_token_literal();
|
|
tokenizer->advance();
|
|
}
|
|
}
|
|
|
|
p_class->variables.push_back(member);
|
|
|
|
if (!_end_statement()) {
|
|
_set_error("Expected end of statement (continue)");
|
|
return;
|
|
}
|
|
} break;
|
|
case GDTokenizer::TK_PR_CONST: {
|
|
//variale declaration and (eventual) initialization
|
|
|
|
ClassNode::Constant constant;
|
|
|
|
tokenizer->advance();
|
|
if (!tokenizer->is_token_literal(0, true)) {
|
|
|
|
_set_error("Expected name (identifier) for constant.");
|
|
return;
|
|
}
|
|
|
|
constant.identifier = tokenizer->get_token_literal();
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() != GDTokenizer::TK_OP_ASSIGN) {
|
|
_set_error("Constant expects assignment.");
|
|
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 constant expression");
|
|
}
|
|
constant.expression = subexpr;
|
|
|
|
p_class->constant_expressions.push_back(constant);
|
|
|
|
if (!_end_statement()) {
|
|
_set_error("Expected end of statement (constant)");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
case GDTokenizer::TK_PR_ENUM: {
|
|
//mutiple constant declarations..
|
|
|
|
int last_assign = -1; // Incremented by 1 right before the assingment.
|
|
String enum_name;
|
|
Dictionary enum_dict;
|
|
|
|
tokenizer->advance();
|
|
if (tokenizer->is_token_literal(0, true)) {
|
|
enum_name = tokenizer->get_token_literal();
|
|
tokenizer->advance();
|
|
}
|
|
if (tokenizer->get_token() != GDTokenizer::TK_CURLY_BRACKET_OPEN) {
|
|
_set_error("Expected '{' in enum declaration");
|
|
return;
|
|
}
|
|
tokenizer->advance();
|
|
|
|
while (true) {
|
|
if (tokenizer->get_token() == GDTokenizer::TK_NEWLINE) {
|
|
|
|
tokenizer->advance(); // Ignore newlines
|
|
} else if (tokenizer->get_token() == GDTokenizer::TK_CURLY_BRACKET_CLOSE) {
|
|
|
|
tokenizer->advance();
|
|
break; // End of enum
|
|
} else if (!tokenizer->is_token_literal(0, true)) {
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_EOF) {
|
|
_set_error("Unexpected end of file.");
|
|
} else {
|
|
_set_error(String("Unexpected ") + GDTokenizer::get_token_name(tokenizer->get_token()) + ", expected identifier");
|
|
}
|
|
|
|
return;
|
|
} else { // tokenizer->is_token_literal(0, true)
|
|
ClassNode::Constant constant;
|
|
|
|
constant.identifier = tokenizer->get_token_literal();
|
|
|
|
tokenizer->advance();
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::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 constant expression");
|
|
}
|
|
|
|
const ConstantNode *subexpr_const = static_cast<const ConstantNode *>(subexpr);
|
|
|
|
if (subexpr_const->value.get_type() != Variant::INT) {
|
|
_set_error("Expected an int value for enum");
|
|
}
|
|
|
|
last_assign = subexpr_const->value;
|
|
|
|
constant.expression = subexpr;
|
|
|
|
} else {
|
|
last_assign = last_assign + 1;
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = last_assign;
|
|
constant.expression = cn;
|
|
}
|
|
|
|
if (tokenizer->get_token() == GDTokenizer::TK_COMMA) {
|
|
tokenizer->advance();
|
|
}
|
|
|
|
if (enum_name != "") {
|
|
const ConstantNode *cn = static_cast<const ConstantNode *>(constant.expression);
|
|
enum_dict[constant.identifier] = cn->value;
|
|
}
|
|
|
|
p_class->constant_expressions.push_back(constant);
|
|
}
|
|
}
|
|
|
|
if (enum_name != "") {
|
|
ClassNode::Constant enum_constant;
|
|
enum_constant.identifier = enum_name;
|
|
ConstantNode *cn = alloc_node<ConstantNode>();
|
|
cn->value = enum_dict;
|
|
enum_constant.expression = cn;
|
|
p_class->constant_expressions.push_back(enum_constant);
|
|
}
|
|
|
|
if (!_end_statement()) {
|
|
_set_error("Expected end of statement (enum)");
|
|
return;
|
|
}
|
|
|
|
} break;
|
|
|
|
case GDTokenizer::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;
|
|
|
|
default: {
|
|
|
|
_set_error(String() + "Unexpected token: " + tokenizer->get_token_name(tokenizer->get_token()) + ":" + tokenizer->get_token_identifier());
|
|
return;
|
|
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GDParser::_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;
|
|
}
|
|
|
|
String GDParser::get_error() const {
|
|
|
|
return error;
|
|
}
|
|
|
|
int GDParser::get_error_line() const {
|
|
|
|
return error_line;
|
|
}
|
|
int GDParser::get_error_column() const {
|
|
|
|
return error_column;
|
|
}
|
|
|
|
Error GDParser::_parse(const String &p_base_path) {
|
|
|
|
base_path = p_base_path;
|
|
|
|
clear();
|
|
|
|
//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() == GDTokenizer::TK_ERROR) {
|
|
error_set = false;
|
|
_set_error("Parse Error: " + tokenizer->get_token_error());
|
|
}
|
|
|
|
if (error_set) {
|
|
|
|
return ERR_PARSE_ERROR;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
Error GDParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path, const String &p_self_path) {
|
|
|
|
for_completion = false;
|
|
validating = false;
|
|
completion_type = COMPLETION_NONE;
|
|
completion_node = NULL;
|
|
completion_class = NULL;
|
|
completion_function = NULL;
|
|
completion_block = NULL;
|
|
completion_found = false;
|
|
current_block = NULL;
|
|
current_class = NULL;
|
|
current_function = NULL;
|
|
|
|
self_path = p_self_path;
|
|
GDTokenizerBuffer *tb = memnew(GDTokenizerBuffer);
|
|
tb->set_code_buffer(p_bytecode);
|
|
tokenizer = tb;
|
|
Error ret = _parse(p_base_path);
|
|
memdelete(tb);
|
|
tokenizer = NULL;
|
|
return ret;
|
|
}
|
|
|
|
Error GDParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion) {
|
|
|
|
completion_type = COMPLETION_NONE;
|
|
completion_node = NULL;
|
|
completion_class = NULL;
|
|
completion_function = NULL;
|
|
completion_block = NULL;
|
|
completion_found = false;
|
|
current_block = NULL;
|
|
current_class = NULL;
|
|
|
|
current_function = NULL;
|
|
|
|
self_path = p_self_path;
|
|
GDTokenizerText *tt = memnew(GDTokenizerText);
|
|
tt->set_code(p_code);
|
|
|
|
validating = p_just_validate;
|
|
for_completion = p_for_completion;
|
|
tokenizer = tt;
|
|
Error ret = _parse(p_base_path);
|
|
memdelete(tt);
|
|
tokenizer = NULL;
|
|
return ret;
|
|
}
|
|
|
|
bool GDParser::is_tool_script() const {
|
|
|
|
return (head && head->type == Node::TYPE_CLASS && static_cast<const ClassNode *>(head)->tool);
|
|
}
|
|
|
|
const GDParser::Node *GDParser::get_parse_tree() const {
|
|
|
|
return head;
|
|
}
|
|
|
|
void GDParser::clear() {
|
|
|
|
while (list) {
|
|
|
|
Node *l = list;
|
|
list = list->next;
|
|
memdelete(l);
|
|
}
|
|
|
|
head = NULL;
|
|
list = NULL;
|
|
|
|
completion_type = COMPLETION_NONE;
|
|
completion_node = NULL;
|
|
completion_class = NULL;
|
|
completion_function = NULL;
|
|
completion_block = NULL;
|
|
current_block = NULL;
|
|
current_class = NULL;
|
|
|
|
completion_found = false;
|
|
rpc_mode = ScriptInstance::RPC_MODE_DISABLED;
|
|
|
|
current_function = NULL;
|
|
|
|
validating = false;
|
|
for_completion = false;
|
|
error_set = false;
|
|
tab_level.clear();
|
|
tab_level.push_back(0);
|
|
error_line = 0;
|
|
error_column = 0;
|
|
pending_newline = -1;
|
|
parenthesis = 0;
|
|
current_export.type = Variant::NIL;
|
|
error = "";
|
|
}
|
|
|
|
GDParser::CompletionType GDParser::get_completion_type() {
|
|
|
|
return completion_type;
|
|
}
|
|
|
|
StringName GDParser::get_completion_cursor() {
|
|
|
|
return completion_cursor;
|
|
}
|
|
|
|
int GDParser::get_completion_line() {
|
|
|
|
return completion_line;
|
|
}
|
|
|
|
Variant::Type GDParser::get_completion_built_in_constant() {
|
|
|
|
return completion_built_in_constant;
|
|
}
|
|
|
|
GDParser::Node *GDParser::get_completion_node() {
|
|
|
|
return completion_node;
|
|
}
|
|
|
|
GDParser::BlockNode *GDParser::get_completion_block() {
|
|
|
|
return completion_block;
|
|
}
|
|
|
|
GDParser::ClassNode *GDParser::get_completion_class() {
|
|
|
|
return completion_class;
|
|
}
|
|
|
|
GDParser::FunctionNode *GDParser::get_completion_function() {
|
|
|
|
return completion_function;
|
|
}
|
|
|
|
int GDParser::get_completion_argument_index() {
|
|
|
|
return completion_argument;
|
|
}
|
|
|
|
int GDParser::get_completion_identifier_is_function() {
|
|
|
|
return completion_ident_is_call;
|
|
}
|
|
|
|
GDParser::GDParser() {
|
|
|
|
head = NULL;
|
|
list = NULL;
|
|
tokenizer = NULL;
|
|
pending_newline = -1;
|
|
clear();
|
|
}
|
|
|
|
GDParser::~GDParser() {
|
|
|
|
clear();
|
|
}
|