Reintroduce code completion

This commit is contained in:
George Marques 2020-07-06 12:24:24 -03:00
parent b6a2628c48
commit aa09b4f85d
No known key found for this signature in database
GPG Key ID: 046BD46A3201E43D
9 changed files with 2903 additions and 30 deletions

View File

@ -1778,6 +1778,7 @@ CodeTextEditor::CodeTextEditor() {
cs.push_back("(");
cs.push_back("=");
cs.push_back("$");
cs.push_back("@");
text_editor->set_completion(true, cs);
idle->connect("timeout", callable_mp(this, &CodeTextEditor::_text_changed_idle_timeout));

View File

@ -471,7 +471,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
members_cache.push_back(member.variable->export_info);
Variant default_value;
if (member.variable->initializer->is_constant) {
if (member.variable->initializer && member.variable->initializer->is_constant) {
default_value = member.variable->initializer->reduced_value;
}
member_default_values_cache[member.variable->identifier->name] = default_value;

View File

@ -1134,6 +1134,10 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expression) {
// This one makes some magic happen.
if (p_expression == nullptr) {
return;
}
if (p_expression->reduced) {
// Don't do this more than once.
return;
@ -1248,6 +1252,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
reduce_expression(p_assignment->assignee);
reduce_expression(p_assignment->assigned_value);
if (p_assignment->assigned_value == nullptr || p_assignment->assignee == nullptr) {
return;
}
if (p_assignment->assignee->get_datatype().is_constant) {
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
}
@ -2038,6 +2046,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
// Reduce index first. If it's a constant StringName, use attribute instead.
if (!p_subscript->is_attribute) {
if (p_subscript->index == nullptr) {
return;
}
reduce_expression(p_subscript->index);
if (p_subscript->index->is_constant && p_subscript->index->reduced_value.get_type() == Variant::STRING_NAME) {
@ -2053,6 +2064,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
}
if (p_subscript->is_attribute) {
if (p_subscript->attribute == nullptr) {
return;
}
if (p_subscript->base->is_constant) {
// Just try to get it.
bool valid = false;

View File

@ -165,10 +165,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
}
} break;
default: {
case GDScriptParser::DataType::UNRESOLVED: {
ERR_PRINT("Parser bug: converting unresolved type.");
return GDScriptDataType();
}
default:
break; // FIXME
}
return result;
@ -1325,10 +1327,9 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return -1;
}
// FIXME: Actually check type.
GDScriptDataType assign_type; // = _gdtype_from_datatype(on->arguments[0]->get_datatype());
GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype());
if (assign_type.has_type && !assignment->get_datatype().is_set()) {
if (assign_type.has_type && !assignment->assigned_value->get_datatype().is_variant()) {
// Typed assignment
switch (assign_type.kind) {
case GDScriptDataType::BUILTIN: {

File diff suppressed because it is too large Load Diff

View File

@ -41,6 +41,10 @@
#include "core/string_builder.h"
#endif // DEBUG_ENABLED
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
#endif // TOOLS_ENABLED
static HashMap<StringName, Variant::Type> builtin_types;
Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
if (builtin_types.empty()) {
@ -99,6 +103,14 @@ GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringNam
return GDScriptFunctions::FUNC_MAX;
}
void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const {
List<StringName> keys;
valid_annotations.get_key_list(&keys);
for (const List<StringName>::Element *E = keys.front(); E != nullptr; E = E->next()) {
r_annotations->push_back(valid_annotations[E->get()].info);
}
}
GDScriptParser::GDScriptParser() {
// Register valid annotations.
// TODO: Should this be static?
@ -239,9 +251,114 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
}
}
void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) {
if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
return;
}
CompletionContext context;
context.type = p_type;
context.current_class = current_class;
context.current_function = current_function;
context.current_suite = current_suite;
context.current_line = tokenizer.get_cursor_line();
context.current_argument = p_argument;
context.node = p_node;
completion_context = context;
}
void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) {
if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
return;
}
CompletionContext context;
context.type = p_type;
context.current_class = current_class;
context.current_function = current_function;
context.current_suite = current_suite;
context.current_line = tokenizer.get_cursor_line();
context.builtin_type = p_builtin_type;
completion_context = context;
}
void GDScriptParser::push_completion_call(Node *p_call) {
if (!for_completion) {
return;
}
CompletionCall call;
call.call = p_call;
call.argument = 0;
completion_call_stack.push_back(call);
if (previous.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) {
completion_call = call;
}
}
void GDScriptParser::pop_completion_call() {
if (!for_completion) {
return;
}
ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to pop empty completion call stack");
completion_call_stack.pop_back();
}
void GDScriptParser::set_last_completion_call_arg(int p_argument) {
if (!for_completion || passed_cursor) {
return;
}
ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to set argument on empty completion call stack");
completion_call_stack.back()->get().argument = p_argument;
}
Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) {
clear();
tokenizer.set_source_code(p_source_code);
String source = p_source_code;
int cursor_line = -1;
int cursor_column = -1;
for_completion = p_for_completion;
int tab_size = 4;
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size");
}
#endif // TOOLS_ENABLED
if (p_for_completion) {
// Remove cursor sentinel char.
const Vector<String> lines = p_source_code.split("\n");
cursor_line = 1;
cursor_column = 1;
for (int i = 0; i < lines.size(); i++) {
bool found = false;
const String &line = lines[i];
for (int j = 0; j < line.size(); j++) {
if (line[j] == CharType(0xFFFF)) {
found = true;
break;
} else if (line[j] == '\t') {
cursor_column += tab_size - 1;
}
cursor_column++;
}
if (found) {
break;
}
cursor_line++;
cursor_column = 1;
}
source = source.replace_first(String::chr(0xFFFF), String());
}
tokenizer.set_source_code(source);
tokenizer.set_cursor_position(cursor_line, cursor_column);
script_path = p_script_path;
current = tokenizer.scan();
// Avoid error as the first token.
@ -271,6 +388,12 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
if (current.type == GDScriptTokenizer::Token::TK_EOF) {
ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream.");
}
if (for_completion && !completion_call_stack.empty()) {
if (completion_call.call == nullptr && tokenizer.is_past_cursor()) {
completion_call = completion_call_stack.back()->get();
passed_cursor = true;
}
}
previous = current;
current = tokenizer.scan();
while (current.type == GDScriptTokenizer::Token::ERROR) {
@ -513,6 +636,8 @@ void GDScriptParser::parse_class_name() {
void GDScriptParser::parse_extends() {
current_class->extends_used = true;
int chain_index = 0;
if (match(GDScriptTokenizer::Token::LITERAL)) {
if (previous.literal.get_type() != Variant::STRING) {
push_error(vformat(R"(Only strings or identifiers can be used after "extends", found "%s" instead.)", Variant::get_type_name(previous.literal.get_type())));
@ -525,12 +650,15 @@ void GDScriptParser::parse_extends() {
}
}
make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) {
return;
}
current_class->extends.push_back(previous.literal);
while (match(GDScriptTokenizer::Token::PERIOD)) {
make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) {
return;
}
@ -645,10 +773,13 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
// Infer type.
variable->infer_datatype = true;
} else {
if (p_allow_property && check(GDScriptTokenizer::Token::IDENTIFIER)) {
// Check if get or set.
if (current.get_identifier() == "get" || current.get_identifier() == "set") {
return parse_property(variable, false);
if (p_allow_property) {
make_completion_context(COMPLETION_PROPERTY_DECLARATION_OR_TYPE, variable);
if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
// Check if get or set.
if (current.get_identifier() == "get" || current.get_identifier() == "set") {
return parse_property(variable, false);
}
}
}
@ -685,12 +816,14 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var
}
}
VariableNode *property = p_variable;
make_completion_context(COMPLETION_PROPERTY_DECLARATION, property);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) {
return nullptr;
}
VariableNode *property = p_variable;
IdentifierNode *function = parse_identifier();
if (check(GDScriptTokenizer::Token::EQUAL)) {
@ -770,6 +903,7 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
case VariableNode::PROP_SETGET:
consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")");
make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)")) {
p_variable->setter_pointer = parse_identifier();
}
@ -788,6 +922,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
break;
case VariableNode::PROP_SETGET:
consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")");
make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)")) {
p_variable->getter_pointer = parse_identifier();
}
@ -844,6 +979,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
parameter->infer_datatype = true;
} else {
// Parse type.
make_completion_context(COMPLETION_TYPE_NAME, parameter);
parameter->datatype_specifier = parse_type();
}
}
@ -964,11 +1100,13 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
_static = true;
}
FunctionNode *function = alloc_node<FunctionNode>();
make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
return nullptr;
}
FunctionNode *function = alloc_node<FunctionNode>();
FunctionNode *previous_function = current_function;
current_function = function;
@ -1015,6 +1153,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*");
if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
function->return_type = parse_type(true);
}
@ -1033,6 +1172,8 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
annotation->name = previous.literal;
make_completion_context(COMPLETION_ANNOTATION, annotation);
bool valid = true;
if (!valid_annotations.has(annotation->name)) {
@ -1049,8 +1190,13 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
// Arguments.
push_completion_call(annotation);
make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
int argument = 0;
do {
make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument, true);
set_last_completion_call_arg(argument++, true);
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
valid = false;
@ -1061,6 +1207,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
}
pop_completion_call();
}
match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
@ -1075,7 +1222,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
void GDScriptParser::clear_unused_annotations() {
for (const List<AnnotationNode *>::Element *E = annotation_stack.front(); E != nullptr; E = E->next()) {
AnnotationNode *annotation = E->get();
push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name));
push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation);
}
annotation_stack.clear();
@ -1342,6 +1489,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
SuiteNode *suite = alloc_node<SuiteNode>();
suite->add_local(SuiteNode::Local(n_for->variable));
suite->parent_for = n_for;
n_for->loop = parse_suite(R"("for" block)", suite);
@ -1363,6 +1511,7 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token));
n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token));
n_if->true_block->parent_if = n_if;
if (n_if->true_block->has_continue) {
current_suite->has_continue = true;
@ -1722,6 +1871,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>();
identifier->name = previous.get_identifier();
make_completion_context(COMPLETION_IDENTIFIER, identifier);
if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
switch (declaration.type) {
@ -2000,6 +2151,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
}
AssignmentNode *assignment = alloc_node<AssignmentNode>();
make_completion_context(COMPLETION_ASSIGN, assignment);
bool has_operator = true;
switch (previous.type) {
case GDScriptTokenizer::Token::EQUAL:
@ -2168,11 +2320,26 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p
GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) {
SubscriptNode *attribute = alloc_node<SubscriptNode>();
if (for_completion) {
bool is_builtin = false;
if (p_previous_operand->type == Node::IDENTIFIER) {
const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand);
Variant::Type builtin_type = get_builtin_type(id->name);
if (builtin_type < Variant::VARIANT_MAX) {
make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT, builtin_type, true);
is_builtin = true;
}
}
if (!is_builtin) {
make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true);
}
}
attribute->is_attribute = true;
attribute->base = p_previous_operand;
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) {
return nullptr;
return attribute;
}
attribute->attribute = parse_identifier();
@ -2182,6 +2349,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) {
SubscriptNode *subscript = alloc_node<SubscriptNode>();
make_completion_context(COMPLETION_SUBSCRIPT, subscript);
subscript->base = p_previous_operand;
subscript->index = parse_expression(false);
@ -2222,6 +2391,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
call->function_name = current_function->identifier->name;
} else {
consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)");
make_completion_context(COMPLETION_SUPER_METHOD, call);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {
pop_multiline();
return nullptr;
@ -2251,7 +2421,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Arguments.
push_completion_call(call);
make_completion_context(COMPLETION_CALL_ARGUMENTS, call, 0);
int argument = 0;
do {
make_completion_context(COMPLETION_CALL_ARGUMENTS, call, argument++);
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
break;
@ -2263,6 +2437,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
call->arguments.push_back(argument);
}
} while (match(GDScriptTokenizer::Token::COMMA));
pop_completion_call();
}
pop_multiline();
@ -2278,11 +2453,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
return nullptr;
}
GetNodeNode *get_node = alloc_node<GetNodeNode>();
make_completion_context(COMPLETION_GET_NODE, get_node);
get_node->string = parse_literal();
return get_node;
} else if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
GetNodeNode *get_node = alloc_node<GetNodeNode>();
int chain_position = 0;
do {
make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) {
return nullptr;
}
@ -2303,6 +2481,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
push_multiline(true);
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)");
make_completion_context(COMPLETION_RESOURCE_PATH, preload);
push_completion_call(preload);
preload->path = parse_expression(false);
if (preload->path == nullptr) {
@ -2332,6 +2513,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
}
}
pop_completion_call();
pop_multiline();
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*");
@ -2355,6 +2538,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNo
}
GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
TypeNode *type = alloc_node<TypeNode>();
make_completion_context(p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME, type);
if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {
if (match(GDScriptTokenizer::Token::VOID)) {
if (p_allow_void) {
@ -2368,12 +2553,13 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
return nullptr;
}
TypeNode *type = alloc_node<TypeNode>();
IdentifierNode *type_element = parse_identifier();
type->type_chain.push_back(type_element);
int chain_index = 1;
while (match(GDScriptTokenizer::Token::PERIOD)) {
make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++);
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)")) {
type_element = parse_identifier();
type->type_chain.push_back(type_element);

View File

@ -565,6 +565,9 @@ public:
bool has_member(const StringName &p_name) const {
return members_indices.has(p_name);
}
bool has_function(const StringName &p_name) const {
return has_member(p_name) && members[members_indices[p_name]].type == Member::FUNCTION;
}
template <class T>
void add_member(T *p_member_node) {
members_indices[p_member_node->identifier->name] = members.size();
@ -908,7 +911,6 @@ public:
FunctionNode *parent_function = nullptr;
ForNode *parent_for = nullptr;
IfNode *parent_if = nullptr;
PatternNode *parent_pattern = nullptr;
bool has_return = false;
bool has_continue = false;
@ -1011,6 +1013,47 @@ public:
}
};
enum CompletionType {
COMPLETION_NONE,
COMPLETION_ANNOTATION, // Annotation (following @).
COMPLETION_ANNOTATION_ARGUMENTS, // Annotation arguments hint.
COMPLETION_ASSIGN, // Assignment based on type (e.g. enum values).
COMPLETION_ATTRIBUTE, // After id.| to look for members.
COMPLETION_BUILT_IN_TYPE_CONSTANT, // Constants inside a built-in type (e.g. Color.blue).
COMPLETION_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions).
// TODO: COMPLETION_DECLARATION, // Potential declaration (var, const, func).
COMPLETION_GET_NODE, // Get node with $ notation.
COMPLETION_IDENTIFIER, // List available identifiers in scope.
COMPLETION_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index.
COMPLETION_OVERRIDE_METHOD, // Override implementation, also for native virtuals.
COMPLETION_PROPERTY_DECLARATION, // Property declaration (get, set).
COMPLETION_PROPERTY_DECLARATION_OR_TYPE, // Property declaration (get, set) or a type hint.
COMPLETION_PROPERTY_METHOD, // Property setter or getter (list available methods).
COMPLETION_RESOURCE_PATH, // For load/preload.
COMPLETION_SUBSCRIPT, // Inside id[|].
COMPLETION_SUPER_METHOD, // After super.
COMPLETION_TYPE_ATTRIBUTE, // Attribute in type name (Type.|).
COMPLETION_TYPE_NAME, // Name of type (after :).
COMPLETION_TYPE_NAME_OR_VOID, // Same as TYPE_NAME, but allows void (in function return type).
};
struct CompletionContext {
CompletionType type = COMPLETION_NONE;
ClassNode *current_class = nullptr;
FunctionNode *current_function = nullptr;
SuiteNode *current_suite = nullptr;
int current_line = -1;
int current_argument = -1;
Variant::Type builtin_type = Variant::VARIANT_MAX;
Node *node = nullptr;
Object *base = nullptr;
};
struct CompletionCall {
Node *call = nullptr;
int argument = -1;
};
private:
friend class GDScriptAnalyzer;
@ -1038,6 +1081,11 @@ private:
FunctionNode *current_function = nullptr;
SuiteNode *current_suite = nullptr;
CompletionContext completion_context;
CompletionCall completion_call;
List<CompletionCall> completion_call_stack;
bool passed_cursor = false;
typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target);
struct AnnotationInfo {
enum TargetKind {
@ -1100,6 +1148,12 @@ private:
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String());
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols);
void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false);
void push_completion_call(Node *p_call);
void pop_completion_call();
void set_last_completion_call_arg(int p_argument);
GDScriptTokenizer::Token advance();
bool match(GDScriptTokenizer::Token::Type p_token_type);
bool check(GDScriptTokenizer::Token::Type p_token_type);
@ -1187,6 +1241,10 @@ public:
static Variant::Type get_builtin_type(const StringName &p_type);
static GDScriptFunctions::Function get_builtin_function(const StringName &p_name);
CompletionContext get_completion_context() const { return completion_context; }
CompletionCall get_completion_call() const { return completion_call; }
void get_annotation_list(List<MethodInfo> *r_annotations) const;
const List<ParserError> &get_errors() const { return errors; }
const List<GDScriptWarning> &get_warnings() const { return warnings; }
const List<String> get_dependencies() const {

View File

@ -191,13 +191,26 @@ int GDScriptTokenizer::get_cursor_column() const {
return cursor_column;
}
bool GDScriptTokenizer::is_past_cursor() const {
if (line < cursor_line) {
return false;
}
if (line > cursor_line) {
return true;
}
if (column < cursor_column) {
return false;
}
return true;
}
CharType GDScriptTokenizer::_advance() {
if (unlikely(_is_at_end())) {
return '\0';
}
_current++;
position++;
column++;
position++;
if (column > rightmost_column) {
rightmost_column = column;
}
@ -246,7 +259,7 @@ static bool _is_binary_digit(CharType c) {
return (c == '0' || c == '1');
}
GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) const {
GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) {
Token token(p_type);
token.start_line = start_line;
token.end_line = line;
@ -254,17 +267,63 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) const
token.end_column = column;
token.leftmost_column = leftmost_column;
token.rightmost_column = rightmost_column;
token.source = String(_start, _current - _start);
if (p_type != Token::ERROR && cursor_line > -1) {
// Also count whitespace after token.
int offset = 0;
while (_peek(offset) == ' ' || _peek(offset) == '\t') {
offset++;
}
int last_column = column + offset;
// Check cursor position in token.
if (start_line == line) {
// Single line token.
if (cursor_line == start_line && cursor_column >= start_column && cursor_column <= last_column) {
token.cursor_position = cursor_column - start_column;
if (cursor_column == start_column) {
token.cursor_place = CURSOR_BEGINNING;
} else if (cursor_column < column) {
token.cursor_place = CURSOR_MIDDLE;
} else {
token.cursor_place = CURSOR_END;
}
}
} else {
// Multi line token.
if (cursor_line == start_line && cursor_column >= start_column) {
// Is in first line.
token.cursor_position = cursor_column - start_column;
if (cursor_column == start_column) {
token.cursor_place = CURSOR_BEGINNING;
} else {
token.cursor_place = CURSOR_MIDDLE;
}
} else if (cursor_line == line && cursor_column <= last_column) {
// Is in last line.
token.cursor_position = cursor_column - start_column;
if (cursor_column < column) {
token.cursor_place = CURSOR_MIDDLE;
} else {
token.cursor_place = CURSOR_END;
}
} else if (cursor_line > start_line && cursor_line < line) {
// Is in middle line.
token.cursor_position = CURSOR_MIDDLE;
}
}
}
return token;
}
GDScriptTokenizer::Token GDScriptTokenizer::make_literal(const Variant &p_literal) const {
GDScriptTokenizer::Token GDScriptTokenizer::make_literal(const Variant &p_literal) {
Token token = make_token(Token::LITERAL);
token.literal = p_literal;
return token;
}
GDScriptTokenizer::Token GDScriptTokenizer::make_identifier(const StringName &p_identifier) const {
GDScriptTokenizer::Token GDScriptTokenizer::make_identifier(const StringName &p_identifier) {
Token identifier = make_token(Token::IDENTIFIER);
identifier.literal = p_identifier;
return identifier;
@ -321,14 +380,14 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(CharType p_test, To
GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
if (!_is_alphanumeric(_peek())) {
return make_error("Expected annotation identifier after \"@\".");
push_error("Expected annotation identifier after \"@\".");
}
while (_is_alphanumeric(_peek())) {
// Consume all identifier characters.
_advance();
}
Token annotation = make_token(Token::ANNOTATION);
annotation.literal = StringName(String(_start, _current - _start));
annotation.literal = StringName(annotation.source);
return annotation;
}

View File

@ -38,6 +38,13 @@
class GDScriptTokenizer {
public:
enum CursorPlace {
CURSOR_NONE,
CURSOR_BEGINNING,
CURSOR_MIDDLE,
CURSOR_END,
};
struct Token {
enum Type {
EMPTY,
@ -155,6 +162,9 @@ public:
Variant literal;
int start_line = 0, end_line = 0, start_column = 0, end_column = 0;
int leftmost_column = 0, rightmost_column = 0; // Column span for multiline tokens.
int cursor_position = -1;
CursorPlace cursor_place = CURSOR_NONE;
String source;
const char *get_name() const;
// TODO: Allow some keywords as identifiers?
@ -174,8 +184,8 @@ private:
String source;
const CharType *_source = nullptr;
const CharType *_current = nullptr;
int line = 0, column = 0;
int cursor_line = 0, cursor_column = 0;
int line = -1, column = -1;
int cursor_line = -1, cursor_column = -1;
int tab_size = 4;
// Keep track of multichar tokens.
@ -209,9 +219,9 @@ private:
void push_error(const String &p_message);
void push_error(const Token &p_error);
Token make_paren_error(CharType p_paren);
Token make_token(Token::Type p_type) const;
Token make_literal(const Variant &p_literal) const;
Token make_identifier(const StringName &p_identifier) const;
Token make_token(Token::Type p_type);
Token make_literal(const Variant &p_literal);
Token make_identifier(const StringName &p_identifier);
Token check_vcs_marker(CharType p_test, Token::Type p_double_type);
void push_paren(CharType p_char);
bool pop_paren(CharType p_expected);
@ -231,6 +241,7 @@ public:
int get_cursor_column() const;
void set_cursor_position(int p_line, int p_column);
void set_multiline_mode(bool p_state);
bool is_past_cursor() const;
static String get_token_name(Token::Type p_token_type);
GDScriptTokenizer();