Commit 9e477bab by Juan Linietsky

-GDScript support for accessing properties directly

-Added code lookup and code completion support for properties too
parent 76c2e858
......@@ -581,6 +581,28 @@ static void _disassemble_class(const Ref<GDScript>& p_class,const Vector<String>
incr+=4;
} break;
case GDFunction::OPCODE_SET_MEMBER: {
txt+=" set_member ";
txt+="[\"";
txt+=func.get_global_name(code[ip+1]);
txt+="\"]=";
txt+=DADDR(2);
incr+=3;
} break;
case GDFunction::OPCODE_GET_MEMBER: {
txt+=" get_member ";
txt+=DADDR(2);
txt+="=";
txt+="[\"";
txt+=func.get_global_name(code[ip+1]);
txt+="\"]";
incr+=3;
} break;
case GDFunction::OPCODE_ASSIGN: {
txt+=" assign ";
......
......@@ -991,6 +991,22 @@ StringName ClassDB::get_property_getter(StringName p_class,const StringName p_pr
return StringName();
}
bool ClassDB::has_property(const StringName& p_class, const StringName& p_property, bool p_no_inheritance) {
ClassInfo *type=classes.getptr(p_class);
ClassInfo *check=type;
while(check) {
if (check->property_setget.has(p_property))
return true;
if (p_no_inheritance)
break;
check=check->inherits_ptr;
}
return false;
}
void ClassDB::set_method_flags(StringName p_class,StringName p_method,int p_flags) {
......
......@@ -481,6 +481,7 @@ public:
static void get_property_list(StringName p_class, List<PropertyInfo> *p_list, bool p_no_inheritance=false, const Object *p_validator=NULL);
static bool set_property(Object* p_object, const StringName& p_property, const Variant& p_value, bool *r_valid=NULL);
static bool get_property(Object* p_object,const StringName& p_property, Variant& r_value);
static bool has_property(const StringName& p_class,const StringName& p_property,bool p_no_inheritance=false);
static Variant::Type get_property_type(const StringName& p_class, const StringName& p_property,bool *r_is_valid=NULL);
static StringName get_property_setter(StringName p_class,const StringName p_property);
static StringName get_property_getter(StringName p_class,const StringName p_property);
......
......@@ -29,6 +29,31 @@
#include "gd_compiler.h"
#include "gd_script.h"
bool GDCompiler::_is_class_member_property(CodeGen & codegen, const StringName & p_name) {
if (!codegen.function_node || codegen.function_node->_static)
return false;
return _is_class_member_property(codegen.script,p_name);
}
bool GDCompiler::_is_class_member_property(GDScript *owner, const StringName & p_name) {
GDScript *scr = owner;
GDNativeClass *nc=NULL;
while(scr) {
if (scr->native.is_valid())
nc=scr->native.ptr();
scr=scr->_base;
}
ERR_FAIL_COND_V(!nc,false);
return ClassDB::has_property(nc->get_name(),p_name);
}
void GDCompiler::_set_error(const String& p_error,const GDParser::Node *p_node) {
......@@ -164,6 +189,17 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
StringName identifier = in->name;
if (_is_class_member_property(codegen,identifier)) {
//get property
codegen.opcodes.push_back(GDFunction::OPCODE_GET_MEMBER); // perform operator
codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter)
int dst_addr=(p_stack_level)|(GDFunction::ADDR_TYPE_STACK<<GDFunction::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;
}
// TRY STACK!
if (!p_initializer && codegen.stack_identifiers.has(identifier)) {
......@@ -776,6 +812,8 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
/* Find chain of sets */
StringName assign_property;
List<GDParser::OperatorNode*> chain;
{
......@@ -784,8 +822,20 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
while(true) {
chain.push_back(n);
if (n->arguments[0]->type!=GDParser::Node::TYPE_OPERATOR)
if (n->arguments[0]->type!=GDParser::Node::TYPE_OPERATOR) {
//check for a built-in property
if (n->arguments[0]->type==GDParser::Node::TYPE_IDENTIFIER) {
GDParser::IdentifierNode *identifier = static_cast<GDParser::IdentifierNode*>(n->arguments[0]);
if (_is_class_member_property(codegen,identifier->name)) {
assign_property = identifier->name;
}
}
break;
}
n = static_cast<GDParser::OperatorNode*>(n->arguments[0]);
if (n->op!=GDParser::OperatorNode::OP_INDEX && n->op!=GDParser::OperatorNode::OP_INDEX_NAMED)
break;
......@@ -810,6 +860,17 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
Vector<int> setchain;
if (assign_property!=StringName()) {
// recover and assign at the end, this allows stuff like
// position.x+=2.0
// in Node2D
setchain.push_back(prev_pos);
setchain.push_back(codegen.get_name_map_pos(assign_property));
setchain.push_back(GDFunction::OPCODE_SET_MEMBER);
}
for(List<GDParser::OperatorNode*>::Element *E=chain.back();E;E=E->prev()) {
......@@ -840,7 +901,7 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
}
if (key_idx<0)
if (key_idx<0) //error
return key_idx;
codegen.opcodes.push_back(named ? GDFunction::OPCODE_GET_NAMED : GDFunction::OPCODE_GET);
......@@ -852,7 +913,10 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
codegen.opcodes.push_back(dst_pos);
//add in reverse order, since it will be reverted
setchain.push_back(dst_pos);
setchain.push_back(key_idx);
setchain.push_back(prev_pos);
......@@ -881,7 +945,7 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
}
if (set_index<0)
if (set_index<0) //error
return set_index;
if (set_index&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) {
......@@ -891,7 +955,7 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
int set_value = _parse_assign_right_expression(codegen,on,slevel+1);
if (set_value<0)
if (set_value<0) //error
return set_value;
codegen.opcodes.push_back(named?GDFunction::OPCODE_SET_NAMED:GDFunction::OPCODE_SET);
......@@ -899,20 +963,36 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
codegen.opcodes.push_back(set_index);
codegen.opcodes.push_back(set_value);
for(int i=0;i<setchain.size();i+=4) {
for(int i=0;i<setchain.size();i++) {
codegen.opcodes.push_back(setchain[i+0]);
codegen.opcodes.push_back(setchain[i+1]);
codegen.opcodes.push_back(setchain[i+2]);
codegen.opcodes.push_back(setchain[i+3]);
codegen.opcodes.push_back(setchain[i]);
}
return retval;
} else if (on->arguments[0]->type==GDParser::Node::TYPE_IDENTIFIER && _is_class_member_property(codegen,static_cast<GDParser::IdentifierNode*>(on->arguments[0])->name)) {
//assignment to member property
int slevel = p_stack_level;
int src_address = _parse_assign_right_expression(codegen,on,slevel);
if (src_address<0)
return -1;
StringName name = static_cast<GDParser::IdentifierNode*>(on->arguments[0])->name;
codegen.opcodes.push_back(GDFunction::OPCODE_SET_MEMBER);
codegen.opcodes.push_back(codegen.get_name_map_pos(name));
codegen.opcodes.push_back(src_address);
return GDFunction::ADDR_TYPE_NIL<<GDFunction::ADDR_BITS;
} else {
//ASSIGNMENT MODE!!
//REGULAR ASSIGNMENT MODE!!
int slevel = p_stack_level;
......@@ -1211,6 +1291,11 @@ Error GDCompiler::_parse_block(CodeGen& codegen,const GDParser::BlockNode *p_blo
const GDParser::LocalVarNode *lv = static_cast<const GDParser::LocalVarNode*>(s);
if (_is_class_member_property(codegen,lv->name)) {
_set_error("Name for local variable '"+String(lv->name)+"' can't shadow class property of the same name.",lv);
return ERR_ALREADY_EXISTS;
}
codegen.add_stack_identifier(lv->name,p_stack_level++);
codegen.alloc_stack(p_stack_level);
new_identifiers++;
......@@ -1249,6 +1334,10 @@ Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode *
if (p_func) {
for(int i=0;i<p_func->arguments.size();i++) {
if (_is_class_member_property(p_script,p_func->arguments[i])) {
_set_error("Name for argument '"+String(p_func->arguments[i])+"' can't shadow class property of the same name.",p_func);
return ERR_ALREADY_EXISTS;
}
codegen.add_stack_identifier(p_func->arguments[i],i);
#ifdef TOOLS_ENABLED
argnames.push_back(p_func->arguments[i]);
......@@ -1653,6 +1742,10 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa
_set_error("Member '"+name+"' already exists (in current or parent class)",p_class);
return ERR_ALREADY_EXISTS;
}
if (_is_class_member_property(p_script,name)) {
_set_error("Member '"+name+"' already exists as a class property.",p_class);
return ERR_ALREADY_EXISTS;
}
if (p_class->variables[i]._export.type!=Variant::NIL) {
......@@ -1691,6 +1784,11 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa
StringName name = p_class->constant_expressions[i].identifier;
ERR_CONTINUE( p_class->constant_expressions[i].expression->type!=GDParser::Node::TYPE_CONSTANT );
if (_is_class_member_property(p_script,name)) {
_set_error("Member '"+name+"' already exists as a class property.",p_class);
return ERR_ALREADY_EXISTS;
}
GDParser::ConstantNode *constant = static_cast<GDParser::ConstantNode*>(p_class->constant_expressions[i].expression);
p_script->constants.insert(name,constant->value);
......
......@@ -37,6 +37,7 @@ class GDCompiler {
const GDParser *parser;
struct CodeGen {
GDScript *script;
const GDParser::ClassNode *class_node;
const GDParser::FunctionNode *function_node;
......@@ -134,6 +135,9 @@ class GDCompiler {
Ref<GDScript> _parse_class(GDParser::ClassNode *p_class);
#endif
bool _is_class_member_property(CodeGen & codegen, const StringName & p_name);
bool _is_class_member_property(GDScript *owner, const StringName & p_name);
void _set_error(const String& p_error,const GDParser::Node *p_node);
bool _create_unary_operator(CodeGen& codegen,const GDParser::OperatorNode *on,Variant::Operator op, int p_stack_level);
......
......@@ -487,7 +487,7 @@ Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_a
case OPCODE_GET_NAMED: {
CHECK_SPACE(3);
CHECK_SPACE(4);
GET_VARIANT_PTR(src,1);
GET_VARIANT_PTR(dst,3);
......@@ -519,6 +519,46 @@ Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_a
#endif
ip+=4;
} continue;
case OPCODE_SET_MEMBER: {
CHECK_SPACE(3);
int indexname = _code_ptr[ip+1];
ERR_BREAK(indexname<0 || indexname>=_global_names_count);
const StringName *index = &_global_names_ptr[indexname];
GET_VARIANT_PTR(src,2);
bool valid;
bool ok = ClassDB::set_property(p_instance->owner,*index,*src,&valid);
#ifdef DEBUG_ENABLED
if (!ok) {
err_text="Internal error setting property: "+String(*index);
break;
} else if (!valid) {
err_text="Error setting property '"+String(*index)+"' with value of type "+Variant::get_type_name(src->get_type())+".";
break;
}
#endif
ip+=3;
} continue;
case OPCODE_GET_MEMBER: {
CHECK_SPACE(3);
int indexname = _code_ptr[ip+1];
ERR_BREAK(indexname<0 || indexname>=_global_names_count);
const StringName *index = &_global_names_ptr[indexname];
GET_VARIANT_PTR(dst,2);
bool ok = ClassDB::get_property(p_instance->owner,*index,*dst);
#ifdef DEBUG_ENABLED
if (!ok) {
err_text="Internal error getting property: "+String(*index);
break;
}
#endif
ip+=3;
} continue;
case OPCODE_ASSIGN: {
CHECK_SPACE(3);
......
......@@ -23,6 +23,8 @@ public:
OPCODE_GET,
OPCODE_SET_NAMED,
OPCODE_GET_NAMED,
OPCODE_SET_MEMBER,
OPCODE_GET_MEMBER,
OPCODE_ASSIGN,
OPCODE_ASSIGN_TRUE,
OPCODE_ASSIGN_FALSE,
......
......@@ -1363,8 +1363,12 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT("script_text_editor/select_all", TTR("Select All"), KEY_MASK_CMD|KEY_A);
ED_SHORTCUT("script_text_editor/move_up", TTR("Move Up"), KEY_MASK_ALT|KEY_UP);
ED_SHORTCUT("script_text_editor/move_down", TTR("Move Down"), KEY_MASK_ALT|KEY_DOWN);
ED_SHORTCUT("script_text_editor/indent_left", TTR("Indent Left"), KEY_MASK_ALT|KEY_LEFT);
ED_SHORTCUT("script_text_editor/indent_right", TTR("Indent Right"), KEY_MASK_ALT|KEY_RIGHT);
//leave these at zero, same can be accomplished with tab/shift-tab, including selection
//the next/previous in history shortcut in this case makes a lot more sene.
ED_SHORTCUT("script_text_editor/indent_left", TTR("Indent Left"), 0);
ED_SHORTCUT("script_text_editor/indent_right", TTR("Indent Right"), 0);
ED_SHORTCUT("script_text_editor/toggle_comment", TTR("Toggle Comment"), KEY_MASK_CMD|KEY_K);
ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_CMD|KEY_B);
#ifdef OSX_ENABLED
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment