mirror of
https://github.com/godotengine/godot.git
synced 2025-11-01 22:21:18 +00:00
Add support for properties
This commit is contained in:
parent
34c28eb2b8
commit
886732ac2b
4 changed files with 488 additions and 28 deletions
|
|
@ -184,6 +184,11 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
|
|||
clear();
|
||||
tokenizer.set_source_code(p_source_code);
|
||||
current = tokenizer.scan();
|
||||
// Avoid error as the first token.
|
||||
while (current.type == GDScriptTokenizer::Token::ERROR) {
|
||||
push_error(current.literal);
|
||||
current = tokenizer.scan();
|
||||
}
|
||||
|
||||
push_multiline(false); // Keep one for the whole parsing.
|
||||
parse_program();
|
||||
|
|
@ -555,6 +560,10 @@ void GDScriptParser::parse_class_body() {
|
|||
}
|
||||
|
||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
|
||||
return parse_variable(true);
|
||||
}
|
||||
|
||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) {
|
||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -563,10 +572,26 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
|
|||
variable->identifier = parse_identifier();
|
||||
|
||||
if (match(GDScriptTokenizer::Token::COLON)) {
|
||||
if (check((GDScriptTokenizer::Token::EQUAL))) {
|
||||
if (check(GDScriptTokenizer::Token::NEWLINE)) {
|
||||
if (p_allow_property) {
|
||||
advance();
|
||||
|
||||
return parse_property(variable, true);
|
||||
} else {
|
||||
push_error(R"(Expected type after ":")");
|
||||
return nullptr;
|
||||
}
|
||||
} else if (check((GDScriptTokenizer::Token::EQUAL))) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
variable->datatype_specifier = parse_type();
|
||||
}
|
||||
|
|
@ -577,6 +602,14 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
|
|||
variable->initializer = parse_expression(false);
|
||||
}
|
||||
|
||||
if (p_allow_property && match(GDScriptTokenizer::Token::COLON)) {
|
||||
if (match(GDScriptTokenizer::Token::NEWLINE)) {
|
||||
return parse_property(variable, true);
|
||||
} else {
|
||||
return parse_property(variable, false);
|
||||
}
|
||||
}
|
||||
|
||||
end_statement("variable declaration");
|
||||
|
||||
variable->export_info.name = variable->identifier->name;
|
||||
|
|
@ -584,6 +617,125 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
|
|||
return variable;
|
||||
}
|
||||
|
||||
GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_variable, bool p_need_indent) {
|
||||
if (p_need_indent) {
|
||||
if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block for property after ":".)")) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
p_variable->property = VariableNode::PROP_SETGET;
|
||||
} else {
|
||||
p_variable->property = VariableNode::PROP_INLINE;
|
||||
if (!p_need_indent) {
|
||||
push_error("Property with inline code must go to an indented block.");
|
||||
}
|
||||
}
|
||||
|
||||
bool getter_used = false;
|
||||
bool setter_used = false;
|
||||
|
||||
// Run with a loop because order doesn't matter.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (function->name == "set") {
|
||||
if (setter_used) {
|
||||
push_error(R"(Properties can only have one setter.)");
|
||||
} else {
|
||||
parse_property_setter(property);
|
||||
setter_used = true;
|
||||
}
|
||||
} else if (function->name == "get") {
|
||||
if (getter_used) {
|
||||
push_error(R"(Properties can only have one getter.)");
|
||||
} else {
|
||||
parse_property_getter(property);
|
||||
getter_used = true;
|
||||
}
|
||||
} else {
|
||||
// TODO: Update message to only have the missing one if it's the case.
|
||||
push_error(R"(Expected "get" or "set" for property declaration.)");
|
||||
}
|
||||
|
||||
if (i == 0 && p_variable->property == VariableNode::PROP_SETGET) {
|
||||
if (match(GDScriptTokenizer::Token::COMMA)) {
|
||||
// Consume potential newline.
|
||||
if (match(GDScriptTokenizer::Token::NEWLINE)) {
|
||||
if (!p_need_indent) {
|
||||
push_error(R"(Inline setter/getter setting cannot span across multiple lines (use "\\"" if needed).)");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {
|
||||
break;
|
||||
}
|
||||
function = parse_identifier();
|
||||
}
|
||||
|
||||
if (p_variable->property == VariableNode::PROP_SETGET) {
|
||||
end_statement("property declaration");
|
||||
}
|
||||
|
||||
if (p_need_indent) {
|
||||
consume(GDScriptTokenizer::Token::DEDENT, R"(Expected end of indented block for property.)");
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
|
||||
switch (p_variable->property) {
|
||||
case VariableNode::PROP_INLINE:
|
||||
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
|
||||
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) {
|
||||
p_variable->setter_parameter = parse_identifier();
|
||||
}
|
||||
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*");
|
||||
consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*");
|
||||
|
||||
p_variable->setter = parse_suite("setter definition");
|
||||
break;
|
||||
|
||||
case VariableNode::PROP_SETGET:
|
||||
consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")");
|
||||
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)")) {
|
||||
p_variable->setter_pointer = parse_identifier();
|
||||
}
|
||||
break;
|
||||
case VariableNode::PROP_NONE:
|
||||
break; // Unreachable.
|
||||
}
|
||||
}
|
||||
|
||||
void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
|
||||
switch (p_variable->property) {
|
||||
case VariableNode::PROP_INLINE:
|
||||
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)");
|
||||
|
||||
p_variable->getter = parse_suite("getter definition");
|
||||
break;
|
||||
case VariableNode::PROP_SETGET:
|
||||
consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")");
|
||||
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)")) {
|
||||
p_variable->getter_pointer = parse_identifier();
|
||||
}
|
||||
break;
|
||||
case VariableNode::PROP_NONE:
|
||||
break; // Unreachable.
|
||||
}
|
||||
}
|
||||
|
||||
GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
|
||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
|
||||
return nullptr;
|
||||
|
|
@ -1021,6 +1173,9 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
|||
default: {
|
||||
// Expression statement.
|
||||
ExpressionNode *expression = parse_expression(true); // Allow assignment here.
|
||||
if (expression == nullptr) {
|
||||
push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name()));
|
||||
}
|
||||
end_statement("expression");
|
||||
result = expression;
|
||||
break;
|
||||
|
|
@ -1402,7 +1557,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
|
|||
ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token.");
|
||||
}
|
||||
IdentifierNode *identifier = alloc_node<IdentifierNode>();
|
||||
identifier->name = previous.literal;
|
||||
identifier->name = previous.get_identifier();
|
||||
return identifier;
|
||||
}
|
||||
|
||||
|
|
@ -3016,6 +3171,15 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
|
|||
push_text("Variable ");
|
||||
print_identifier(p_variable->identifier);
|
||||
|
||||
push_text(" : ");
|
||||
if (p_variable->datatype_specifier != nullptr) {
|
||||
print_type(p_variable->datatype_specifier);
|
||||
} else if (p_variable->infer_datatype) {
|
||||
push_text("<inferred type>");
|
||||
} else {
|
||||
push_text("Variant");
|
||||
}
|
||||
|
||||
increase_indent();
|
||||
|
||||
push_line();
|
||||
|
|
@ -3025,6 +3189,46 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
|
|||
} else {
|
||||
print_expression(p_variable->initializer);
|
||||
}
|
||||
push_line();
|
||||
|
||||
if (p_variable->property != VariableNode::PROP_NONE) {
|
||||
if (p_variable->getter != nullptr) {
|
||||
push_text("Get");
|
||||
if (p_variable->property == VariableNode::PROP_INLINE) {
|
||||
push_line(":");
|
||||
increase_indent();
|
||||
print_suite(p_variable->getter);
|
||||
decrease_indent();
|
||||
} else {
|
||||
push_line(" =");
|
||||
increase_indent();
|
||||
print_identifier(p_variable->getter_pointer);
|
||||
push_line();
|
||||
decrease_indent();
|
||||
}
|
||||
}
|
||||
if (p_variable->setter != nullptr) {
|
||||
push_text("Set (");
|
||||
if (p_variable->property == VariableNode::PROP_INLINE) {
|
||||
if (p_variable->setter_parameter != nullptr) {
|
||||
print_identifier(p_variable->setter_parameter);
|
||||
} else {
|
||||
push_text("<missing>");
|
||||
}
|
||||
push_line("):");
|
||||
increase_indent();
|
||||
print_suite(p_variable->setter);
|
||||
decrease_indent();
|
||||
} else {
|
||||
push_line(" =");
|
||||
increase_indent();
|
||||
print_identifier(p_variable->setter_pointer);
|
||||
push_line();
|
||||
decrease_indent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decrease_indent();
|
||||
push_line();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue