Browse Source

Add support for properties

George Marques 5 years ago
parent
commit
886732ac2b

+ 259 - 27
modules/gdscript/gdscript_compiler.cpp

@@ -273,8 +273,22 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 				// TRY MEMBER VARIABLES!
 				// TRY MEMBER VARIABLES!
 				//static function
 				//static function
 				if (codegen.script->member_indices.has(identifier)) {
 				if (codegen.script->member_indices.has(identifier)) {
-					int idx = codegen.script->member_indices[identifier].index;
-					return idx | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); //argument (stack root)
+					if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) {
+						// Perform getter.
+						codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN);
+						codegen.opcodes.push_back(0); // Argument count.
+						codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self).
+						codegen.opcodes.push_back(codegen.get_name_map_pos(codegen.script->member_indices[identifier].getter)); // Method name.
+						// Destination.
+						int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+						codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode
+						codegen.alloc_stack(p_stack_level);
+						return dst_addr;
+					} else {
+						// No getter or inside getter: direct member access.
+						int idx = codegen.script->member_indices[identifier].index;
+						return idx | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); //argument (stack root)
+					}
 				}
 				}
 			}
 			}
 
 
@@ -779,12 +793,12 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 			if (p_index_addr != 0) {
 			if (p_index_addr != 0) {
 				index = p_index_addr;
 				index = p_index_addr;
 			} else if (subscript->is_attribute) {
 			} else if (subscript->is_attribute) {
-				if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script && codegen.function_node && !codegen.function_node->is_static) {
+				if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) {
 					GDScriptParser::IdentifierNode *identifier = subscript->attribute;
 					GDScriptParser::IdentifierNode *identifier = subscript->attribute;
 					const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name);
 					const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name);
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
-					if (MI && MI->get().getter == codegen.function_node->identifier->name) {
+					if (MI && MI->get().getter == codegen.function_name) {
 						String n = identifier->name;
 						String n = identifier->name;
 						_set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", identifier);
 						_set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", identifier);
 						return -1;
 						return -1;
@@ -1095,9 +1109,9 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 				const GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(assignment->assignee);
 				const GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(assignment->assignee);
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 				if (subscript->is_attribute) {
 				if (subscript->is_attribute) {
-					if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script && codegen.function_node && !codegen.function_node->is_static) {
+					if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) {
 						const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(subscript->attribute->name);
 						const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(subscript->attribute->name);
-						if (MI && MI->get().setter == codegen.function_node->identifier->name) {
+						if (MI && MI->get().setter == codegen.function_name) {
 							String n = subscript->attribute->name;
 							String n = subscript->attribute->name;
 							_set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript);
 							_set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript);
 							return -1;
 							return -1;
@@ -1263,15 +1277,43 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 				//REGULAR ASSIGNMENT MODE!!
 				//REGULAR ASSIGNMENT MODE!!
 
 
 				int slevel = p_stack_level;
 				int slevel = p_stack_level;
-
-				int dst_address_a = _parse_expression(codegen, assignment->assignee, slevel);
-				if (dst_address_a < 0) {
-					return -1;
+				int dst_address_a = -1;
+
+				bool has_setter = false;
+				bool is_in_setter = false;
+				StringName setter_function;
+				if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
+					StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
+					if (!codegen.stack_identifiers.has(var_name) && codegen.script->member_indices.has(var_name)) {
+						setter_function = codegen.script->member_indices[var_name].setter;
+						if (setter_function != StringName()) {
+							has_setter = true;
+							is_in_setter = setter_function == codegen.function_name;
+							dst_address_a = codegen.script->member_indices[var_name].index;
+						}
+					}
 				}
 				}
 
 
-				if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
-					slevel++;
-					codegen.alloc_stack(slevel);
+				if (has_setter) {
+					if (is_in_setter) {
+						// Use direct member access.
+						dst_address_a |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS;
+					} else {
+						// Store stack slot for the temp value.
+						dst_address_a = slevel++;
+						codegen.alloc_stack(slevel);
+						dst_address_a |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+					}
+				} else {
+					dst_address_a = _parse_expression(codegen, assignment->assignee, slevel);
+					if (dst_address_a < 0) {
+						return -1;
+					}
+
+					if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
+						slevel++;
+						codegen.alloc_stack(slevel);
+					}
 				}
 				}
 
 
 				int src_address_b = _parse_assign_right_expression(codegen, assignment, slevel);
 				int src_address_b = _parse_assign_right_expression(codegen, assignment, slevel);
@@ -1330,6 +1372,18 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 					codegen.opcodes.push_back(dst_address_a); // argument 1
 					codegen.opcodes.push_back(dst_address_a); // argument 1
 					codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
 					codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
 				}
 				}
+
+				if (has_setter && !is_in_setter) {
+					// Call setter.
+					codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL);
+					codegen.opcodes.push_back(1); // Argument count.
+					codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self).
+					codegen.opcodes.push_back(codegen.get_name_map_pos(setter_function)); // Method name.
+					codegen.opcodes.push_back(dst_address_a); // Argument.
+					codegen.opcodes.push_back(dst_address_a); // Result address (won't be used here).
+					codegen.alloc_call(1);
+				}
+
 				return dst_address_a; //if anything, returns wathever was assigned or correct stack position
 				return dst_address_a; //if anything, returns wathever was assigned or correct stack position
 			}
 			}
 		} break;
 		} break;
@@ -2157,13 +2211,14 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
 			}
 			}
 			defarg_addr.invert();
 			defarg_addr.invert();
 		}
 		}
+		func_name = p_func->identifier->name;
+		codegen.function_name = func_name;
 
 
 		Error err = _parse_block(codegen, p_func->body, stack_level);
 		Error err = _parse_block(codegen, p_func->body, stack_level);
 		if (err) {
 		if (err) {
 			return err;
 			return err;
 		}
 		}
 
 
-		func_name = p_func->identifier->name;
 	} else {
 	} else {
 		if (p_for_ready) {
 		if (p_for_ready) {
 			func_name = "_ready";
 			func_name = "_ready";
@@ -2172,6 +2227,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
 		}
 		}
 	}
 	}
 
 
+	codegen.function_name = func_name;
 	codegen.opcodes.push_back(GDScriptFunction::OPCODE_END);
 	codegen.opcodes.push_back(GDScriptFunction::OPCODE_END);
 
 
 	/*
 	/*
@@ -2327,6 +2383,150 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
 	return OK;
 	return OK;
 }
 }
 
 
+Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) {
+	Vector<int> bytecode;
+	CodeGen codegen;
+
+	codegen.class_node = p_class;
+	codegen.script = p_script;
+	codegen.function_node = nullptr;
+	codegen.stack_max = 0;
+	codegen.current_line = 0;
+	codegen.call_max = 0;
+	codegen.debug_stack = EngineDebugger::is_active();
+	Vector<StringName> argnames;
+
+	int stack_level = 0;
+
+	if (p_is_setter) {
+		codegen.add_stack_identifier(p_variable->setter_parameter->name, stack_level++);
+		argnames.push_back(p_variable->setter_parameter->name);
+	}
+
+	codegen.alloc_stack(stack_level);
+
+	StringName func_name;
+
+	if (p_is_setter) {
+		func_name = "@" + p_variable->identifier->name + "_setter";
+	} else {
+		func_name = "@" + p_variable->identifier->name + "_getter";
+	}
+	codegen.function_name = func_name;
+
+	Error err = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter, stack_level);
+	if (err != OK) {
+		return err;
+	}
+
+	codegen.opcodes.push_back(GDScriptFunction::OPCODE_END);
+
+	p_script->member_functions[func_name] = memnew(GDScriptFunction);
+	GDScriptFunction *gdfunc = p_script->member_functions[func_name];
+
+	gdfunc->_static = false;
+	gdfunc->rpc_mode = p_variable->rpc_mode;
+	gdfunc->argument_types.resize(p_is_setter ? 1 : 0);
+	gdfunc->return_type = GDScriptDataType(); // TODO
+#ifdef TOOLS_ENABLED
+	gdfunc->arg_names = argnames;
+#endif
+
+	// TODO: Unify this with function compiler.
+	//constants
+	if (codegen.constant_map.size()) {
+		gdfunc->_constant_count = codegen.constant_map.size();
+		gdfunc->constants.resize(codegen.constant_map.size());
+		gdfunc->_constants_ptr = gdfunc->constants.ptrw();
+		const Variant *K = nullptr;
+		while ((K = codegen.constant_map.next(K))) {
+			int idx = codegen.constant_map[*K];
+			gdfunc->constants.write[idx] = *K;
+		}
+	} else {
+		gdfunc->_constants_ptr = nullptr;
+		gdfunc->_constant_count = 0;
+	}
+	//global names
+	if (codegen.name_map.size()) {
+		gdfunc->global_names.resize(codegen.name_map.size());
+		gdfunc->_global_names_ptr = &gdfunc->global_names[0];
+		for (Map<StringName, int>::Element *E = codegen.name_map.front(); E; E = E->next()) {
+			gdfunc->global_names.write[E->get()] = E->key();
+		}
+		gdfunc->_global_names_count = gdfunc->global_names.size();
+
+	} else {
+		gdfunc->_global_names_ptr = nullptr;
+		gdfunc->_global_names_count = 0;
+	}
+
+#ifdef TOOLS_ENABLED
+	// Named globals
+	if (codegen.named_globals.size()) {
+		gdfunc->named_globals.resize(codegen.named_globals.size());
+		gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr();
+		for (int i = 0; i < codegen.named_globals.size(); i++) {
+			gdfunc->named_globals.write[i] = codegen.named_globals[i];
+		}
+		gdfunc->_named_globals_count = gdfunc->named_globals.size();
+	}
+#endif
+
+	gdfunc->code = codegen.opcodes;
+	gdfunc->_code_ptr = &gdfunc->code[0];
+	gdfunc->_code_size = codegen.opcodes.size();
+	gdfunc->_default_arg_count = 0;
+	gdfunc->_default_arg_ptr = nullptr;
+	gdfunc->_argument_count = argnames.size();
+	gdfunc->_stack_size = codegen.stack_max;
+	gdfunc->_call_size = codegen.call_max;
+	gdfunc->name = func_name;
+#ifdef DEBUG_ENABLED
+	if (EngineDebugger::is_active()) {
+		String signature;
+		//path
+		if (p_script->get_path() != String()) {
+			signature += p_script->get_path();
+		}
+		//loc
+		signature += "::" + itos(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line);
+
+		//function and class
+
+		if (p_class->identifier) {
+			signature += "::" + String(p_class->identifier->name) + "." + String(func_name);
+		} else {
+			signature += "::" + String(func_name);
+		}
+
+		gdfunc->profile.signature = signature;
+	}
+#endif
+	gdfunc->_script = p_script;
+	gdfunc->source = source;
+
+#ifdef DEBUG_ENABLED
+
+	{
+		gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8();
+		gdfunc->_func_cname = gdfunc->func_cname.get_data();
+	}
+
+#endif
+	gdfunc->_initial_line = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line;
+#ifdef TOOLS_ENABLED
+
+	p_script->member_lines[func_name] = gdfunc->_initial_line;
+#endif
+
+	if (codegen.debug_stack) {
+		gdfunc->stack_debug = codegen.stack_debug;
+	}
+
+	return OK;
+}
+
 Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
 Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
 	parsing_classes.insert(p_script);
 	parsing_classes.insert(p_script);
 
 
@@ -2407,9 +2607,26 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
 
 
 				GDScript::MemberInfo minfo;
 				GDScript::MemberInfo minfo;
 				minfo.index = p_script->member_indices.size();
 				minfo.index = p_script->member_indices.size();
-				// FIXME: Getter and setter.
-				// minfo.setter = p_class->variables[i].setter;
-				// minfo.getter = p_class->variables[i].getter;
+				switch (variable->property) {
+					case GDScriptParser::VariableNode::PROP_NONE:
+						break; // Nothing to do.
+					case GDScriptParser::VariableNode::PROP_SETGET:
+						if (variable->setter_pointer != nullptr) {
+							minfo.setter = variable->setter_pointer->name;
+						}
+						if (variable->getter_pointer != nullptr) {
+							minfo.getter = variable->getter_pointer->name;
+						}
+						break;
+					case GDScriptParser::VariableNode::PROP_INLINE:
+						if (variable->setter != nullptr) {
+							minfo.setter = "@" + variable->identifier->name + "_setter";
+						}
+						if (variable->getter != nullptr) {
+							minfo.getter = "@" + variable->identifier->name + "_getter";
+						}
+						break;
+				}
 				minfo.rpc_mode = variable->rpc_mode;
 				minfo.rpc_mode = variable->rpc_mode;
 				// FIXME: Types.
 				// FIXME: Types.
 				// minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type);
 				// minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type);
@@ -2565,16 +2782,31 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa
 
 
 	for (int i = 0; i < p_class->members.size(); i++) {
 	for (int i = 0; i < p_class->members.size(); i++) {
 		const GDScriptParser::ClassNode::Member &member = p_class->members[i];
 		const GDScriptParser::ClassNode::Member &member = p_class->members[i];
-		if (member.type != member.FUNCTION) {
-			continue;
-		}
-		const GDScriptParser::FunctionNode *function = member.function;
-		if (!has_ready && function->identifier->name == "_ready") {
-			has_ready = true;
-		}
-		Error err = _parse_function(p_script, p_class, function);
-		if (err) {
-			return err;
+		if (member.type == member.FUNCTION) {
+			const GDScriptParser::FunctionNode *function = member.function;
+			if (!has_ready && function->identifier->name == "_ready") {
+				has_ready = true;
+			}
+			Error err = _parse_function(p_script, p_class, function);
+			if (err) {
+				return err;
+			}
+		} else if (member.type == member.VARIABLE) {
+			const GDScriptParser::VariableNode *variable = member.variable;
+			if (variable->property == GDScriptParser::VariableNode::PROP_INLINE) {
+				if (variable->setter != nullptr) {
+					Error err = _parse_setter_getter(p_script, p_class, variable, true);
+					if (err) {
+						return err;
+					}
+				}
+				if (variable->getter != nullptr) {
+					Error err = _parse_setter_getter(p_script, p_class, variable, false);
+					if (err) {
+						return err;
+					}
+				}
+			}
 		}
 		}
 	}
 	}
 
 

+ 2 - 0
modules/gdscript/gdscript_compiler.h

@@ -45,6 +45,7 @@ class GDScriptCompiler {
 		GDScript *script;
 		GDScript *script;
 		const GDScriptParser::ClassNode *class_node;
 		const GDScriptParser::ClassNode *class_node;
 		const GDScriptParser::FunctionNode *function_node;
 		const GDScriptParser::FunctionNode *function_node;
+		StringName function_name;
 		bool debug_stack;
 		bool debug_stack;
 
 
 		List<Map<StringName, int>> stack_id_stack;
 		List<Map<StringName, int>> stack_id_stack;
@@ -153,6 +154,7 @@ class GDScriptCompiler {
 	Error _parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address);
 	Error _parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address);
 	Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1);
 	Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1);
 	Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false);
 	Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false);
+	Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
 	Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
 	void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);

+ 206 - 2
modules/gdscript/gdscript_parser.cpp

@@ -184,6 +184,11 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
 	clear();
 	clear();
 	tokenizer.set_source_code(p_source_code);
 	tokenizer.set_source_code(p_source_code);
 	current = tokenizer.scan();
 	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.
 	push_multiline(false); // Keep one for the whole parsing.
 	parse_program();
 	parse_program();
@@ -555,6 +560,10 @@ void GDScriptParser::parse_class_body() {
 }
 }
 
 
 GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
 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".)")) {
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
 		return nullptr;
 		return nullptr;
 	}
 	}
@@ -563,10 +572,26 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
 	variable->identifier = parse_identifier();
 	variable->identifier = parse_identifier();
 
 
 	if (match(GDScriptTokenizer::Token::COLON)) {
 	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.
 			// Infer type.
 			variable->infer_datatype = true;
 			variable->infer_datatype = true;
 		} else {
 		} 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.
 			// Parse type.
 			variable->datatype_specifier = parse_type();
 			variable->datatype_specifier = parse_type();
 		}
 		}
@@ -577,6 +602,14 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
 		variable->initializer = parse_expression(false);
 		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");
 	end_statement("variable declaration");
 
 
 	variable->export_info.name = variable->identifier->name;
 	variable->export_info.name = variable->identifier->name;
@@ -584,6 +617,125 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
 	return 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() {
 GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
 		return nullptr;
 		return nullptr;
@@ -1021,6 +1173,9 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
 		default: {
 		default: {
 			// Expression statement.
 			// Expression statement.
 			ExpressionNode *expression = parse_expression(true); // Allow assignment here.
 			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");
 			end_statement("expression");
 			result = expression;
 			result = expression;
 			break;
 			break;
@@ -1402,7 +1557,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
 		ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token.");
 		ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token.");
 	}
 	}
 	IdentifierNode *identifier = alloc_node<IdentifierNode>();
 	IdentifierNode *identifier = alloc_node<IdentifierNode>();
-	identifier->name = previous.literal;
+	identifier->name = previous.get_identifier();
 	return identifier;
 	return identifier;
 }
 }
 
 
@@ -3016,6 +3171,15 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
 	push_text("Variable ");
 	push_text("Variable ");
 	print_identifier(p_variable->identifier);
 	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();
 	increase_indent();
 
 
 	push_line();
 	push_line();
@@ -3025,6 +3189,46 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
 	} else {
 	} else {
 		print_expression(p_variable->initializer);
 		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();
 	decrease_indent();
 	push_line();
 	push_line();
 }
 }

+ 22 - 0
modules/gdscript/gdscript_parser.h

@@ -777,10 +777,28 @@ public:
 	};
 	};
 
 
 	struct VariableNode : public Node {
 	struct VariableNode : public Node {
+		enum PropertyStyle {
+			PROP_NONE,
+			PROP_INLINE,
+			PROP_SETGET,
+		};
+
 		IdentifierNode *identifier = nullptr;
 		IdentifierNode *identifier = nullptr;
 		ExpressionNode *initializer = nullptr;
 		ExpressionNode *initializer = nullptr;
 		TypeNode *datatype_specifier = nullptr;
 		TypeNode *datatype_specifier = nullptr;
 		bool infer_datatype = false;
 		bool infer_datatype = false;
+
+		PropertyStyle property = PROP_NONE;
+		union {
+			SuiteNode *setter = nullptr;
+			IdentifierNode *setter_pointer;
+		};
+		IdentifierNode *setter_parameter = nullptr;
+		union {
+			SuiteNode *getter = nullptr;
+			IdentifierNode *getter_pointer;
+		};
+
 		bool exported = false;
 		bool exported = false;
 		bool onready = false;
 		bool onready = false;
 		PropertyInfo export_info;
 		PropertyInfo export_info;
@@ -923,6 +941,10 @@ private:
 	// Statements.
 	// Statements.
 	Node *parse_statement();
 	Node *parse_statement();
 	VariableNode *parse_variable();
 	VariableNode *parse_variable();
+	VariableNode *parse_variable(bool p_allow_property);
+	VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
+	void parse_property_getter(VariableNode *p_variable);
+	void parse_property_setter(VariableNode *p_variable);
 	ConstantNode *parse_constant();
 	ConstantNode *parse_constant();
 	AssertNode *parse_assert();
 	AssertNode *parse_assert();
 	BreakNode *parse_break();
 	BreakNode *parse_break();