diff options
author | Juan Linietsky <reduzio@gmail.com> | 2014-02-24 09:53:33 -0300 |
---|---|---|
committer | Juan Linietsky <reduzio@gmail.com> | 2014-02-24 09:53:33 -0300 |
commit | 4b07eb8deb03ce8c7870d621cd03d04d45f4caaa (patch) | |
tree | 3314811f8a347ae60d951163c19d291b46381511 /modules | |
parent | 51609ffc04290f8bd1ecbd9cf1639c0cc6368fac (diff) |
-moved script to modules
Diffstat (limited to 'modules')
23 files changed, 11441 insertions, 0 deletions
diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub new file mode 100644 index 0000000000..d20da72b72 --- /dev/null +++ b/modules/gdscript/SCsub @@ -0,0 +1,7 @@ +Import('env') + +env.add_source_files(env.modules_sources,"*.cpp") + +Export('env') + + diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py new file mode 100644 index 0000000000..f9bd7da08d --- /dev/null +++ b/modules/gdscript/config.py @@ -0,0 +1,11 @@ + + +def can_build(platform): + return True + + +def configure(env): + pass + + + diff --git a/modules/gdscript/gd_compiler.cpp b/modules/gdscript/gd_compiler.cpp new file mode 100644 index 0000000000..dd2834bf34 --- /dev/null +++ b/modules/gdscript/gd_compiler.cpp @@ -0,0 +1,1531 @@ +/*************************************************************************/ +/* gd_compiler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_compiler.h" +#include "gd_script.h" +/* TODO: + + *AND and OR need early abort + -Inheritance properly process (done?) + *create built in initializer and constructor + *assign operators + *build arrays and dictionaries + *call parent constructor + */ + + +void GDCompiler::_set_error(const String& p_error,const GDParser::Node *p_node) { + + if (error!="") + return; + + error=p_error; + err_line=p_node->line; + err_column=p_node->column; +} + +bool GDCompiler::_create_unary_operator(CodeGen& codegen,const GDParser::OperatorNode *on,Variant::Operator op, int p_stack_level) { + + ERR_FAIL_COND_V(on->arguments.size()!=1,false); + + int src_address_a = _parse_expression(codegen,on->arguments[0],p_stack_level); + if (src_address_a<0) + return false; + + codegen.opcodes.push_back(GDFunction::OPCODE_OPERATOR); // perform operator + codegen.opcodes.push_back(op); //which operator + codegen.opcodes.push_back(src_address_a); // argument 1 + codegen.opcodes.push_back(GDFunction::ADDR_TYPE_NIL); // argument 2 (unary only takes one parameter) + return true; +} + +bool GDCompiler::_create_binary_operator(CodeGen& codegen,const GDParser::OperatorNode *on,Variant::Operator op, int p_stack_level) { + + ERR_FAIL_COND_V(on->arguments.size()!=2,false); + + + int src_address_a = _parse_expression(codegen,on->arguments[0],p_stack_level); + if (src_address_a<0) + return false; + if (src_address_a&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) + p_stack_level++; //uses stack for return, increase stack + + int src_address_b = _parse_expression(codegen,on->arguments[1],p_stack_level); + if (src_address_b<0) + return false; + + + codegen.opcodes.push_back(GDFunction::OPCODE_OPERATOR); // perform operator + codegen.opcodes.push_back(op); //which operator + codegen.opcodes.push_back(src_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + return true; +} + + +/* +int GDCompiler::_parse_subexpression(CodeGen& codegen,const GDParser::Node *p_expression) { + + + int ret = _parse_expression(codegen,p_expression); + if (ret<0) + return ret; + + if (ret&(GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS)) { + codegen.stack_level++; + codegen.check_max_stack_level(); + //stack was used, keep value + } + + return ret; +} +*/ + +int GDCompiler::_parse_assign_right_expression(CodeGen& codegen,const GDParser::OperatorNode *p_expression, int p_stack_level) { + + Variant::Operator var_op=Variant::OP_MAX; + + switch(p_expression->op) { + + case GDParser::OperatorNode::OP_ASSIGN_ADD: var_op=Variant::OP_ADD; break; + case GDParser::OperatorNode::OP_ASSIGN_SUB: var_op=Variant::OP_SUBSTRACT; break; + case GDParser::OperatorNode::OP_ASSIGN_MUL: var_op=Variant::OP_MULTIPLY; break; + case GDParser::OperatorNode::OP_ASSIGN_DIV: var_op=Variant::OP_DIVIDE; break; + case GDParser::OperatorNode::OP_ASSIGN_MOD: var_op=Variant::OP_MODULE; break; + case GDParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT: var_op=Variant::OP_SHIFT_LEFT; break; + case GDParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT: var_op=Variant::OP_SHIFT_RIGHT; break; + case GDParser::OperatorNode::OP_ASSIGN_BIT_AND: var_op=Variant::OP_BIT_AND; break; + case GDParser::OperatorNode::OP_ASSIGN_BIT_OR: var_op=Variant::OP_BIT_OR; break; + case GDParser::OperatorNode::OP_ASSIGN_BIT_XOR: var_op=Variant::OP_BIT_XOR; break; + case GDParser::OperatorNode::OP_ASSIGN: { + + //none + } break; + default: { + + ERR_FAIL_V(-1); + } + } + + if (var_op==Variant::OP_MAX) { + + return _parse_expression(codegen,p_expression->arguments[1],p_stack_level); + } + + if (!_create_binary_operator(codegen,p_expression,var_op,p_stack_level)) + return -1; + + 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; + +} + +int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expression, int p_stack_level,bool p_root) { + + + switch(p_expression->type) { + //should parse variable declaration and adjust stack accordingly... + case GDParser::Node::TYPE_IDENTIFIER: { + //return identifier + //wait, identifier could be a local variable or something else... careful here, must reference properly + //as stack may be more interesting to work with + + //This could be made much simpler by just indexing "self", but done this way (with custom self-addressing modes) increases peformance a lot. + + const GDParser::IdentifierNode *in = static_cast<const GDParser::IdentifierNode*>(p_expression); + + StringName identifier = in->name; + + // TRY STACK! + if (codegen.stack_identifiers.has(identifier)) { + + int pos = codegen.stack_identifiers[identifier]; + return pos|(GDFunction::ADDR_TYPE_STACK_VARIABLE<<GDFunction::ADDR_BITS); + + } + //TRY ARGUMENTS! + if (!codegen.function_node || !codegen.function_node->_static) { + + // TRY MEMBER VARIABLES! + + //static function + if (codegen.script->member_indices.has(identifier)) { + + int idx = codegen.script->member_indices[identifier]; + return idx|(GDFunction::ADDR_TYPE_MEMBER<<GDFunction::ADDR_BITS); //argument (stack root) + } + } + + //TRY CLASS CONSTANTS + + GDScript *scr = codegen.script; + GDNativeClass *nc=NULL; + while(scr) { + + if (scr->constants.has(identifier)) { + + //int idx=scr->constants[identifier]; + int idx = codegen.get_name_map_pos(identifier); + return idx|(GDFunction::ADDR_TYPE_CLASS_CONSTANT<<GDFunction::ADDR_BITS); //argument (stack root) + } + if (scr->native.is_valid()) + nc=scr->native.ptr(); + scr=scr->_base; + } + + // CLASS C++ Integer Constant + + if (nc) { + + bool success=false; + int constant = ObjectTypeDB::get_integer_constant(nc->get_name(),identifier,&success); + if (success) { + Variant key=constant; + int idx; + + if (!codegen.constant_map.has(key)) { + + idx=codegen.constant_map.size(); + codegen.constant_map[key]=idx; + + } else { + idx=codegen.constant_map[key]; + } + + return idx|(GDFunction::ADDR_TYPE_LOCAL_CONSTANT<<GDFunction::ADDR_BITS); //make it a local constant (faster access) + } + + } + + if (codegen.script->subclasses.has(identifier)) { + //same with a subclass, make it a local constant. + int idx = codegen.get_constant_pos(codegen.script->subclasses[identifier]); + return idx|(GDFunction::ADDR_TYPE_LOCAL_CONSTANT<<GDFunction::ADDR_BITS); //make it a local constant (faster access) + + } + + if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { + + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + return idx|(GDFunction::ADDR_TYPE_GLOBAL<<GDFunction::ADDR_BITS); //argument (stack root) + } + + //not found, error + + _set_error("Identifier not found: "+String(identifier),p_expression); + + return -1; + + + } break; + case GDParser::Node::TYPE_CONSTANT: { + //return constant + const GDParser::ConstantNode *cn = static_cast<const GDParser::ConstantNode*>(p_expression); + + + int idx; + + if (!codegen.constant_map.has(cn->value)) { + + idx=codegen.constant_map.size(); + codegen.constant_map[cn->value]=idx; + + } else { + idx=codegen.constant_map[cn->value]; + } + + + return idx|(GDFunction::ADDR_TYPE_LOCAL_CONSTANT<<GDFunction::ADDR_BITS); //argument (stack root) + + } break; + case GDParser::Node::TYPE_SELF: { + //return constant + if (codegen.function_node && codegen.function_node->_static) { + _set_error("'self' not present in static function!",p_expression); + return -1; + } + return (GDFunction::ADDR_TYPE_SELF<<GDFunction::ADDR_BITS); + } break; + case GDParser::Node::TYPE_ARRAY: { + + const GDParser::ArrayNode *an = static_cast<const GDParser::ArrayNode*>(p_expression); + Vector<int> values; + + int slevel=p_stack_level; + + for(int i=0;i<an->elements.size();i++) { + + int ret = _parse_expression(codegen,an->elements[i],slevel); + if (ret<0) + return ret; + if (ret&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + values.push_back(ret); + } + + codegen.opcodes.push_back(GDFunction::OPCODE_CONSTRUCT_ARRAY); + codegen.opcodes.push_back(values.size()); + for(int i=0;i<values.size();i++) + codegen.opcodes.push_back(values[i]); + + 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; + + } break; + case GDParser::Node::TYPE_DICTIONARY: { + + const GDParser::DictionaryNode *dn = static_cast<const GDParser::DictionaryNode*>(p_expression); + Vector<int> values; + + int slevel=p_stack_level; + + for(int i=0;i<dn->elements.size();i++) { + + int ret = _parse_expression(codegen,dn->elements[i].key,slevel); + if (ret<0) + return ret; + if (ret&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + values.push_back(ret); + + ret = _parse_expression(codegen,dn->elements[i].value,slevel); + if (ret<0) + return ret; + if (ret&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + values.push_back(ret); + } + + codegen.opcodes.push_back(GDFunction::OPCODE_CONSTRUCT_DICTIONARY); + codegen.opcodes.push_back(dn->elements.size()); + for(int i=0;i<values.size();i++) + codegen.opcodes.push_back(values[i]); + + 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; + + } break; + case GDParser::Node::TYPE_OPERATOR: { + //hell breaks loose + + const GDParser::OperatorNode *on = static_cast<const GDParser::OperatorNode*>(p_expression); + switch(on->op) { + + + //call/constructor operator + case GDParser::OperatorNode::OP_PARENT_CALL: { + + + ERR_FAIL_COND_V(on->arguments.size()<1,-1); + + const GDParser::IdentifierNode *in = (const GDParser::IdentifierNode *)on->arguments[0]; + + + Vector<int> arguments; + int slevel = p_stack_level; + for(int i=1;i<on->arguments.size();i++) { + + int ret = _parse_expression(codegen,on->arguments[i],slevel); + if (ret<0) + return ret; + if (ret&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + arguments.push_back(ret); + } + + //push call bytecode + codegen.opcodes.push_back(GDFunction::OPCODE_CALL_SELF_BASE); // basic type constructor + + codegen.opcodes.push_back(codegen.get_name_map_pos(in->name)); //instance + codegen.opcodes.push_back(arguments.size()); //argument count + codegen.alloc_call(arguments.size()); + for(int i=0;i<arguments.size();i++) + codegen.opcodes.push_back(arguments[i]); //arguments + + } break; + case GDParser::OperatorNode::OP_CALL: { + + if (on->arguments[0]->type==GDParser::Node::TYPE_TYPE) { + //construct a basic type + ERR_FAIL_COND_V(on->arguments.size()<1,-1); + + const GDParser::TypeNode *tn = (const GDParser::TypeNode *)on->arguments[0]; + int vtype = tn->vtype; + + Vector<int> arguments; + int slevel = p_stack_level; + for(int i=1;i<on->arguments.size();i++) { + + int ret = _parse_expression(codegen,on->arguments[i],slevel); + if (ret<0) + return ret; + if (ret&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + arguments.push_back(ret); + } + + //push call bytecode + codegen.opcodes.push_back(GDFunction::OPCODE_CONSTRUCT); // basic type constructor + codegen.opcodes.push_back(vtype); //instance + codegen.opcodes.push_back(arguments.size()); //argument count + codegen.alloc_call(arguments.size()); + for(int i=0;i<arguments.size();i++) + codegen.opcodes.push_back(arguments[i]); //arguments + + } else if (on->arguments[0]->type==GDParser::Node::TYPE_BUILT_IN_FUNCTION) { + //built in function + + ERR_FAIL_COND_V(on->arguments.size()<1,-1); + + + Vector<int> arguments; + int slevel = p_stack_level; + for(int i=1;i<on->arguments.size();i++) { + + int ret = _parse_expression(codegen,on->arguments[i],slevel); + if (ret<0) + return ret; + + if (ret&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + arguments.push_back(ret); + } + + + codegen.opcodes.push_back(GDFunction::OPCODE_CALL_BUILT_IN); + codegen.opcodes.push_back(static_cast<const GDParser::BuiltInFunctionNode*>(on->arguments[0])->function); + codegen.opcodes.push_back(on->arguments.size()-1); + codegen.alloc_call(on->arguments.size()-1); + for(int i=0;i<arguments.size();i++) + codegen.opcodes.push_back(arguments[i]); + + } else { + //regular function + ERR_FAIL_COND_V(on->arguments.size()<2,-1); + + const GDParser::Node *instance = on->arguments[0]; + + if (instance->type==GDParser::Node::TYPE_SELF) { + //room for optimization + + } + + + Vector<int> arguments; + int slevel = p_stack_level; + + for(int i=0;i<on->arguments.size();i++) { + + int ret; + + if (i==1) { + + if (on->arguments[i]->type!=GDParser::Node::TYPE_IDENTIFIER) { + _set_error("Attempt to call a non-identifier.",on); + return -1; + } + GDParser::IdentifierNode *id = static_cast<GDParser::IdentifierNode*>(on->arguments[i]); + ret=codegen.get_name_map_pos(id->name); + + } else { + ret = _parse_expression(codegen,on->arguments[i],slevel); + if (ret<0) + return ret; + if (ret&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + } + arguments.push_back(ret); + + } + + codegen.opcodes.push_back(p_root?GDFunction::OPCODE_CALL:GDFunction::OPCODE_CALL_RETURN); // perform operator + codegen.opcodes.push_back(on->arguments.size()-2); + codegen.alloc_call(on->arguments.size()-2); + for(int i=0;i<arguments.size();i++) + codegen.opcodes.push_back(arguments[i]); + } + } break; + //indexing operator + case GDParser::OperatorNode::OP_INDEX: + case GDParser::OperatorNode::OP_INDEX_NAMED: { + + ERR_FAIL_COND_V(on->arguments.size()!=2,-1); + + int slevel = p_stack_level; + bool named=(on->op==GDParser::OperatorNode::OP_INDEX_NAMED); + + int from = _parse_expression(codegen,on->arguments[0],slevel); + if (from<0) + return from; + + int index; + if (named) { + + index=codegen.get_name_map_pos(static_cast<GDParser::IdentifierNode*>(on->arguments[1])->name); + + } else { + + if (on->arguments[1]->type==GDParser::Node::TYPE_CONSTANT && static_cast<const GDParser::ConstantNode*>(on->arguments[1])->value.get_type()==Variant::STRING) { + //also, somehow, named (speed up anyway) + StringName name = static_cast<const GDParser::ConstantNode*>(on->arguments[1])->value; + index=codegen.get_name_map_pos(name); + named=true; + + } else { + //regular indexing + if (from&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + index = _parse_expression(codegen,on->arguments[1],slevel); + if (index<0) + return index; + } + } + + codegen.opcodes.push_back(named?GDFunction::OPCODE_GET_NAMED:GDFunction::OPCODE_GET); // perform operator + codegen.opcodes.push_back(from); // argument 1 + codegen.opcodes.push_back(index); // argument 2 (unary only takes one parameter) + + } break; + case GDParser::OperatorNode::OP_AND: { + + // AND operator with early out on failure + + int res = _parse_expression(codegen,on->arguments[0],p_stack_level); + if (res<0) + return res; + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(res); + int jump_fail_pos=codegen.opcodes.size(); + codegen.opcodes.push_back(0); + + res = _parse_expression(codegen,on->arguments[1],p_stack_level); + if (res<0) + return res; + + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(res); + int jump_fail_pos2=codegen.opcodes.size(); + codegen.opcodes.push_back(0); + + codegen.alloc_stack(p_stack_level); //it will be used.. + codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN_TRUE); + codegen.opcodes.push_back(p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + codegen.opcodes.push_back(codegen.opcodes.size()+3); + codegen.opcodes[jump_fail_pos]=codegen.opcodes.size(); + codegen.opcodes[jump_fail_pos2]=codegen.opcodes.size(); + codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN_FALSE); + codegen.opcodes.push_back(p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + return p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS; + + } break; + case GDParser::OperatorNode::OP_OR: { + + // OR operator with early out on success + + int res = _parse_expression(codegen,on->arguments[0],p_stack_level); + if (res<0) + return res; + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP_IF); + codegen.opcodes.push_back(res); + int jump_success_pos=codegen.opcodes.size(); + codegen.opcodes.push_back(0); + + res = _parse_expression(codegen,on->arguments[1],p_stack_level); + if (res<0) + return res; + + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP_IF); + codegen.opcodes.push_back(res); + int jump_success_pos2=codegen.opcodes.size(); + codegen.opcodes.push_back(0); + + codegen.alloc_stack(p_stack_level); //it will be used.. + codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN_FALSE); + codegen.opcodes.push_back(p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + codegen.opcodes.push_back(codegen.opcodes.size()+3); + codegen.opcodes[jump_success_pos]=codegen.opcodes.size(); + codegen.opcodes[jump_success_pos2]=codegen.opcodes.size(); + codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN_TRUE); + codegen.opcodes.push_back(p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + return p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS; + + } break; + //unary operators + case GDParser::OperatorNode::OP_NEG: { if (!_create_unary_operator(codegen,on,Variant::OP_NEGATE,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_NOT: { if (!_create_unary_operator(codegen,on,Variant::OP_NOT,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_BIT_INVERT: { if (!_create_unary_operator(codegen,on,Variant::OP_BIT_NEGATE,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_PREINC: { } break; //? + case GDParser::OperatorNode::OP_PREDEC: { } break; + case GDParser::OperatorNode::OP_INC: { } break; + case GDParser::OperatorNode::OP_DEC: { } break; + //binary operators (in precedence order) + case GDParser::OperatorNode::OP_IN: { if (!_create_binary_operator(codegen,on,Variant::OP_IN,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_EQUAL: { if (!_create_binary_operator(codegen,on,Variant::OP_EQUAL,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_NOT_EQUAL: { if (!_create_binary_operator(codegen,on,Variant::OP_NOT_EQUAL,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_LESS: { if (!_create_binary_operator(codegen,on,Variant::OP_LESS,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_LESS_EQUAL: { if (!_create_binary_operator(codegen,on,Variant::OP_LESS_EQUAL,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_GREATER: { if (!_create_binary_operator(codegen,on,Variant::OP_GREATER,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_GREATER_EQUAL: { if (!_create_binary_operator(codegen,on,Variant::OP_GREATER_EQUAL,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_ADD: { if (!_create_binary_operator(codegen,on,Variant::OP_ADD,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_SUB: { if (!_create_binary_operator(codegen,on,Variant::OP_SUBSTRACT,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_MUL: { if (!_create_binary_operator(codegen,on,Variant::OP_MULTIPLY,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_DIV: { if (!_create_binary_operator(codegen,on,Variant::OP_DIVIDE,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_MOD: { if (!_create_binary_operator(codegen,on,Variant::OP_MODULE,p_stack_level)) return -1;} break; + //case GDParser::OperatorNode::OP_SHIFT_LEFT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_LEFT,p_stack_level)) return -1;} break; + //case GDParser::OperatorNode::OP_SHIFT_RIGHT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_RIGHT,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_BIT_AND: { if (!_create_binary_operator(codegen,on,Variant::OP_BIT_AND,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_BIT_OR: { if (!_create_binary_operator(codegen,on,Variant::OP_BIT_OR,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_BIT_XOR: { if (!_create_binary_operator(codegen,on,Variant::OP_BIT_XOR,p_stack_level)) return -1;} break; + //shift + case GDParser::OperatorNode::OP_SHIFT_LEFT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_LEFT,p_stack_level)) return -1;} break; + case GDParser::OperatorNode::OP_SHIFT_RIGHT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_RIGHT,p_stack_level)) return -1;} break; + //assignment operators + case GDParser::OperatorNode::OP_ASSIGN_ADD: + case GDParser::OperatorNode::OP_ASSIGN_SUB: + case GDParser::OperatorNode::OP_ASSIGN_MUL: + case GDParser::OperatorNode::OP_ASSIGN_DIV: + case GDParser::OperatorNode::OP_ASSIGN_MOD: + case GDParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT: + case GDParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT: + case GDParser::OperatorNode::OP_ASSIGN_BIT_AND: + case GDParser::OperatorNode::OP_ASSIGN_BIT_OR: + case GDParser::OperatorNode::OP_ASSIGN_BIT_XOR: + case GDParser::OperatorNode::OP_ASSIGN: { + + ERR_FAIL_COND_V(on->arguments.size()!=2,-1); + + + if (on->arguments[0]->type==GDParser::Node::TYPE_OPERATOR && (static_cast<GDParser::OperatorNode*>(on->arguments[0])->op==GDParser::OperatorNode::OP_INDEX || static_cast<GDParser::OperatorNode*>(on->arguments[0])->op==GDParser::OperatorNode::OP_INDEX_NAMED)) { + //SET (chained) MODE!! + + int slevel=p_stack_level; + + GDParser::OperatorNode* op = static_cast<GDParser::OperatorNode*>(on->arguments[0]); + + /* Find chain of sets */ + + List<GDParser::OperatorNode*> chain; + + { + //create get/set chain + GDParser::OperatorNode* n=op; + while(true) { + + chain.push_back(n); + if (n->arguments[0]->type!=GDParser::Node::TYPE_OPERATOR) + break; + n = static_cast<GDParser::OperatorNode*>(n->arguments[0]); + if (n->op!=GDParser::OperatorNode::OP_INDEX && n->op!=GDParser::OperatorNode::OP_INDEX_NAMED) + break; + } + } + + /* Chain of gets */ + + //get at (potential) root stack pos, so it can be returned + int prev_pos = _parse_expression(codegen,chain.back()->get()->arguments[0],slevel); + if (prev_pos<0) + return prev_pos; + int retval=prev_pos; + + if (retval&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + + Vector<int> setchain; + + for(List<GDParser::OperatorNode*>::Element *E=chain.back();E;E=E->prev()) { + + + if (E==chain.front()) //ignore first + break; + + bool named = E->get()->op==GDParser::OperatorNode::OP_INDEX_NAMED; + int key_idx; + + if (named) { + + key_idx = codegen.get_name_map_pos(static_cast<const GDParser::IdentifierNode*>(E->get()->arguments[1])->name); + } else { + + GDParser::Node *key = E->get()->arguments[1]; + key_idx = _parse_expression(codegen,key,slevel); + if (retval&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + } + + if (key_idx<0) + return key_idx; + + codegen.opcodes.push_back(named ? GDFunction::OPCODE_GET_NAMED : GDFunction::OPCODE_GET); + codegen.opcodes.push_back(prev_pos); + codegen.opcodes.push_back(key_idx); + slevel++; + codegen.alloc_stack(slevel); + int dst_pos = (GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS)|slevel; + 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); + setchain.push_back(named ? GDFunction::OPCODE_SET_NAMED : GDFunction::OPCODE_SET); + + prev_pos=dst_pos; + + } + + setchain.invert(); + + + int set_index; + bool named=false; + + + if (static_cast<const GDParser::OperatorNode*>(op)->op==GDParser::OperatorNode::OP_INDEX_NAMED) { + + + set_index=codegen.get_name_map_pos(static_cast<const GDParser::IdentifierNode*>(op->arguments[1])->name); + named=true; + } else { + + set_index = _parse_expression(codegen,op->arguments[1],slevel+1); + named=false; + } + + + if (set_index<0) + return set_index; + + if (set_index&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + + int set_value = _parse_assign_right_expression(codegen,on,slevel+1); + if (set_value<0) + return set_value; + + codegen.opcodes.push_back(named?GDFunction::OPCODE_SET_NAMED:GDFunction::OPCODE_SET); + codegen.opcodes.push_back(prev_pos); + codegen.opcodes.push_back(set_index); + codegen.opcodes.push_back(set_value); + + for(int i=0;i<setchain.size();i+=4) { + + + 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]); + } + + return retval; + + + } else { + //ASSIGNMENT MODE!! + + int slevel = p_stack_level; + + int dst_address_a = _parse_expression(codegen,on->arguments[0],slevel); + if (dst_address_a<0) + return -1; + + if (dst_address_a&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } + + int src_address_b = _parse_assign_right_expression(codegen,on,slevel); + if (src_address_b<0) + return -1; + + + + + codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN); // perform operator + codegen.opcodes.push_back(dst_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + return dst_address_a; //if anything, returns wathever was assigned or correct stack position + + } + + + } break; + case GDParser::OperatorNode::OP_EXTENDS: { + + ERR_FAIL_COND_V(on->arguments.size()!=2,false); + + + int slevel = p_stack_level; + + int src_address_a = _parse_expression(codegen,on->arguments[0],slevel); + if (src_address_a<0) + return -1; + + if (src_address_a&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) + slevel++; //uses stack for return, increase stack + + int src_address_b = _parse_expression(codegen,on->arguments[1],slevel); + if (src_address_b<0) + return -1; + + codegen.opcodes.push_back(GDFunction::OPCODE_EXTENDS_TEST); // perform operator + codegen.opcodes.push_back(src_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + + } break; + default: { + + + ERR_EXPLAIN("Bug in bytecode compiler, unexpected operator #"+itos(on->op)+" in parse tree while parsing expression."); + ERR_FAIL_V(0); //unreachable code + + } break; + } + + 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; + } break; + //TYPE_TYPE, + default: { + + ERR_EXPLAIN("Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); + ERR_FAIL_V(-1); //unreachable code + } break; + + + } + + ERR_FAIL_V(-1); //unreachable code +} + + +Error GDCompiler::_parse_block(CodeGen& codegen,const GDParser::BlockNode *p_block,int p_stack_level,int p_break_addr,int p_continue_addr) { + + codegen.push_stack_identifiers(); + int new_identifiers=0; + codegen.current_line=p_block->line; + + for(int i=0;i<p_block->statements.size();i++) { + + const GDParser::Node *s = p_block->statements[i]; + + + switch(s->type) { + case GDParser::Node::TYPE_NEWLINE: { + + const GDParser::NewLineNode *nl = static_cast<const GDParser::NewLineNode*>(s); + codegen.opcodes.push_back(GDFunction::OPCODE_LINE); + codegen.opcodes.push_back(nl->line); + codegen.current_line=nl->line; + + } break; + case GDParser::Node::TYPE_CONTROL_FLOW: { + // try subblocks + + const GDParser::ControlFlowNode *cf = static_cast<const GDParser::ControlFlowNode*>(s); + + switch(cf->cf_type) { + + + case GDParser::ControlFlowNode::CF_IF: { + +#ifdef DEBUG_ENABLED + codegen.opcodes.push_back(GDFunction::OPCODE_LINE); + codegen.opcodes.push_back(cf->line); + codegen.current_line=cf->line; +#endif + int ret = _parse_expression(codegen,cf->arguments[0],p_stack_level,false); + if (ret<0) + return ERR_PARSE_ERROR; + + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(ret); + int else_addr=codegen.opcodes.size(); + codegen.opcodes.push_back(0); //temporary + + Error err = _parse_block(codegen,cf->body,p_stack_level,p_break_addr,p_continue_addr); + if (err) + return err; + + if (cf->body_else) { + + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + int end_addr=codegen.opcodes.size(); + codegen.opcodes.push_back(0); + codegen.opcodes[else_addr]=codegen.opcodes.size(); + + Error err = _parse_block(codegen,cf->body_else,p_stack_level,p_break_addr,p_continue_addr); + if (err) + return err; + + codegen.opcodes[end_addr]=codegen.opcodes.size(); + } else { + //end without else + codegen.opcodes[else_addr]=codegen.opcodes.size(); + + } + + } break; + case GDParser::ControlFlowNode::CF_FOR: { + + + + int slevel=p_stack_level; + int iter_stack_pos=slevel; + int iterator_pos = (slevel++)|(GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + int counter_pos = (slevel++)|(GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + int container_pos = (slevel++)|(GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + codegen.alloc_stack(slevel); + + codegen.push_stack_identifiers(); + codegen.add_stack_identifier(static_cast<const GDParser::IdentifierNode*>(cf->arguments[0])->name,iter_stack_pos); + + int ret = _parse_expression(codegen,cf->arguments[1],slevel,false); + if (ret<0) + return ERR_COMPILATION_FAILED; + + //assign container + codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN); + codegen.opcodes.push_back(container_pos); + codegen.opcodes.push_back(ret); + + //begin loop + codegen.opcodes.push_back(GDFunction::OPCODE_ITERATE_BEGIN); + codegen.opcodes.push_back(counter_pos); + codegen.opcodes.push_back(container_pos); + codegen.opcodes.push_back(codegen.opcodes.size()+4); + codegen.opcodes.push_back(iterator_pos); + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); //skip code for next + codegen.opcodes.push_back(codegen.opcodes.size()+8); + //break loop + int break_pos=codegen.opcodes.size(); + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); //skip code for next + codegen.opcodes.push_back(0); //skip code for next + //next loop + int continue_pos=codegen.opcodes.size(); + codegen.opcodes.push_back(GDFunction::OPCODE_ITERATE); + codegen.opcodes.push_back(counter_pos); + codegen.opcodes.push_back(container_pos); + codegen.opcodes.push_back(break_pos); + codegen.opcodes.push_back(iterator_pos); + + + Error err = _parse_block(codegen,cf->body,slevel,break_pos,continue_pos); + if (err) + return err; + + + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + codegen.opcodes.push_back(continue_pos); + codegen.opcodes[break_pos+1]=codegen.opcodes.size(); + + + codegen.pop_stack_identifiers(); + + } break; + case GDParser::ControlFlowNode::CF_WHILE: { + + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + codegen.opcodes.push_back(codegen.opcodes.size()+3); + int break_addr=codegen.opcodes.size(); + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + codegen.opcodes.push_back(0); + int continue_addr=codegen.opcodes.size(); + + int ret = _parse_expression(codegen,cf->arguments[0],p_stack_level,false); + if (ret<0) + return ERR_PARSE_ERROR; + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(ret); + codegen.opcodes.push_back(break_addr); + Error err = _parse_block(codegen,cf->body,p_stack_level,break_addr,continue_addr); + if (err) + return err; + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + codegen.opcodes.push_back(continue_addr); + + codegen.opcodes[break_addr+1]=codegen.opcodes.size(); + + } break; + case GDParser::ControlFlowNode::CF_SWITCH: { + + } break; + case GDParser::ControlFlowNode::CF_BREAK: { + + if (p_break_addr<0) { + + _set_error("'break'' not within loop",cf); + return ERR_COMPILATION_FAILED; + } + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + codegen.opcodes.push_back(p_break_addr); + + } break; + case GDParser::ControlFlowNode::CF_CONTINUE: { + + if (p_continue_addr<0) { + + _set_error("'continue' not within loop",cf); + return ERR_COMPILATION_FAILED; + } + + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + codegen.opcodes.push_back(p_continue_addr); + + } break; + case GDParser::ControlFlowNode::CF_RETURN: { + + int ret; + + if (cf->arguments.size()) { + + ret = _parse_expression(codegen,cf->arguments[0],p_stack_level,false); + if (ret<0) + return ERR_PARSE_ERROR; + + } else { + + ret=GDFunction::ADDR_TYPE_NIL << GDFunction::ADDR_BITS; + } + + codegen.opcodes.push_back(GDFunction::OPCODE_RETURN); + codegen.opcodes.push_back(ret); + + } break; + + } + } break; + case GDParser::Node::TYPE_ASSERT: { + // try subblocks + + const GDParser::AssertNode *as = static_cast<const GDParser::AssertNode*>(s); + + int ret = _parse_expression(codegen,as->condition,p_stack_level,false); + if (ret<0) + return ERR_PARSE_ERROR; + + codegen.opcodes.push_back(GDFunction::OPCODE_ASSERT); + codegen.opcodes.push_back(ret); + } break; + case GDParser::Node::TYPE_LOCAL_VAR: { + + + const GDParser::LocalVarNode *lv = static_cast<const GDParser::LocalVarNode*>(s); + + codegen.add_stack_identifier(lv->name,p_stack_level++); + codegen.alloc_stack(p_stack_level); + new_identifiers++; + + } break; + default: { + //expression + int ret = _parse_expression(codegen,s,p_stack_level,true); + if (ret<0) + return ERR_PARSE_ERROR; + } break; + + } + + } + codegen.pop_stack_identifiers(); + return OK; +} + + +Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode *p_class,const GDParser::FunctionNode *p_func) { + + Vector<int> bytecode; + CodeGen codegen; + + codegen.class_node=p_class; + codegen.script=p_script; + codegen.function_node=p_func; + codegen.stack_max=0; + codegen.current_line=0; + codegen.call_max=0; + codegen.debug_stack=ScriptDebugger::get_singleton()!=NULL; + + int stack_level=0; + + if (p_func) { + for(int i=0;i<p_func->arguments.size();i++) { + int idx = i; + codegen.add_stack_identifier(p_func->arguments[i],i); + } + stack_level=p_func->arguments.size(); + } + + codegen.alloc_stack(stack_level); + + /* Parse initializer -if applies- */ + + bool is_initializer=false || !p_func; + + if (!p_func || String(p_func->name)=="_init") { + //parse initializer for class members + if (!p_func && p_class->extends_used && p_script->native.is_null()){ + + //call implicit parent constructor + codegen.opcodes.push_back(GDFunction::OPCODE_CALL_SELF_BASE); + codegen.opcodes.push_back(codegen.get_name_map_pos("_init")); + codegen.opcodes.push_back(0); + codegen.opcodes.push_back((GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS)|0); + + } + Error err = _parse_block(codegen,p_class->initializer,stack_level); + if (err) + return err; + is_initializer=true; + + } + + /* Parse default argument code -if applies- */ + + Vector<int> defarg_addr; + StringName func_name; + + if (p_func) { + if (p_func->default_values.size()) { + + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP_TO_DEF_ARGUMENT); + defarg_addr.push_back(codegen.opcodes.size()); + for(int i=0;i<p_func->default_values.size();i++) { + + _parse_expression(codegen,p_func->default_values[i],stack_level,true); + defarg_addr.push_back(codegen.opcodes.size()); + } + + + defarg_addr.invert(); + } + + + + Error err = _parse_block(codegen,p_func->body,stack_level); + if (err) + return err; + + func_name=p_func->name; + } else { + func_name="_init"; + } + + codegen.opcodes.push_back(GDFunction::OPCODE_END); + + GDFunction *gdfunc=NULL; + + //if (String(p_func->name)=="") { //initializer func + // gdfunc = &p_script->initializer; + + //} else { //regular func + p_script->member_functions[func_name]=GDFunction(); + gdfunc = &p_script->member_functions[func_name]; + //} + + if (p_func) + gdfunc->_static=p_func->_static; + + //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[0]; + const Variant *K=NULL; + while((K=codegen.constant_map.next(K))) { + int idx = codegen.constant_map[*K]; + gdfunc->constants[idx]=*K; + } + } else { + + gdfunc->_constants_ptr=NULL; + 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[E->get()]=E->key(); + } + gdfunc->_global_names_count=gdfunc->global_names.size(); + + } else { + gdfunc->_global_names_ptr = NULL; + gdfunc->_global_names_count =0; + } + + + if (codegen.opcodes.size()) { + + gdfunc->code=codegen.opcodes; + gdfunc->_code_ptr=&gdfunc->code[0]; + gdfunc->_code_size=codegen.opcodes.size(); + + } else { + + gdfunc->_code_ptr=NULL; + gdfunc->_code_size=0; + } + + if (defarg_addr.size()) { + + gdfunc->default_arguments=defarg_addr; + gdfunc->_default_arg_count=defarg_addr.size(); + gdfunc->_default_arg_ptr=&gdfunc->default_arguments[0]; + } else { + gdfunc->_default_arg_count=0; + gdfunc->_default_arg_ptr=NULL; + } + + gdfunc->_argument_count=p_func ? p_func->arguments.size() : 0; + gdfunc->_stack_size=codegen.stack_max; + gdfunc->_call_size=codegen.call_max; + gdfunc->name=func_name; + gdfunc->_script=p_script; + gdfunc->source=source; + if (p_func) { + gdfunc->_initial_line=p_func->line; + } else { + gdfunc->_initial_line=0; + } + + if (codegen.debug_stack) + gdfunc->stack_debug=codegen.stack_debug; + + if (is_initializer) + p_script->initializer=gdfunc; + + + return OK; +} + + + +Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDParser::ClassNode *p_class) { + + + p_script->native=Ref<GDNativeClass>(); + p_script->base=Ref<GDScript>(); + p_script->_base=NULL; + p_script->members.clear(); + p_script->constants.clear(); + p_script->member_functions.clear(); + p_script->member_indices.clear(); + p_script->member_info.clear(); + p_script->initializer=NULL; + p_script->subclasses.clear(); + p_script->_owner=p_owner; + p_script->tool=p_class->tool; + p_script->name=p_class->name; + + + int index_from=0; + + if (p_class->extends_used) { + //do inheritance + String path = p_class->extends_file; + + Ref<GDScript> script; + Ref<GDNativeClass> native; + + if (path!="") { + //path (and optionally subclasses) + + script = ResourceLoader::load(path); + if (script.is_null()) { + _set_error("Could not load base class: "+path,p_class); + return ERR_FILE_NOT_FOUND; + } + + if (p_class->extends_class.size()) { + + for(int i=0;i<p_class->extends_class.size();i++) { + + String sub = p_class->extends_class[i]; + if (script->subclasses.has(sub)) { + + script=script->subclasses[sub]; + } else { + + _set_error("Could not find subclass: "+sub,p_class); + return ERR_FILE_NOT_FOUND; + } + } + } + + } else { + + ERR_FAIL_COND_V(p_class->extends_class.size()==0,ERR_BUG); + //look around for the subclasses + + String base=p_class->extends_class[0]; + GDScript *p = p_owner; + Ref<GDScript> base_class; + + while(p) { + + if (p->subclasses.has(base)) { + + base_class=p->subclasses[base]; + break; + } + p=p->_owner; + } + + if (base_class.is_valid()) { + + for(int i=1;i<p_class->extends_class.size();i++) { + + String subclass=p_class->extends_class[i]; + + if (base_class->subclasses.has(subclass)) { + + base_class=base_class->subclasses[subclass]; + } else { + + _set_error("Could not find subclass: "+subclass,p_class); + return ERR_FILE_NOT_FOUND; + } + } + + script=base_class; + + + } else { + + if (p_class->extends_class.size()>1) { + + _set_error("Invalid inheritance (unknown class+subclasses)",p_class); + return ERR_FILE_NOT_FOUND; + + } + //if not found, try engine classes + if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { + + _set_error("Unknown class: '"+base+"'",p_class); + return ERR_FILE_NOT_FOUND; + } + + int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; + native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; + if (!native.is_valid()) { + + _set_error("Global not a class: '"+base+"'",p_class); + + return ERR_FILE_NOT_FOUND; + } + } + + + } + + if (script.is_valid()) { + + p_script->base=script; + p_script->_base=p_script->base.ptr(); + p_script->member_indices=script->member_indices; + + } else if (native.is_valid()) { + + p_script->native=native; + } else { + + _set_error("Could not determine inheritance",p_class); + return ERR_FILE_NOT_FOUND; + } + + + } + + + for(int i=0;i<p_class->variables.size();i++) { + + StringName name = p_class->variables[i].identifier; + if (p_script->member_indices.has(name)) { + _set_error("Member '"+name+"' already exists (in current or parent class)",p_class); + return ERR_ALREADY_EXISTS; + } + + if (p_class->variables[i]._export.type!=Variant::NIL) { + + p_script->member_info[name]=p_class->variables[i]._export; +#ifdef TOOLS_ENABLED + if (p_class->variables[i].default_value.get_type()!=Variant::NIL) { + + p_script->member_default_values[name]=p_class->variables[i].default_value; + } +#endif + } + + int new_idx = p_script->member_indices.size(); + p_script->member_indices[name]=new_idx; + p_script->members.insert(name); + + } + + for(int i=0;i<p_class->constant_expressions.size();i++) { + + StringName name = p_class->constant_expressions[i].identifier; + ERR_CONTINUE( p_class->constant_expressions[i].expression->type!=GDParser::Node::TYPE_CONSTANT ); + + GDParser::ConstantNode *constant = static_cast<GDParser::ConstantNode*>(p_class->constant_expressions[i].expression); + + p_script->constants.insert(name,constant->value); + //p_script->constants[constant->value].make_const(); + } + + + //parse sub-classes + + for(int i=0;i<p_class->subclasses.size();i++) { + StringName name = p_class->subclasses[i]->name; + + Ref<GDScript> subclass = memnew( GDScript ); + + Error err = _parse_class(subclass.ptr(),p_script,p_class->subclasses[i]); + if (err) + return err; + p_script->subclasses.insert(name,subclass); + + } + + + //parse methods + + bool has_initializer=false; + for(int i=0;i<p_class->functions.size();i++) { + + if (!has_initializer && p_class->functions[i]->name=="_init") + has_initializer=true; + Error err = _parse_function(p_script,p_class,p_class->functions[i]); + if (err) + return err; + } + + //parse static methods + + for(int i=0;i<p_class->static_functions.size();i++) { + + Error err = _parse_function(p_script,p_class,p_class->static_functions[i]); + if (err) + return err; + } + + + if (!has_initializer) { + //create a constructor + Error err = _parse_function(p_script,p_class,NULL); + if (err) + return err; + } + + return OK; +} + +Error GDCompiler::compile(const GDParser *p_parser,GDScript *p_script) { + + err_line=-1; + err_column=-1; + error=""; + parser=p_parser; + const GDParser::Node* root = parser->get_parse_tree(); + ERR_FAIL_COND_V(root->type!=GDParser::Node::TYPE_CLASS,ERR_INVALID_DATA); + + source=p_script->get_path(); + + + + Error err = _parse_class(p_script,NULL,static_cast<const GDParser::ClassNode*>(root)); + + if (err) + return err; + + return OK; + +} + +String GDCompiler::get_error() const { + + return error; +} +int GDCompiler::get_error_line() const{ + + return err_line; +} +int GDCompiler::get_error_column() const{ + + return err_column; +} + +GDCompiler::GDCompiler() +{ +} + + diff --git a/modules/gdscript/gd_compiler.h b/modules/gdscript/gd_compiler.h new file mode 100644 index 0000000000..cda221dab0 --- /dev/null +++ b/modules/gdscript/gd_compiler.h @@ -0,0 +1,181 @@ +/*************************************************************************/ +/* gd_compiler.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +#ifndef GD_COMPILER_H +#define GD_COMPILER_H + +#include "gd_parser.h" +#include "gd_script.h" + + +class GDCompiler { + + const GDParser *parser; + struct CodeGen { + + + GDScript *script; + const GDParser::ClassNode *class_node; + const GDParser::FunctionNode *function_node; + + + bool debug_stack; + + + List< Map<StringName,int> > stack_id_stack; + Map<StringName,int> stack_identifiers; + + List<GDFunction::StackDebug> stack_debug; + List< Map<StringName,int> > block_identifier_stack; + Map<StringName,int> block_identifiers; + + + void add_stack_identifier(const StringName& p_id,int p_stackpos) { + + stack_identifiers[p_id]=p_stackpos; + if (debug_stack) { + + block_identifiers[p_id]=p_stackpos; + GDFunction::StackDebug sd; + sd.added=true; + sd.line=current_line; + sd.identifier=p_id; + sd.pos=p_stackpos; + stack_debug.push_back(sd); + } + } + + void push_stack_identifiers() { + + stack_id_stack.push_back( stack_identifiers ); + if (debug_stack) { + + block_identifier_stack.push_back(block_identifiers); + block_identifiers.clear(); + } + } + + void pop_stack_identifiers() { + + stack_identifiers = stack_id_stack.back()->get(); + stack_id_stack.pop_back(); + + if (debug_stack) { + for (Map<StringName,int>::Element *E=block_identifiers.front();E;E=E->next()) { + + GDFunction::StackDebug sd; + sd.added=false; + sd.identifier=E->key(); + sd.line=current_line; + sd.pos=E->get(); + stack_debug.push_back(sd); + } + block_identifiers=block_identifier_stack.back()->get(); + block_identifier_stack.pop_back(); + } + + } + + + // int get_identifier_pos(const StringName& p_dentifier) const; + HashMap<Variant,int,VariantHasher> constant_map; + Map<StringName,int> name_map; + + int get_name_map_pos(const StringName& p_identifier) { + + int ret; + if (!name_map.has(p_identifier)) { + ret=name_map.size(); + name_map[p_identifier]=ret; + } else { + ret=name_map[p_identifier]; + } + return ret; + } + + + + int get_constant_pos(const Variant& p_constant) { + + + if (constant_map.has(p_constant)) + return constant_map[p_constant]; + int pos = constant_map.size(); + constant_map[p_constant]=pos; + return pos; + } + + Vector<int> opcodes; + void alloc_stack(int p_level) { if (p_level >= stack_max) stack_max=p_level+1; } + void alloc_call(int p_params) { if (p_params >= call_max) call_max=p_params; } + + int current_line; + int stack_max; + int call_max; + }; + +#if 0 + void _create_index(const GDParser::OperatorNode *on); + void _create_call(const GDParser::OperatorNode *on); + + + int _parse_expression(const GDParser::Node *p_expr,CodeGen& codegen); + void _parse_block(GDParser::BlockNode *p_block); + void _parse_function(GDParser::FunctionNode *p_func); + Ref<GDScript> _parse_class(GDParser::ClassNode *p_class); +#endif + + 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); + bool _create_binary_operator(CodeGen& codegen,const GDParser::OperatorNode *on,Variant::Operator op, int p_stack_level); + + //int _parse_subexpression(CodeGen& codegen,const GDParser::BlockNode *p_block,const GDParser::Node *p_expression); + int _parse_assign_right_expression(CodeGen& codegen,const GDParser::OperatorNode *p_expression, int p_stack_level); + int _parse_expression(CodeGen& codegen,const GDParser::Node *p_expression, int p_stack_level,bool p_root=false); + Error _parse_block(CodeGen& codegen,const GDParser::BlockNode *p_block,int p_stack_level=0,int p_break_addr=-1,int p_continue_addr=-1); + Error _parse_function(GDScript *p_script,const GDParser::ClassNode *p_class,const GDParser::FunctionNode *p_func); + Error _parse_class(GDScript *p_script,GDScript *p_owner,const GDParser::ClassNode *p_class); + int err_line; + int err_column; + StringName source; + String error; + +public: + + Error compile(const GDParser *p_parser,GDScript *p_script); + + String get_error() const; + int get_error_line() const; + int get_error_column() const; + + GDCompiler(); +}; + + +#endif // COMPILER_H diff --git a/modules/gdscript/gd_editor.cpp b/modules/gdscript/gd_editor.cpp new file mode 100644 index 0000000000..c10cadf83f --- /dev/null +++ b/modules/gdscript/gd_editor.cpp @@ -0,0 +1,789 @@ +/*************************************************************************/ +/* gd_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_script.h" +#include "gd_compiler.h" + + +void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const { + + p_delimiters->push_back("#"); + +} +void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { + + p_delimiters->push_back("\" \""); + p_delimiters->push_back("' '"); + + +} +String GDScriptLanguage::get_template(const String& p_class_name, const String& p_base_class_name) const { + + String _template = String()+ + "\nextends %BASE%\n\n"+ + "# member variables here, example:\n"+ + "# var a=2\n"+ + "# var b=\"textvar\"\n\n"+ + "func _ready():\n"+ + "\t# Initalization here\n"+ + "\tpass\n"+ + "\n"+ + "\n"; + + return _template.replace("%BASE%",p_base_class_name); +} + + + + +bool GDScriptLanguage::validate(const String& p_script, int &r_line_error,int &r_col_error,String& r_test_error, const String& p_path,List<String> *r_functions) const { + + GDParser parser; + + Error err = parser.parse(p_script,p_path.get_base_dir()); + if (err) { + r_line_error=parser.get_error_line(); + r_col_error=parser.get_error_column(); + r_test_error=parser.get_error(); + return false; + } else { + + const GDParser::Node *root = parser.get_parse_tree(); + ERR_FAIL_COND_V(root->type!=GDParser::Node::TYPE_CLASS,false); + + const GDParser::ClassNode *cl = static_cast<const GDParser::ClassNode*>(root); + Map<int,String> funcs; + for(int i=0;i<cl->functions.size();i++) { + + funcs[cl->functions[i]->line]=cl->functions[i]->name; + } + + for(int i=0;i<cl->static_functions.size();i++) { + + funcs[cl->static_functions[i]->line]=cl->static_functions[i]->name; + } + + for (Map<int,String>::Element *E=funcs.front();E;E=E->next()) { + + r_functions->push_back(E->get()+":"+itos(E->key())); + } + + + } + + return true; +} + +bool GDScriptLanguage::has_named_classes() const { + + return false; +} + +int GDScriptLanguage::find_function(const String& p_function,const String& p_code) const { + + GDTokenizer tokenizer; + tokenizer.set_code(p_code); + int indent=0; + while(tokenizer.get_token()!=GDTokenizer::TK_EOF && tokenizer.get_token()!=GDTokenizer::TK_ERROR) { + + if (tokenizer.get_token()==GDTokenizer::TK_NEWLINE) { + indent=tokenizer.get_token_line_indent(); + } + if (indent==0 && tokenizer.get_token()==GDTokenizer::TK_PR_FUNCTION && tokenizer.get_token(1)==GDTokenizer::TK_IDENTIFIER) { + + String identifier = tokenizer.get_token_identifier(1); + if (identifier==p_function) { + return tokenizer.get_token_line(); + } + } + tokenizer.advance(); + } + return -1; +} + +Script *GDScriptLanguage::create_script() const { + + return memnew( GDScript ); +} + +/* DEBUGGER FUNCTIONS */ + + +bool GDScriptLanguage::debug_break_parse(const String& p_file, int p_line,const String& p_error) { + //break because of parse error + + if (ScriptDebugger::get_singleton() && Thread::get_caller_ID()==Thread::get_main_ID()) { + + _debug_parse_err_line=p_line; + _debug_parse_err_file=p_file; + _debug_error=p_error; + ScriptDebugger::get_singleton()->debug(this,false); + return true; + } else { + return false; + } + +} + +bool GDScriptLanguage::debug_break(const String& p_error,bool p_allow_continue) { + + if (ScriptDebugger::get_singleton() && Thread::get_caller_ID()==Thread::get_main_ID()) { + + _debug_parse_err_line=-1; + _debug_parse_err_file=""; + _debug_error=p_error; + ScriptDebugger::get_singleton()->debug(this,p_allow_continue); + return true; + } else { + return false; + } + +} + +String GDScriptLanguage::debug_get_error() const { + + return _debug_error; +} + +int GDScriptLanguage::debug_get_stack_level_count() const { + + if (_debug_parse_err_line>=0) + return 1; + + + return _debug_call_stack_pos; +} +int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { + + if (_debug_parse_err_line>=0) + return _debug_parse_err_line; + + ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,-1); + + int l = _debug_call_stack_pos - p_level -1; + + return *(_call_stack[l].line); + +} +String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { + + if (_debug_parse_err_line>=0) + return ""; + + ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); + int l = _debug_call_stack_pos - p_level -1; + return _call_stack[l].function->get_name(); +} +String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { + + if (_debug_parse_err_line>=0) + return _debug_parse_err_file; + + ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); + int l = _debug_call_stack_pos - p_level -1; + return _call_stack[l].function->get_script()->get_path(); + +} +void GDScriptLanguage::debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { + + if (_debug_parse_err_line>=0) + return; + + ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); + int l = _debug_call_stack_pos - p_level -1; + + GDFunction *f = _call_stack[l].function; + + List<Pair<StringName,int> > locals; + + f->debug_get_stack_member_state(*_call_stack[l].line,&locals); + for( List<Pair<StringName,int> >::Element *E = locals.front();E;E=E->next() ) { + + p_locals->push_back(E->get().first); + p_values->push_back(_call_stack[l].stack[E->get().second]); + } + +} +void GDScriptLanguage::debug_get_stack_level_members(int p_level,List<String> *p_members, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { + + if (_debug_parse_err_line>=0) + return; + + ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); + int l = _debug_call_stack_pos - p_level -1; + + + GDInstance *instance = _call_stack[l].instance; + + if (!instance) + return; + + Ref<GDScript> script = instance->get_script(); + ERR_FAIL_COND( script.is_null() ); + + + const Map<StringName,int>& mi = script->debug_get_member_indices(); + + for(const Map<StringName,int>::Element *E=mi.front();E;E=E->next()) { + + p_members->push_back(E->key()); + p_values->push_back( instance->debug_get_member_by_index(E->get())); + } + +} +void GDScriptLanguage::debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { + + //no globals are really reachable in gdscript +} +String GDScriptLanguage::debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems,int p_max_depth) { + + if (_debug_parse_err_line>=0) + return ""; + return ""; +} + +void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("gd"); +} + + +void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { + + + for(int i=0;i<GDFunctions::FUNC_MAX;i++) { + + p_functions->push_back(GDFunctions::get_info(GDFunctions::Function(i))); + } +} + +void GDScriptLanguage::get_public_constants(List<Pair<String,Variant> > *p_constants) const { + + Pair<String,Variant> pi; + pi.first="PI"; + pi.second=Math_PI; + p_constants->push_back(pi); +} + +String GDScriptLanguage::make_function(const String& p_class,const String& p_name,const StringArray& p_args) const { + + String s="func "+p_name+"("; + if (p_args.size()) { + s+=" "; + for(int i=0;i<p_args.size();i++) { + if (i>0) + s+=", "; + s+=p_args[i]; + } + s+=" "; + } + s+="):\n\tpass # replace with function body\n"; + + return s; + +} + +static void _parse_native_symbols(const StringName& p_native,bool p_static,List<String>* r_options) { + + if (!p_static) { + List<MethodInfo> methods; + ObjectTypeDB::get_method_list(p_native,&methods); + for(List<MethodInfo>::Element *E=methods.front();E;E=E->next()) { + if (!E->get().name.begins_with("_")) { + r_options->push_back(E->get().name); + } + } + } + + List<String> constants; + ObjectTypeDB::get_integer_constant_list(p_native,&constants); + + for(List<String>::Element *E=constants.front();E;E=E->next()) { + r_options->push_back(E->get()); + } + +} + + +static bool _parse_script_symbols(const Ref<GDScript>& p_script,bool p_static,List<String>* r_options,List<String>::Element *p_indices); + + +static bool _parse_completion_variant(const Variant& p_var,List<String>* r_options,List<String>::Element *p_indices) { + + if (p_indices) { + + bool ok; + Variant si = p_var.get(p_indices->get(),&ok); + if (!ok) + return false; + return _parse_completion_variant(si,r_options,p_indices->next()); + } else { + + switch(p_var.get_type()) { + + + case Variant::DICTIONARY: { + + Dictionary d=p_var; + List<Variant> vl; + d.get_key_list(&vl); + for (List<Variant>::Element *E=vl.front();E;E=E->next()) { + + if (E->get().get_type()==Variant::STRING) + r_options->push_back(E->get()); + } + + + List<MethodInfo> ml; + p_var.get_method_list(&ml); + for(List<MethodInfo>::Element *E=ml.front();E;E=E->next()) { + r_options->push_back(E->get().name); + } + + } break; + case Variant::OBJECT: { + + + Object *o=p_var; + if (o) { + print_line("OBJECT: "+o->get_type()); + if (p_var.is_ref() && o->cast_to<GDScript>()) { + + Ref<GDScript> gds = p_var; + _parse_script_symbols(gds,true,r_options,NULL); + } else if (o->is_type("GDNativeClass")){ + + GDNativeClass *gnc = o->cast_to<GDNativeClass>(); + _parse_native_symbols(gnc->get_name(),false,r_options); + } else { + + print_line("REGULAR BLEND"); + _parse_native_symbols(o->get_type(),false,r_options); + } + } + + } break; + default: { + + List<PropertyInfo> pi; + p_var.get_property_list(&pi); + for(List<PropertyInfo>::Element *E=pi.front();E;E=E->next()) { + r_options->push_back(E->get().name); + } + List<StringName> cl; + + p_var.get_numeric_constants_for_type(p_var.get_type(),&cl); + for(List<StringName>::Element *E=cl.front();E;E=E->next()) { + r_options->push_back(E->get()); + } + + List<MethodInfo> ml; + p_var.get_method_list(&ml); + for(List<MethodInfo>::Element *E=ml.front();E;E=E->next()) { + r_options->push_back(E->get().name); + } + + } break; + } + + return true; + } + + +} + + +static void _parse_expression_node(const GDParser::Node *p_node,List<String>* r_options,List<String>::Element *p_indices) { + + + + if (p_node->type==GDParser::Node::TYPE_CONSTANT) { + + const GDParser::ConstantNode *cn=static_cast<const GDParser::ConstantNode *>(p_node); + _parse_completion_variant(cn->value,r_options,p_indices?p_indices->next():NULL); + } else if (p_node->type==GDParser::Node::TYPE_DICTIONARY) { + + const GDParser::DictionaryNode *dn=static_cast<const GDParser::DictionaryNode*>(p_node); + for(int i=0;i<dn->elements.size();i++) { + + if (dn->elements[i].key->type==GDParser::Node::TYPE_CONSTANT) { + + const GDParser::ConstantNode *cn=static_cast<const GDParser::ConstantNode *>(dn->elements[i].key); + if (cn->value.get_type()==Variant::STRING) { + + String str=cn->value; + if (p_indices) { + + if (str==p_indices->get()) { + _parse_expression_node(dn->elements[i].value,r_options,p_indices->next()); + return; + } + + } else { + r_options->push_back(str); + } + } + } + } + } +} + +static bool _parse_completion_block(const GDParser::BlockNode *p_block,int p_line,List<String>* r_options,List<String>::Element *p_indices) { + + for(int i=0;i<p_block->sub_blocks.size();i++) { + //parse inner first + if (p_line>=p_block->sub_blocks[i]->line && (p_line<=p_block->sub_blocks[i]->end_line || p_block->sub_blocks[i]->end_line==-1)) { + if (_parse_completion_block(p_block->sub_blocks[i],p_line,r_options,p_indices)) + return true; + } + } + + if (p_indices) { + + //parse indices in expressions :| + for (int i=0;i<p_block->statements.size();i++) { + + if (p_block->statements[i]->line>p_line) + break; + + if (p_block->statements[i]->type==GDParser::BlockNode::TYPE_LOCAL_VAR) { + + const GDParser::LocalVarNode *lv=static_cast<const GDParser::LocalVarNode *>(p_block->statements[i]); + if (lv->assign && String(lv->name)==p_indices->get()) { + + _parse_expression_node(lv->assign,r_options,p_indices->next()); + return true; + } + } + } + + } else { + for(int i=0;i<p_block->variables.size();i++) { + //parse variables second + if (p_line>=p_block->variable_lines[i]) { + r_options->push_back(p_block->variables[i]); + } + else break; + + } + } + + return false; +} + + +static bool _parse_script_symbols(const Ref<GDScript>& p_script,bool p_static,List<String>* r_options,List<String>::Element *p_indices) { + + //for (Map<StringName,Ref<GDScript> >::Element ? + + if (!p_static && !p_indices) { + for(const Set<StringName>::Element *E=p_script->get_members().front();E;E=E->next()) { + + r_options->push_back(E->get()); + } + } + + for (const Map<StringName,Variant >::Element *E=p_script->get_constants().front();E;E=E->next()) { + + if( p_indices) { + if (p_indices->get()==String(E->get())) { + _parse_completion_variant(E->get(),r_options,p_indices->next()); + return true; + } + } else { + r_options->push_back(E->key()); + } + } + + if (!p_indices){ + for (const Map<StringName,GDFunction>::Element *E=p_script->get_member_functions().front();E;E=E->next()) { + + if (E->get().is_static() || !p_static) + r_options->push_back(E->key()); + } + } + + if (p_script->get_base().is_valid()){ + if (_parse_script_symbols(p_script->get_base(),p_static,r_options,p_indices)) + return true; + } else if (p_script->get_native().is_valid() && !p_indices) { + _parse_native_symbols(p_script->get_native()->get_name(),p_static,r_options); + } + + return false; +} + + +static bool _parse_completion_class(const String& p_base_path,const GDParser::ClassNode *p_class,int p_line,List<String>* r_options,List<String>::Element *p_indices) { + + + static const char*_type_names[Variant::VARIANT_MAX]={ + "null","bool","int","float","String","Vector2","Rect2","Vector3","Matrix32","Plane","Quat","AABB","Matrix3","Trasnform", + "Color","Image","NodePath","RID","Object","InputEvent","Dictionary","Array","RawArray","IntArray","FloatArray","StringArray", + "Vector2Array","Vector3Array","ColorArray"}; + + if (p_indices && !p_indices->next()) { + for(int i=0;i<Variant::VARIANT_MAX;i++) { + + if (p_indices->get()==_type_names[i]) { + + List<StringName> ic; + + Variant::get_numeric_constants_for_type(Variant::Type(i),&ic); + for(List<StringName>::Element *E=ic.front();E;E=E->next()) { + r_options->push_back(E->get()); + } + return true; + } + } + } + + + + for(int i=0;i<p_class->subclasses.size();i++) { + + if (p_line>=p_class->subclasses[i]->line && (p_line<=p_class->subclasses[i]->end_line || p_class->subclasses[i]->end_line==-1)) { + + if (_parse_completion_class(p_base_path,p_class->subclasses[i],p_line,r_options,p_indices)) + return true; + } + } + + bool in_static_func=false; + + for(int i=0;i<p_class->functions.size();i++) { + + const GDParser::FunctionNode *fu = p_class->functions[i]; + + if (p_line>=fu->body->line && (p_line<=fu->body->end_line || fu->body->end_line==-1)) { + //if in function, first block stuff from outer to inner + if (_parse_completion_block(fu->body,p_line,r_options,p_indices)) + return true; + //then function arguments + if (!p_indices) { + for(int j=0;j<fu->arguments.size();j++) { + + r_options->push_back(fu->arguments[j]); + } + } + } + + } + + for(int i=0;i<p_class->static_functions.size();i++) { + + const GDParser::FunctionNode *fu = p_class->static_functions[i]; + + if (p_line>=fu->body->line && (p_line<=fu->body->end_line || fu->body->end_line==-1)) { + + //if in function, first block stuff from outer to inne + if (_parse_completion_block(fu->body,p_line,r_options,p_indices)) + return true; + //then function arguments + if (!p_indices) { + for(int j=0;j<fu->arguments.size();j++) { + + r_options->push_back(fu->arguments[j]); + } + } + + in_static_func=true; + } + + } + + + //add all local names + if (!p_indices) { + + if (!in_static_func) { + + for(int i=0;i<p_class->variables.size();i++) { + + r_options->push_back(p_class->variables[i].identifier); + } + } + + for(int i=0;i<p_class->constant_expressions.size();i++) { + + r_options->push_back(p_class->constant_expressions[i].identifier); + } + + if (!in_static_func) { + for(int i=0;i<p_class->functions.size();i++) { + + r_options->push_back(p_class->functions[i]->name); + } + } + + for(int i=0;i<p_class->static_functions.size();i++) { + + r_options->push_back(p_class->static_functions[i]->name); + } + } + + + if (p_class->extends_used) { + //do inheritance + String path = p_class->extends_file; + + Ref<GDScript> script; + Ref<GDNativeClass> native; + + if (path!="") { + //path (and optionally subclasses) + + script = ResourceLoader::load(path); + if (script.is_null()) { + return false; + } + + if (p_class->extends_class.size()) { + + for(int i=0;i<p_class->extends_class.size();i++) { + + String sub = p_class->extends_class[i]; + if (script->get_subclasses().has(sub)) { + + script=script->get_subclasses()[sub]; + } else { + + return false; + } + } + } + + } else { + + ERR_FAIL_COND_V(p_class->extends_class.size()==0,false); + //look around for the subclasses + + String base=p_class->extends_class[0]; + Ref<GDScript> base_class; +#if 0 + while(p) { + + if (p->subclasses.has(base)) { + + base_class=p->subclasses[base]; + break; + } + p=p->_owner; + } + + if (base_class.is_valid()) { + + for(int i=1;i<p_class->extends_class.size();i++) { + + String subclass=p_class->extends_class[i]; + + if (base_class->subclasses.has(subclass)) { + + base_class=base_class->subclasses[subclass]; + } else { + + _set_error("Could not find subclass: "+subclass,p_class); + return ERR_FILE_NOT_FOUND; + } + } + + + } else { +#endif + if (p_class->extends_class.size()>1) { + + return false; + + } + //if not found, try engine classes + if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { + return false; + } + + int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; + native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; + if (!native.is_valid()) { + return false; + } +#if 0 + } +#endif + + } + + if (script.is_valid()) { + if (_parse_script_symbols(script,in_static_func,r_options,p_indices)) + return true; + + } else if (native.is_valid() && !p_indices) { + + _parse_native_symbols(native->get_name(),in_static_func,r_options); + } + } + + return false; + +} + + +Error GDScriptLanguage::complete_keyword(const String& p_code, int p_line, const String& p_base_path, const String& p_base, List<String>* r_options) { + + GDParser p; + Error err = p.parse(p_code,p_base_path); + // don't care much about error I guess + const GDParser::Node* root = p.get_parse_tree(); + ERR_FAIL_COND_V(root->type!=GDParser::Node::TYPE_CLASS,ERR_INVALID_DATA); + + const GDParser::ClassNode *cl = static_cast<const GDParser::ClassNode*>(root); + + List<String> indices; + Vector<String> spl = p_base.split("."); + + for(int i=0;i<spl.size()-1;i++) { + indices.push_back(spl[i]); + } + + if (_parse_completion_class(p_base,cl,p_line,r_options,indices.front())) + return OK; + //and the globals x_x? + for(Map<StringName,int>::Element *E=globals.front();E;E=E->next()) { + if (!indices.empty()) { + if (String(E->key())==indices.front()->get()) { + + _parse_completion_variant(global_array[E->get()],r_options,indices.front()->next()); + + return OK; + } + } else { + r_options->push_back(E->key()); + } + } + + return OK; +} + diff --git a/modules/gdscript/gd_functions.cpp b/modules/gdscript/gd_functions.cpp new file mode 100644 index 0000000000..2930d9322c --- /dev/null +++ b/modules/gdscript/gd_functions.cpp @@ -0,0 +1,1218 @@ +/*************************************************************************/ +/* gd_functions.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_functions.h" +#include "math_funcs.h" +#include "object_type_db.h" +#include "reference.h" +#include "gd_script.h" +#include "os/os.h" + +const char *GDFunctions::get_func_name(Function p_func) { + + ERR_FAIL_INDEX_V(p_func,FUNC_MAX,""); + + static const char *_names[FUNC_MAX]={ + "sin", + "cos", + "tan", + "sinh", + "cosh", + "tanh", + "asin", + "acos", + "atan", + "atan2", + "sqrt", + "fmod", + "fposmod", + "floor", + "ceil", + "round", + "abs", + "sign", + "pow", + "log", + "exp", + "is_nan", + "is_inf", + "ease", + "decimals", + "stepify", + "lerp", + "dectime", + "randomize", + "randi", + "randf", + "rand_range", + "rand_seed", + "deg2rad", + "rad2deg", + "linear2db", + "db2linear", + "max", + "min", + "clamp", + "nearest_po2", + "weakref", + "convert", + "typeof", + "str", + "print", + "printt", + "printerr", + "printraw", + "range", + "load", + "inst2dict", + "dict2inst", + "print_stack", + }; + + return _names[p_func]; + +} + +void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Variant &r_ret,Variant::CallError &r_error) { + + r_error.error=Variant::CallError::CALL_OK; +#ifdef DEBUG_ENABLED + +#define VALIDATE_ARG_COUNT(m_count) \ + if (p_arg_count<m_count) {\ + r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;\ + r_error.argument=m_count;\ + return;\ + }\ + if (p_arg_count>m_count) {\ + r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;\ + r_error.argument=m_count;\ + return;\ + } + +#define VALIDATE_ARG_NUM(m_arg) \ + if (!p_args[m_arg]->is_num()) {\ + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;\ + r_error.argument=m_arg;\ + r_error.expected=Variant::REAL;\ + return;\ + } + +#else + +#define VALIDATE_ARG_COUNT(m_count) +#define VALIDATE_ARG_NUM(m_arg) +#endif + + //using a switch, so the compiler generates a jumptable + + switch(p_func) { + + case MATH_SIN: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::sin(*p_args[0]); + } break; + case MATH_COS: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::cos(*p_args[0]); + } break; + case MATH_TAN: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::tan(*p_args[0]); + } break; + case MATH_SINH: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::sinh(*p_args[0]); + } break; + case MATH_COSH: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::cosh(*p_args[0]); + } break; + case MATH_TANH: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::tanh(*p_args[0]); + } break; + case MATH_ASIN: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::asin(*p_args[0]); + } break; + case MATH_ACOS: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::acos(*p_args[0]); + } break; + case MATH_ATAN: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::atan(*p_args[0]); + } break; + case MATH_ATAN2: { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + r_ret=Math::atan2(*p_args[0],*p_args[1]); + } break; + case MATH_SQRT: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::sqrt(*p_args[0]); + } break; + case MATH_FMOD: { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + r_ret=Math::fmod(*p_args[0],*p_args[1]); + } break; + case MATH_FPOSMOD: { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + r_ret=Math::fposmod(*p_args[0],*p_args[1]); + } break; + case MATH_FLOOR: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::floor(*p_args[0]); + } break; + case MATH_CEIL: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::ceil(*p_args[0]); + } break; + case MATH_ROUND: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::round(*p_args[0]); + } break; + case MATH_ABS: { + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type()==Variant::INT) { + + int64_t i = *p_args[0]; + r_ret=ABS(i); + } else if (p_args[0]->get_type()==Variant::REAL) { + + real_t r = *p_args[0]; + r_ret=Math::abs(r); + } else { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::REAL; + } + } break; + case MATH_SIGN: { + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type()==Variant::INT) { + + int64_t i = *p_args[0]; + r_ret= i < 0 ? -1 : ( i > 0 ? +1 : 0); + } else if (p_args[0]->get_type()==Variant::REAL) { + + real_t r = *p_args[0]; + r_ret= r < 0.0 ? -1.0 : ( r > 0.0 ? +1.0 : 0.0); + } else { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::REAL; + } + } break; + case MATH_POW: { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + r_ret=Math::pow(*p_args[0],*p_args[1]); + } break; + case MATH_LOG: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::log(*p_args[0]); + } break; + case MATH_EXP: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::exp(*p_args[0]); + } break; + case MATH_ISNAN: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::is_nan(*p_args[0]); + } break; + case MATH_ISINF: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::is_inf(*p_args[0]); + } break; + case MATH_EASE: { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + r_ret=Math::ease(*p_args[0],*p_args[1]); + } break; + case MATH_DECIMALS: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::decimals(*p_args[0]); + } break; + case MATH_STEPIFY: { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + r_ret=Math::stepify(*p_args[0],*p_args[1]); + } break; + case MATH_LERP: { + VALIDATE_ARG_COUNT(3); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + r_ret=Math::lerp(*p_args[0],*p_args[1],*p_args[2]); + } break; + case MATH_DECTIME: { + VALIDATE_ARG_COUNT(3); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + r_ret=Math::dectime(*p_args[0],*p_args[1],*p_args[2]); + } break; + case MATH_RANDOMIZE: { + Math::randomize(); + r_ret=Variant(); + } break; + case MATH_RAND: { + r_ret=Math::rand(); + } break; + case MATH_RANDF: { + r_ret=Math::randf(); + } break; + case MATH_RANDOM: { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + r_ret=Math::random(*p_args[0],*p_args[1]); + } break; + case MATH_RANDSEED: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + uint32_t seed=*p_args[0]; + int ret = Math::rand_from_seed(&seed); + Array reta; + reta.push_back(ret); + reta.push_back(seed); + r_ret=reta; + + } break; + case MATH_DEG2RAD: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::deg2rad(*p_args[0]); + } break; + case MATH_RAD2DEG: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::rad2deg(*p_args[0]); + } break; + case MATH_LINEAR2DB: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::linear2db(*p_args[0]); + } break; + case MATH_DB2LINEAR: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + r_ret=Math::db2linear(*p_args[0]); + } break; + case LOGIC_MAX: { + VALIDATE_ARG_COUNT(2); + if (p_args[0]->get_type()==Variant::INT && p_args[1]->get_type()==Variant::INT) { + + int64_t a = *p_args[0]; + int64_t b = *p_args[1]; + r_ret=MAX(a,b); + } else { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + + real_t a = *p_args[0]; + real_t b = *p_args[1]; + + r_ret=MAX(a,b); + } + + } break; + case LOGIC_MIN: { + VALIDATE_ARG_COUNT(2); + if (p_args[0]->get_type()==Variant::INT && p_args[1]->get_type()==Variant::INT) { + + int64_t a = *p_args[0]; + int64_t b = *p_args[1]; + r_ret=MIN(a,b); + } else { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + + real_t a = *p_args[0]; + real_t b = *p_args[1]; + + r_ret=MIN(a,b); + } + } break; + case LOGIC_CLAMP: { + VALIDATE_ARG_COUNT(3); + if (p_args[0]->get_type()==Variant::INT && p_args[1]->get_type()==Variant::INT && p_args[2]->get_type()==Variant::INT) { + + int64_t a = *p_args[0]; + int64_t b = *p_args[1]; + int64_t c = *p_args[2]; + r_ret=CLAMP(a,b,c); + } else { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + + real_t a = *p_args[0]; + real_t b = *p_args[1]; + real_t c = *p_args[2]; + + r_ret=CLAMP(a,b,c); + } + } break; + case LOGIC_NEAREST_PO2: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + int64_t num = *p_args[0]; + r_ret = nearest_power_of_2(num); + } break; + case OBJ_WEAKREF: { + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type()!=Variant::OBJECT) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::OBJECT; + return; + + } + + if (p_args[0]->is_ref()) { + + REF r = *p_args[0]; + if (!r.is_valid()) { + r_ret=Variant(); + return; + } + + Ref<WeakRef> wref = memnew( WeakRef ); + wref->set_ref(r); + r_ret=wref; + } else { + Object *obj = *p_args[0]; + if (!obj) { + r_ret=Variant(); + return; + } + Ref<WeakRef> wref = memnew( WeakRef ); + wref->set_obj(obj); + r_ret=wref; + } + + + + + } break; + case TYPE_CONVERT: { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_NUM(1); + int type=*p_args[1]; + if (type<0 || type>=Variant::VARIANT_MAX) { + + ERR_PRINT("Invalid type argument to convert()"); + r_ret=Variant::NIL; + + } else { + + + r_ret=Variant::construct(Variant::Type(type),p_args,1,r_error); + } + } break; + case TYPE_OF: { + + VALIDATE_ARG_COUNT(1); + r_ret = p_args[0]->get_type(); + + } break; + case TEXT_STR: { + + String str; + for(int i=0;i<p_arg_count;i++) { + + String os = p_args[i]->operator String();; + if (i==0) + str=os; + else + str+=os; + } + + r_ret=str; + + } break; + case TEXT_PRINT: { + + String str; + for(int i=0;i<p_arg_count;i++) { + + str+=p_args[i]->operator String(); + } + + //str+="\n"; + print_line(str); + r_ret=Variant(); + + + } break; + case TEXT_PRINT_TABBED: { + + String str; + for(int i=0;i<p_arg_count;i++) { + + if (i) + str+="\t"; + str+=p_args[i]->operator String(); + } + + //str+="\n"; + print_line(str); + r_ret=Variant(); + + + } break; + + case TEXT_PRINTERR: { + + String str; + for(int i=0;i<p_arg_count;i++) { + + str+=p_args[i]->operator String(); + } + + //str+="\n"; + OS::get_singleton()->printerr("%s\n",str.utf8().get_data()); + r_ret=Variant(); + + } break; + case TEXT_PRINTRAW: { + String str; + for(int i=0;i<p_arg_count;i++) { + + str+=p_args[i]->operator String(); + } + + //str+="\n"; + OS::get_singleton()->print("%s\n",str.utf8().get_data()); + r_ret=Variant(); + + } break; + case GEN_RANGE: { + + + + switch(p_arg_count) { + + case 0: { + + r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument=1; + + } break; + case 1: { + + VALIDATE_ARG_NUM(0); + int count=*p_args[0]; + Array arr(true); + if (count<=0) { + r_ret=arr; + return; + } + Error err = arr.resize(count); + if (err!=OK) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_ret=Variant(); + return; + } + + for(int i=0;i<count;i++) { + arr[i]=i; + } + + r_ret=arr; + } break; + case 2: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + + int from=*p_args[0]; + int to=*p_args[1]; + + Array arr(true); + if (from>=to) { + r_ret=arr; + return; + } + Error err = arr.resize(to-from); + if (err!=OK) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_ret=Variant(); + return; + } + for(int i=from;i<to;i++) + arr[i-from]=i; + r_ret=arr; + } break; + case 3: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + + int from=*p_args[0]; + int to=*p_args[1]; + int incr=*p_args[2]; + if (incr==0) { + + ERR_EXPLAIN("step argument is zero!"); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + ERR_FAIL(); + } + + Array arr(true); + if (from>=to && incr>0) { + r_ret=arr; + return; + } + if (from<=to && incr<0) { + r_ret=arr; + return; + } + + //calculate how many + int count=0; + if (incr>0) { + + count=((to-from-1)/incr)+1; + } else { + + count=((from-to-1)/-incr)+1; + } + + + Error err = arr.resize(count); + + if (err!=OK) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_ret=Variant(); + return; + } + + if (incr>0) { + int idx=0; + for(int i=from;i<to;i+=incr) { + arr[idx++]=i; + } + } else { + + int idx=0; + for(int i=from;i>to;i+=incr) { + arr[idx++]=i; + } + } + + r_ret=arr; + } break; + default: { + + r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.argument=3; + } break; + } + + } break; + case RESOURCE_LOAD: { + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type()!=Variant::STRING) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_ret=Variant(); + } + r_ret=ResourceLoader::load(*p_args[0]); + + } + case INST2DICT: { + + VALIDATE_ARG_COUNT(1); + + if (p_args[0]->get_type()==Variant::NIL) { + r_ret=Variant(); + } else if (p_args[0]->get_type()!=Variant::OBJECT) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_ret=Variant(); + } else { + + Object *obj = *p_args[0]; + if (!obj) { + r_ret=Variant(); + + } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language()!=GDScriptLanguage::get_singleton()) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::DICTIONARY; + ERR_PRINT("Not a script with an instance"); + + } else { + + GDInstance *ins = static_cast<GDInstance*>(obj->get_script_instance()); + Ref<GDScript> base = ins->get_script(); + if (base.is_null()) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::DICTIONARY; + ERR_PRINT("Not based on a script"); + return; + + } + + + GDScript *p = base.ptr(); + Vector<StringName> sname; + + while(p->_owner) { + + sname.push_back(p->name); + p=p->_owner; + } + sname.invert(); + + + if (!p->path.is_resource_file()) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::DICTIONARY; + print_line("PATH: "+p->path); + ERR_PRINT("Not based on a resource file"); + + return; + } + + NodePath cp(sname,Vector<StringName>(),false); + + Dictionary d(true); + d["@subpath"]=cp; + d["@path"]=p->path; + + + p = base.ptr(); + + while(p) { + + for(Set<StringName>::Element *E=p->members.front();E;E=E->next()) { + + Variant value; + if (ins->get(E->get(),value)) { + + String k = E->get(); + if (!d.has(k)) { + d[k]=value; + } + } + } + + p=p->_base; + } + + r_ret=d; + + } + } + + } break; + case DICT2INST: { + + VALIDATE_ARG_COUNT(1); + + if (p_args[0]->get_type()!=Variant::DICTIONARY) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::DICTIONARY; + return; + } + + Dictionary d = *p_args[0]; + + if (!d.has("@path")) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::OBJECT; + return; + } + + Ref<Script> scr = ResourceLoader::load(d["@path"]); + if (!scr.is_valid()) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::OBJECT; + return; + } + + Ref<GDScript> gdscr = scr; + + if (!gdscr.is_valid()) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::OBJECT; + return; + } + + NodePath sub; + if (d.has("@subpath")) { + sub=d["@subpath"]; + } + + for(int i=0;i<sub.get_name_count();i++) { + + gdscr = gdscr->subclasses[ sub.get_name(i)]; + if (!gdscr.is_valid()) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::OBJECT; + return; + } + } + + + r_ret = gdscr->_new(NULL,0,r_error); + + } break; + + case PRINT_STACK: { + + ScriptLanguage* script = GDScriptLanguage::get_singleton(); + for (int i=0; i < script->debug_get_stack_level_count(); i++) { + + print_line("Frame "+itos(i)+" - "+script->debug_get_stack_level_source(i)+":"+itos(script->debug_get_stack_level_line(i))+" in function '"+script->debug_get_stack_level_function(i)+"'"); + }; + } break; + + case FUNC_MAX: { + + ERR_FAIL_V(); + } break; + + } + +} + +bool GDFunctions::is_deterministic(Function p_func) { + + //man i couldn't have chosen a worse function name, + //way too controversial.. + + switch(p_func) { + + case MATH_SIN: + case MATH_COS: + case MATH_TAN: + case MATH_SINH: + case MATH_COSH: + case MATH_TANH: + case MATH_ASIN: + case MATH_ACOS: + case MATH_ATAN: + case MATH_ATAN2: + case MATH_SQRT: + case MATH_FMOD: + case MATH_FPOSMOD: + case MATH_FLOOR: + case MATH_CEIL: + case MATH_ROUND: + case MATH_ABS: + case MATH_SIGN: + case MATH_POW: + case MATH_LOG: + case MATH_EXP: + case MATH_ISNAN: + case MATH_ISINF: + case MATH_EASE: + case MATH_DECIMALS: + case MATH_STEPIFY: + case MATH_LERP: + case MATH_DECTIME: + case MATH_DEG2RAD: + case MATH_RAD2DEG: + case MATH_LINEAR2DB: + case MATH_DB2LINEAR: + case LOGIC_MAX: + case LOGIC_MIN: + case LOGIC_CLAMP: + case LOGIC_NEAREST_PO2: + case TYPE_CONVERT: + case TYPE_OF: + case TEXT_STR: +// enable for debug only, otherwise not desirable - case GEN_RANGE: + return true; + default: + return false; + + } + + return false; + + +} + +MethodInfo GDFunctions::get_info(Function p_func) { + +#ifdef TOOLS_ENABLED + //using a switch, so the compiler generates a jumptable + + switch(p_func) { + + case MATH_SIN: { + MethodInfo mi("sin",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + + } break; + case MATH_COS: { + MethodInfo mi("cos",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_TAN: { + MethodInfo mi("tan",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_SINH: { + MethodInfo mi("sinh",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_COSH: { + MethodInfo mi("cosh",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_TANH: { + MethodInfo mi("tanh",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_ASIN: { + MethodInfo mi("asin",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_ACOS: { + MethodInfo mi("acos",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_ATAN: { + MethodInfo mi("atan",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_ATAN2: { + MethodInfo mi("atan2",PropertyInfo(Variant::REAL,"x"),PropertyInfo(Variant::REAL,"y")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_SQRT: { + MethodInfo mi("sqrt",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_FMOD: { + MethodInfo mi("fmod",PropertyInfo(Variant::REAL,"x"),PropertyInfo(Variant::REAL,"y")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_FPOSMOD: { + MethodInfo mi("fposmod",PropertyInfo(Variant::REAL,"x"),PropertyInfo(Variant::REAL,"y")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_FLOOR: { + MethodInfo mi("floor",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_CEIL: { + MethodInfo mi("ceil",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_ROUND: { + MethodInfo mi("round",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_ABS: { + MethodInfo mi("abs",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_SIGN: { + MethodInfo mi("sign",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_POW: { + MethodInfo mi("pow",PropertyInfo(Variant::REAL,"x"),PropertyInfo(Variant::REAL,"y")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_LOG: { + MethodInfo mi("log",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_EXP: { + MethodInfo mi("exp",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_ISNAN: { + MethodInfo mi("isnan",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_ISINF: { + MethodInfo mi("isinf",PropertyInfo(Variant::REAL,"s")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_EASE: { + MethodInfo mi("ease",PropertyInfo(Variant::REAL,"s"),PropertyInfo(Variant::REAL,"curve")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_DECIMALS: { + MethodInfo mi("decimals",PropertyInfo(Variant::REAL,"step")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_STEPIFY: { + MethodInfo mi("stepify",PropertyInfo(Variant::REAL,"s"),PropertyInfo(Variant::REAL,"step")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_LERP: { + MethodInfo mi("lerp",PropertyInfo(Variant::REAL,"a"),PropertyInfo(Variant::REAL,"b"), PropertyInfo(Variant::REAL,"c")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_DECTIME: { + MethodInfo mi("dectime",PropertyInfo(Variant::REAL,"value"),PropertyInfo(Variant::REAL,"amount"),PropertyInfo(Variant::REAL,"step")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_RANDOMIZE: { + MethodInfo mi("randomize"); + mi.return_val.type=Variant::NIL; + return mi; + } break; + case MATH_RAND: { + MethodInfo mi("rand"); + mi.return_val.type=Variant::INT; + return mi; + } break; + case MATH_RANDF: { + MethodInfo mi("randf"); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_RANDOM: { + MethodInfo mi("rand_range",PropertyInfo(Variant::REAL,"from"),PropertyInfo(Variant::REAL,"to")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_RANDSEED: { + MethodInfo mi("rand_seed",PropertyInfo(Variant::REAL,"seed")); + mi.return_val.type=Variant::ARRAY; + return mi; + } break; + case MATH_DEG2RAD: { + MethodInfo mi("deg2rad",PropertyInfo(Variant::REAL,"deg")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_RAD2DEG: { + MethodInfo mi("rad2deg",PropertyInfo(Variant::REAL,"rad")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_LINEAR2DB: { + MethodInfo mi("linear2db",PropertyInfo(Variant::REAL,"nrg")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case MATH_DB2LINEAR: { + MethodInfo mi("db2linear",PropertyInfo(Variant::REAL,"db")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case LOGIC_MAX: { + MethodInfo mi("max",PropertyInfo(Variant::REAL,"a"),PropertyInfo(Variant::REAL,"b")); + mi.return_val.type=Variant::REAL; + return mi; + + } break; + case LOGIC_MIN: { + MethodInfo mi("min",PropertyInfo(Variant::REAL,"a"),PropertyInfo(Variant::REAL,"b")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case LOGIC_CLAMP: { + MethodInfo mi("clamp",PropertyInfo(Variant::REAL,"val"),PropertyInfo(Variant::REAL,"min"),PropertyInfo(Variant::REAL,"max")); + mi.return_val.type=Variant::REAL; + return mi; + } break; + case LOGIC_NEAREST_PO2: { + MethodInfo mi("nearest_po2",PropertyInfo(Variant::INT,"val")); + mi.return_val.type=Variant::INT; + return mi; + } break; + case OBJ_WEAKREF: { + + MethodInfo mi("weakref",PropertyInfo(Variant::OBJECT,"obj")); + mi.return_val.type=Variant::OBJECT; + return mi; + + } break; + case TYPE_CONVERT: { + + MethodInfo mi("convert",PropertyInfo(Variant::NIL,"what"),PropertyInfo(Variant::INT,"type")); + mi.return_val.type=Variant::OBJECT; + return mi; + } break; + case TYPE_OF: { + MethodInfo mi("typeof",PropertyInfo(Variant::NIL,"what")); + mi.return_val.type=Variant::INT; + }; + case TEXT_STR: { + + MethodInfo mi("str",PropertyInfo(Variant::NIL,"what"),PropertyInfo(Variant::NIL,"...")); + mi.return_val.type=Variant::STRING; + return mi; + + } break; + case TEXT_PRINT: { + + MethodInfo mi("print",PropertyInfo(Variant::NIL,"what"),PropertyInfo(Variant::NIL,"...")); + mi.return_val.type=Variant::NIL; + return mi; + + } break; + case TEXT_PRINT_TABBED: { + + MethodInfo mi("printt",PropertyInfo(Variant::NIL,"what"),PropertyInfo(Variant::NIL,"...")); + mi.return_val.type=Variant::NIL; + return mi; + + } break; + case TEXT_PRINTERR: { + + MethodInfo mi("printerr",PropertyInfo(Variant::NIL,"what"),PropertyInfo(Variant::NIL,"...")); + mi.return_val.type=Variant::NIL; + return mi; + + } break; + case TEXT_PRINTRAW: { + + MethodInfo mi("printraw",PropertyInfo(Variant::NIL,"what"),PropertyInfo(Variant::NIL,"...")); + mi.return_val.type=Variant::NIL; + return mi; + + } break; + case GEN_RANGE: { + + MethodInfo mi("range",PropertyInfo(Variant::NIL,"...")); + mi.return_val.type=Variant::ARRAY; + return mi; + } break; + case RESOURCE_LOAD: { + + MethodInfo mi("load",PropertyInfo(Variant::STRING,"path")); + mi.return_val.type=Variant::OBJECT; + return mi; + } break; + case INST2DICT: { + + MethodInfo mi("inst2dict",PropertyInfo(Variant::OBJECT,"inst")); + mi.return_val.type=Variant::DICTIONARY; + return mi; + } break; + case DICT2INST: { + + MethodInfo mi("dict2inst",PropertyInfo(Variant::DICTIONARY,"dict")); + mi.return_val.type=Variant::OBJECT; + return mi; + } break; + + case PRINT_STACK: { + MethodInfo mi("print_stack"); + mi.return_val.type=Variant::NIL; + return mi; + } break; + + case FUNC_MAX: { + + ERR_FAIL_V(MethodInfo()); + } break; + + } +#endif + + return MethodInfo(); +} diff --git a/modules/gdscript/gd_functions.h b/modules/gdscript/gd_functions.h new file mode 100644 index 0000000000..2ab397d18a --- /dev/null +++ b/modules/gdscript/gd_functions.h @@ -0,0 +1,103 @@ +/*************************************************************************/ +/* gd_functions.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +#ifndef GD_FUNCTIONS_H +#define GD_FUNCTIONS_H + +#include "variant.h" + +class GDFunctions { +public: + + enum Function { + MATH_SIN, + MATH_COS, + MATH_TAN, + MATH_SINH, + MATH_COSH, + MATH_TANH, + MATH_ASIN, + MATH_ACOS, + MATH_ATAN, + MATH_ATAN2, + MATH_SQRT, + MATH_FMOD, + MATH_FPOSMOD, + MATH_FLOOR, + MATH_CEIL, + MATH_ROUND, + MATH_ABS, + MATH_SIGN, + MATH_POW, + MATH_LOG, + MATH_EXP, + MATH_ISNAN, + MATH_ISINF, + MATH_EASE, + MATH_DECIMALS, + MATH_STEPIFY, + MATH_LERP, + MATH_DECTIME, + MATH_RANDOMIZE, + MATH_RAND, + MATH_RANDF, + MATH_RANDOM, + MATH_RANDSEED, + MATH_DEG2RAD, + MATH_RAD2DEG, + MATH_LINEAR2DB, + MATH_DB2LINEAR, + LOGIC_MAX, + LOGIC_MIN, + LOGIC_CLAMP, + LOGIC_NEAREST_PO2, + OBJ_WEAKREF, + TYPE_CONVERT, + TYPE_OF, + TEXT_STR, + TEXT_PRINT, + TEXT_PRINT_TABBED, + TEXT_PRINTERR, + TEXT_PRINTRAW, + GEN_RANGE, + RESOURCE_LOAD, + INST2DICT, + DICT2INST, + PRINT_STACK, + FUNC_MAX + + }; + + static const char *get_func_name(Function p_func); + static void call(Function p_func,const Variant **p_args,int p_arg_count,Variant &r_ret,Variant::CallError &r_error); + static bool is_deterministic(Function p_func); + static MethodInfo get_info(Function p_func); + +}; + +#endif // GD_FUNCTIONS_H diff --git a/modules/gdscript/gd_parser.cpp b/modules/gdscript/gd_parser.cpp new file mode 100644 index 0000000000..e558ceb416 --- /dev/null +++ b/modules/gdscript/gd_parser.cpp @@ -0,0 +1,2469 @@ +/*************************************************************************/ +/* gd_parser.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "print_string.h" +#include "io/resource_loader.h" +/* TODO: + + *Property reduce constant expressions + *Implement missing operators in variant? + *constructor + */ + +/* + todo: + fix post ++,-- + make sure ++,-- don't work on constant expressions + seems passing parent node as param is not needed + */ + +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) { + + _set_error("':' expected at end of line."); + return false; + } + tokenizer.advance(); + + if (tokenizer.get_token()!=GDTokenizer::TK_NEWLINE) { + + _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) + 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) { + + if (tokenizer.get_token()==GDTokenizer::TK_PARENTHESIS_CLOSE) { + tokenizer.advance(); + } else { + + while(true) { + + + 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(); + } else { + // something is broken + _set_error("Expected ',' or ')'"); + return false; + } + + } + } + + return true; + +} + + + +GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_allow_assign) { + +// Vector<Node*> expressions; +// Vector<OperatorNode::Operator> operators; + + Vector<Expression> expression; + + Node *expr=NULL; + + while(true) { + + + /*****************/ + /* Parse Operand */ + /*****************/ + + if (tokenizer.get_token()==GDTokenizer::TK_PARENTHESIS_OPEN) { + //subexpression () + tokenizer.advance(); + Node* subexpr = _parse_expression(p_parent,p_static); + 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_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_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_CONSTANT || tokenizer.get_token_constant().get_type()!=Variant::STRING) { + _set_error("Expected string constant as 'preload' argument."); + return NULL; + } + + + String path = tokenizer.get_token_constant(); + if (!path.is_abs_path() && base_path!="") + path=base_path+"/"+path; + + Ref<Resource> res = ResourceLoader::load(path); + if (!res.is_valid()) { + _set_error("Can't preload resource at path: "+path); + return NULL; + } + + tokenizer.advance(); + + if (tokenizer.get_token()!=GDTokenizer::TK_PARENTHESIS_CLOSE) { + _set_error("Expected ')' after 'preload' path"); + return NULL; + } + + ConstantNode *constant = alloc_node<ConstantNode>(); + constant->value=res; + tokenizer.advance(); + + expr=constant; + + } 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); + if (tokenizer.get_token()!=GDTokenizer::TK_IDENTIFIER) { + + _set_error("Built-in type constant expected after '.'"); + return NULL; + } + StringName identifier = tokenizer.get_token_identifier(); + 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; + tokenizer.advance(); + + } else if (tokenizer.get_token(1)==GDTokenizer::TK_PARENTHESIS_OPEN && (tokenizer.get_token()==GDTokenizer::TK_BUILT_IN_TYPE || tokenizer.get_token()==GDTokenizer::TK_IDENTIFIER || tokenizer.get_token()==GDTokenizer::TK_BUILT_IN_FUNC)) { + //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); + } 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); + } else { + + SelfNode *self = alloc_node<SelfNode>(); + op->arguments.push_back(self); + + IdentifierNode* id = alloc_node<IdentifierNode>(); + id->name=tokenizer.get_token_identifier(); + op->arguments.push_back(id); + } + + tokenizer.advance(2); + if (!_parse_arguments(op,op->arguments,p_static)) + return NULL; + + expr=op; + + } else if (tokenizer.get_token()==GDTokenizer::TK_IDENTIFIER) { + //identifier (reference) + + IdentifierNode *id = alloc_node<IdentifierNode>(); + id->name=tokenizer.get_token_identifier(); + tokenizer.advance(); + 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 + OperatorNode *op = alloc_node<OperatorNode>(); + + Expression e; + e.is_op=true; + + switch(tokenizer.get_token()) { + 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); + 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; + + 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.get_token()==GDTokenizer::TK_IDENTIFIER && tokenizer.get_token(1)==GDTokenizer::TK_OP_ASSIGN) { + //lua style identifier, easier to write + ConstantNode *cn = alloc_node<ConstantNode>(); + cn->value = tokenizer.get_token_identifier(); + key = cn; + tokenizer.advance(2); + expecting=DICT_EXPECT_VALUE; + } else { + //python/js style more flexible + key = _parse_expression(dict,p_static); + if (!key) + return NULL; + expecting=DICT_EXPECT_COLON; + } + } + + if (expecting==DICT_EXPECT_VALUE) { + Node *value = _parse_expression(dict,p_static); + if (!value) + return NULL; + expecting=DICT_EXPECT_COMMA; + + 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.get_token(1)==GDTokenizer::TK_IDENTIFIER && tokenizer.get_token(2)==GDTokenizer::TK_PARENTHESIS_OPEN) { + // 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 */ + + IdentifierNode* id = alloc_node<IdentifierNode>(); + id->name=tokenizer.get_token_identifier(); + op->arguments.push_back(id); + + tokenizer.advance(2); + 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_IDENTIFIER && tokenizer.get_token(1)!=GDTokenizer::TK_BUILT_IN_FUNC ) { + _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; + + IdentifierNode * id = alloc_node<IdentifierNode>(); + if (tokenizer.get_token(1)==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(1)); + } else { + id->name=tokenizer.get_token_identifier(1); + } + + op->arguments.push_back(expr); // call what + op->arguments.push_back(id); // call func + //get arguments + tokenizer.advance(3); + if (!_parse_arguments(op,op->arguments,p_static)) + return NULL; + expr=op; + + } else { + //simple indexing! + OperatorNode * op = alloc_node<OperatorNode>(); + op->op=OperatorNode::OP_INDEX_NAMED; + + IdentifierNode * id = alloc_node<IdentifierNode>(); + id->name=tokenizer.get_token_identifier(1); + + op->arguments.push_back(expr); + op->arguments.push_back(id); + + expr=op; + + tokenizer.advance(2); + } + + } 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); + 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 */ + /******************/ + + + 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 alowed 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_EXTENDS: op=OperatorNode::OP_EXTENDS; 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; + + for(int i=0;i<expression.size();i++) { + + + + if (!expression[i].is_op) { + + continue; + } + + int priority; + + bool unary=false; + + switch(expression[i].op) { + + case OperatorNode::OP_EXTENDS: 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_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; + + // ?: = 10 + + case OperatorNode::OP_ASSIGN: priority=14; break; + case OperatorNode::OP_ASSIGN_ADD: priority=14; break; + case OperatorNode::OP_ASSIGN_SUB: priority=14; break; + case OperatorNode::OP_ASSIGN_MUL: priority=14; break; + case OperatorNode::OP_ASSIGN_DIV: priority=14; break; + case OperatorNode::OP_ASSIGN_MOD: priority=14; break; + case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority=14; break; + case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority=14; break; + case OperatorNode::OP_ASSIGN_BIT_AND: priority=14; break; + case OperatorNode::OP_ASSIGN_BIT_OR: priority=14; break; + case OperatorNode::OP_ASSIGN_BIT_XOR: priority=14; break; + + + default: { + _set_error("GDParser bug, invalid operator in expression: "+itos(expression[i].op)); + return NULL; + } + + } + + if (priority<min_priority) { + // < is used for left to right (default) + // <= is used for right to left + next_op=i; + min_priority=priority; + is_unary=unary; + } + + } + + + 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); + expression[i].is_op=false; + expression[i].node=op; + expression.remove(i+1); + } + + + } 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; + + 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 dissapear first + + _set_error("Parser bug.."); + + } + + + 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(!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(!p_to_const); + 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_EXTENDS) { + //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; + } + + 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_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"); + 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; + } + + //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"); + 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");\ + 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");\ + 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_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); + if (!expr || error_set) + return NULL; + expr = _reduce_expression(expr,p_reduce_const); + if (!expr || error_set) + return NULL; + return expr; +} + +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 + + while(true) { + + 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 + } + + 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: { + + NewLineNode *nl = alloc_node<NewLineNode>(); + nl->line=tokenizer.get_token_line(); + p_block->statements.push_back(nl); + + if (!_parse_newline()) { + if (!error_set) { + p_block->end_line=tokenizer.get_token_line(); + } + return; + } + } break; + case GDTokenizer::TK_CF_PASS: { + if (tokenizer.get_token(1)!=GDTokenizer::TK_SEMICOLON && tokenizer.get_token(1)!=GDTokenizer::TK_NEWLINE ) { + + _set_error("Expected ';' or <NewLine>."); + return; + } + tokenizer.advance(); + } break; + case GDTokenizer::TK_PR_VAR: { + //variale declaration and (eventual) initialization + + tokenizer.advance(); + if (tokenizer.get_token()!=GDTokenizer::TK_IDENTIFIER) { + + _set_error("Expected identifier for local variable name."); + return; + } + StringName n = tokenizer.get_token_identifier(); + tokenizer.advance(); + + p_block->variables.push_back(n); //line? + p_block->variable_lines.push_back(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=NULL; + + subexpr = _parse_and_reduce_expression(p_block,p_static); + if (!subexpr) + return; + + lv->assign=subexpr; + assigned=subexpr; + } else { + + ConstantNode *c = alloc_node<ConstantNode>(); + c->value=Variant(); + assigned = c; + + } + 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); + + _end_statement(); + + + } break; + case GDTokenizer::TK_CF_IF: { + + tokenizer.advance(); + Node *condition = _parse_and_reduce_expression(p_block,p_static); + if (!condition) + 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>(); + p_block->sub_blocks.push_back(cf_if->body); + + if (!_enter_indent_block(cf_if->body)) { + p_block->end_line=tokenizer.get_token_line(); + return; + } + + _parse_block(cf_if->body,p_static); + if (error_set) + return; + p_block->statements.push_back(cf_if); + + while(true) { + + while(tokenizer.get_token()==GDTokenizer::TK_NEWLINE) { + tokenizer.advance(); + } + + 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>(); + 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) + 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>(); + p_block->sub_blocks.push_back(cf_if->body); + + + if (!_enter_indent_block(cf_if->body)) { + p_block->end_line=tokenizer.get_token_line(); + return; + } + + _parse_block(cf_else->body,p_static); + 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>(); + p_block->sub_blocks.push_back(cf_if->body_else); + + if (!_enter_indent_block(cf_if->body_else)) { + p_block->end_line=tokenizer.get_token_line(); + return; + } + _parse_block(cf_if->body_else,p_static); + 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) + 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>(); + p_block->sub_blocks.push_back(cf_while->body); + + if (!_enter_indent_block(cf_while->body)) { + p_block->end_line=tokenizer.get_token_line(); + return; + } + + _parse_block(cf_while->body,p_static); + if (error_set) + return; + p_block->statements.push_back(cf_while); + } break; + case GDTokenizer::TK_CF_FOR: { + + tokenizer.advance(); + + if (tokenizer.get_token()!=GDTokenizer::TK_IDENTIFIER) { + + _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) + return; + + 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>(); + p_block->sub_blocks.push_back(cf_for->body); + + if (!_enter_indent_block(cf_for->body)) { + p_block->end_line=tokenizer.get_token_line(); + return; + } + + _parse_block(cf_for->body,p_static); + 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) + 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_PR_ASSERT: { + + tokenizer.advance(); + Node *condition = _parse_and_reduce_expression(p_block,p_static); + if (!condition) + 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; + default: { + + Node *expression = _parse_and_reduce_expression(p_block,p_static,false,true); + if (!expression) + 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; + + //see if inheritance happens from a file + tokenizer.advance(); + + 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; + _end_statement(); + + + } 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->name=name; + + 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; + } + _parse_class(newclass); + + } 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; + + if (tokenizer.get_token(-1)==GDTokenizer::TK_PR_STATIC) { + + _static=true; + } + + + if (tokenizer.get_token(1)!=GDTokenizer::TK_IDENTIFIER) { + + _set_error("Expected identifier after 'func' (syntax: 'func <identifier>([arguments]):' )."); + return; + } + + StringName name = tokenizer.get_token_identifier(1); + + 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)+")."); + } + } + tokenizer.advance(2); + + 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_PR_VAR) { + + tokenizer.advance(); //var before the identifier is allowed + } + + + if (tokenizer.get_token()!=GDTokenizer::TK_IDENTIFIER) { + + _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=NULL; + + 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); + } + + 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>(); + + 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 + 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; + + } + } + + 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; + + + if (_static) + p_class->static_functions.push_back(function); + else + p_class->functions.push_back(function); + + + _parse_block(block,_static); + function->body=block; + //arguments + } 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; + tokenizer.advance(); + if (tokenizer.get_token()==GDTokenizer::TK_COMMA) { + // hint expected next! + tokenizer.advance(); + switch(current_export.type) { + + + case Variant::INT: { + + 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."); + } + + 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."); + } + + tokenizer.advance(); + + } + + break; + } + + }; + case Variant::REAL: { + + if (tokenizer.get_token()!=GDTokenizer::TK_CONSTANT || !tokenizer.get_token_constant().is_num()) { + + current_export=PropertyInfo(); + _set_error("Expected a range in numeric hint."); + + } + //enumeration + current_export.hint=PROPERTY_HINT_RANGE; + + current_export.hint_string=tokenizer.get_token_constant().operator String(); + 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."); + } + + 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."); + } + + current_export.hint_string+=","+tokenizer.get_token_constant().operator String(); + 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."); + } + + 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."); + } + + current_export.hint_string+=","+tokenizer.get_token_constant().operator String(); + 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."); + } + + 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") { + + current_export.hint=PROPERTY_HINT_DIR; + 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()=="FILE") { + + current_export.hint=PROPERTY_HINT_FILE; + tokenizer.advance(); + + if (tokenizer.get_token()==GDTokenizer::TK_COMMA) { + + tokenizer.advance(); + if (tokenizer.get_token()!=GDTokenizer::TK_CONSTANT || tokenizer.get_token_constant().get_type()!=Variant::STRING) { + + _set_error("Expected 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; + } + } 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; + } + + } + + } else if (tokenizer.get_token()==GDTokenizer::TK_IDENTIFIER) { + + String identifier = tokenizer.get_token_identifier(); + if (!ObjectTypeDB::is_type(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.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) { + + current_export=PropertyInfo(); + _set_error("Expected 'var'."); + return; + } + + }; //fallthrough to var + 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(); + } + + tokenizer.advance(); + if (tokenizer.get_token()!=GDTokenizer::TK_IDENTIFIER) { + + _set_error("Expected identifier for member variable name."); + return; + } + + member.identifier=tokenizer.get_token_identifier(); + member._export.name=member.identifier; + tokenizer.advance(); + + p_class->variables.push_back(member); + + if (tokenizer.get_token()!=GDTokenizer::TK_OP_ASSIGN) { + + if (autoexport) { + + _set_error("Type-less export needs a constant expression assigned to infer type."); + return; + } + break; + } +#ifdef DEBUG_ENABLED + int line = tokenizer.get_token_line(); +#endif + tokenizer.advance(); + + Node *subexpr=NULL; + + subexpr = _parse_and_reduce_expression(p_class,false); + if (!subexpr) + return; + + if (autoexport) { + if (subexpr->type==Node::TYPE_ARRAY) { + + p_class->variables[p_class->variables.size()-1]._export.type=Variant::ARRAY; + + } else if (subexpr->type==Node::TYPE_DICTIONARY) { + + p_class->variables[p_class->variables.size()-1]._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; + } + p_class->variables[p_class->variables.size()-1]._export.type=cn->value.get_type(); + } + } +#ifdef TOOLS_ENABLED + if (subexpr->type==Node::TYPE_CONSTANT && p_class->variables[p_class->variables.size()-1]._export.type!=Variant::NIL) { + + ConstantNode *cn = static_cast<ConstantNode*>(subexpr); + if (cn->value.get_type()!=Variant::NIL) { + p_class->variables[p_class->variables.size()-1].default_value=cn->value; + } + } +#endif + + + + IdentifierNode *id = alloc_node<IdentifierNode>(); + id->name=member.identifier; + + OperatorNode *op = alloc_node<OperatorNode>(); + op->op=OperatorNode::OP_ASSIGN; + op->arguments.push_back(id); + op->arguments.push_back(subexpr); + +#ifdef DEBUG_ENABLED + NewLineNode *nl = alloc_node<NewLineNode>(); + nl->line=line; + p_class->initializer->statements.push_back(nl); +#endif + p_class->initializer->statements.push_back(op); + + _end_statement(); + + } break; + case GDTokenizer::TK_PR_CONST: { + //variale declaration and (eventual) initialization + + ClassNode::Constant constant; + + tokenizer.advance(); + if (tokenizer.get_token()!=GDTokenizer::TK_IDENTIFIER) { + + _set_error("Expected name (identifier) for constant."); + return; + } + + constant.identifier=tokenizer.get_token_identifier(); + tokenizer.advance(); + + if (tokenizer.get_token()!=GDTokenizer::TK_OP_ASSIGN) { + _set_error("Constant expects assignment."); + return; + } + + tokenizer.advance(); + + Node *subexpr=NULL; + + subexpr = _parse_and_reduce_expression(p_class,true,true); + if (!subexpr) + return; + + if (subexpr->type!=Node::TYPE_CONSTANT) { + _set_error("Expected constant expression"); + } + constant.expression=subexpr; + + p_class->constant_expressions.push_back(constant); + + _end_statement(); + + + } 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_code,const String& p_base_path) { + + base_path=p_base_path; + + tokenizer.set_code(p_code); + + clear(); + + //assume class + ClassNode *main_class = alloc_node<ClassNode>(); + main_class->initializer = alloc_node<BlockNode>(); + + _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; +} + +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; + + error_set=false; + tab_level.clear(); + tab_level.push_back(0); + error_line=0; + error_column=0; + current_export.type=Variant::NIL; + error=""; + +} + +GDParser::GDParser() { + + head=NULL; + list=NULL; + clear(); + +} + +GDParser::~GDParser() { + + clear(); +} diff --git a/modules/gdscript/gd_parser.h b/modules/gdscript/gd_parser.h new file mode 100644 index 0000000000..8011495340 --- /dev/null +++ b/modules/gdscript/gd_parser.h @@ -0,0 +1,397 @@ +/*************************************************************************/ +/* gd_parser.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +#ifndef GD_PARSER_H +#define GD_PARSER_H + +#include "gd_tokenizer.h" +#include "gd_functions.h" +#include "map.h" +#include "object.h" + +class GDParser { +public: + + struct Node { + + enum Type { + TYPE_CLASS, + TYPE_FUNCTION, + TYPE_BUILT_IN_FUNCTION, + TYPE_BLOCK, + TYPE_IDENTIFIER, + TYPE_TYPE, + TYPE_CONSTANT, + TYPE_ARRAY, + TYPE_DICTIONARY, + TYPE_SELF, + TYPE_OPERATOR, + TYPE_CONTROL_FLOW, + TYPE_LOCAL_VAR, + TYPE_ASSERT, + TYPE_NEWLINE, + }; + + Node * next; + int line; + int column; + Type type; + + virtual ~Node() {} + }; + + struct FunctionNode; + struct BlockNode; + + struct ClassNode : public Node { + + bool tool; + StringName name; + bool extends_used; + StringName extends_file; + Vector<StringName> extends_class; + + struct Member { + PropertyInfo _export; +#ifdef TOOLS_ENABLED + Variant default_value; +#endif + StringName identifier; + }; + struct Constant { + StringName identifier; + Node *expression; + }; + + Vector<ClassNode*> subclasses; + Vector<Member> variables; + Vector<Constant> constant_expressions; + Vector<FunctionNode*> functions; + Vector<FunctionNode*> static_functions; + BlockNode *initializer; + //Vector<Node*> initializers; + int end_line; + + ClassNode() { tool=false; type=TYPE_CLASS; extends_used=false; end_line=-1;} + }; + + + + struct FunctionNode : public Node { + + bool _static; + StringName name; + Vector<StringName> arguments; + Vector<Node*> default_values; + BlockNode *body; + + FunctionNode() { type=TYPE_FUNCTION; _static=false; } + + }; + + struct BlockNode : public Node { + + Map<StringName,int> locals; + List<Node*> statements; + Vector<StringName> variables; + Vector<int> variable_lines; + + //the following is useful for code completion + List<BlockNode*> sub_blocks; + int end_line; + BlockNode() { type=TYPE_BLOCK; end_line=-1;} + }; + + struct TypeNode : public Node { + + Variant::Type vtype; + TypeNode() { type=TYPE_TYPE; } + }; + struct BuiltInFunctionNode : public Node { + GDFunctions::Function function; + BuiltInFunctionNode() { type=TYPE_BUILT_IN_FUNCTION; } + }; + + struct IdentifierNode : public Node { + + StringName name; + IdentifierNode() { type=TYPE_IDENTIFIER; } + }; + + struct LocalVarNode : public Node { + + StringName name; + Node *assign; + LocalVarNode() { type=TYPE_LOCAL_VAR; assign=NULL;} + }; + + struct ConstantNode : public Node { + Variant value; + ConstantNode() { type=TYPE_CONSTANT; } + }; + + struct ArrayNode : public Node { + + Vector<Node*> elements; + ArrayNode() { type=TYPE_ARRAY; } + }; + + + struct DictionaryNode : public Node { + + struct Pair { + + Node *key; + Node *value; + }; + + Vector<Pair> elements; + DictionaryNode() { type=TYPE_DICTIONARY; } + }; + + struct SelfNode : public Node { + SelfNode() { type=TYPE_SELF; } + }; + + struct OperatorNode : public Node { + enum Operator { + //call/constructor operator + OP_CALL, + OP_PARENT_CALL, + OP_EXTENDS, + //indexing operator + OP_INDEX, + OP_INDEX_NAMED, + //unary operators + OP_NEG, + OP_NOT, + OP_BIT_INVERT, + OP_PREINC, + OP_PREDEC, + OP_INC, + OP_DEC, + //binary operators (in precedence order) + OP_IN, + OP_EQUAL, + OP_NOT_EQUAL, + OP_LESS, + OP_LESS_EQUAL, + OP_GREATER, + OP_GREATER_EQUAL, + OP_AND, + OP_OR, + OP_ADD, + OP_SUB, + OP_MUL, + OP_DIV, + OP_MOD, + OP_SHIFT_LEFT, + OP_SHIFT_RIGHT, + OP_ASSIGN, + OP_ASSIGN_ADD, + OP_ASSIGN_SUB, + OP_ASSIGN_MUL, + OP_ASSIGN_DIV, + OP_ASSIGN_MOD, + OP_ASSIGN_SHIFT_LEFT, + OP_ASSIGN_SHIFT_RIGHT, + OP_ASSIGN_BIT_AND, + OP_ASSIGN_BIT_OR, + OP_ASSIGN_BIT_XOR, + OP_BIT_AND, + OP_BIT_OR, + OP_BIT_XOR + }; + + Operator op; + + Vector<Node*> arguments; + OperatorNode() { type=TYPE_OPERATOR; } + }; + + struct ControlFlowNode : public Node { + enum CFType { + CF_IF, + CF_FOR, + CF_WHILE, + CF_SWITCH, + CF_BREAK, + CF_CONTINUE, + CF_RETURN + }; + + CFType cf_type; + Vector<Node*> arguments; + BlockNode *body; + BlockNode *body_else; + + ControlFlowNode *_else; //used for if + ControlFlowNode() { type=TYPE_CONTROL_FLOW; cf_type=CF_IF; body=NULL; body_else=NULL;} + }; + + struct AssertNode : public Node { + Node* condition; + AssertNode() { type=TYPE_ASSERT; } + }; + struct NewLineNode : public Node { + int line; + NewLineNode() { type=TYPE_NEWLINE; } + }; + + + struct Expression { + + bool is_op; + union { + OperatorNode::Operator op; + Node *node; + }; + }; + + +/* + struct OperatorNode : public Node { + + DataType return_cache; + Operator op; + Vector<Node*> arguments; + virtual DataType get_datatype() const { return return_cache; } + + OperatorNode() { type=TYPE_OPERATOR; return_cache=TYPE_VOID; } + }; + + struct VariableNode : public Node { + + DataType datatype_cache; + StringName name; + virtual DataType get_datatype() const { return datatype_cache; } + + VariableNode() { type=TYPE_VARIABLE; datatype_cache=TYPE_VOID; } + }; + + struct ConstantNode : public Node { + + DataType datatype; + Variant value; + virtual DataType get_datatype() const { return datatype; } + + ConstantNode() { type=TYPE_CONSTANT; } + }; + + struct BlockNode : public Node { + + Map<StringName,DataType> variables; + List<Node*> statements; + BlockNode() { type=TYPE_BLOCK; } + }; + + struct ControlFlowNode : public Node { + + FlowOperation flow_op; + Vector<Node*> statements; + ControlFlowNode() { type=TYPE_CONTROL_FLOW; flow_op=FLOW_OP_IF;} + }; + + struct MemberNode : public Node { + + DataType datatype; + StringName name; + Node* owner; + virtual DataType get_datatype() const { return datatype; } + MemberNode() { type=TYPE_MEMBER; } + }; + + + struct ProgramNode : public Node { + + struct Function { + StringName name; + FunctionNode*function; + }; + + Map<StringName,DataType> builtin_variables; + Map<StringName,DataType> preexisting_variables; + + Vector<Function> functions; + BlockNode *body; + + ProgramNode() { type=TYPE_PROGRAM; } + }; +*/ +private: + + + GDTokenizer tokenizer; + + + Node *head; + Node *list; + template<class T> + T* alloc_node(); + + bool error_set; + String error; + int error_line; + int error_column; + + List<int> tab_level; + + String base_path; + + PropertyInfo current_export; + + void _set_error(const String& p_error, int p_line=-1, int p_column=-1); + + + bool _parse_arguments(Node* p_parent,Vector<Node*>& p_args,bool p_static); + bool _enter_indent_block(BlockNode *p_block=NULL); + bool _parse_newline(); + Node* _parse_expression(Node *p_parent,bool p_static,bool p_allow_assign=false); + Node* _reduce_expression(Node *p_node,bool p_to_const=false); + Node* _parse_and_reduce_expression(Node *p_parent,bool p_static,bool p_reduce_const=false,bool p_allow_assign=false); + + void _parse_block(BlockNode *p_block,bool p_static); + void _parse_extends(ClassNode *p_class); + void _parse_class(ClassNode *p_class); + bool _end_statement(); + +public: + + String get_error() const; + int get_error_line() const; + int get_error_column() const; + Error parse(const String& p_code,const String& p_base_path=""); + + const Node *get_parse_tree() const; + + void clear(); + GDParser(); + ~GDParser(); +}; + +#endif // PARSER_H diff --git a/modules/gdscript/gd_pretty_print.cpp b/modules/gdscript/gd_pretty_print.cpp new file mode 100644 index 0000000000..a5a993bb3a --- /dev/null +++ b/modules/gdscript/gd_pretty_print.cpp @@ -0,0 +1,34 @@ +/*************************************************************************/ +/* gd_pretty_print.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_pretty_print.h" + +GDPrettyPrint::GDPrettyPrint() { + + +} diff --git a/modules/gdscript/gd_pretty_print.h b/modules/gdscript/gd_pretty_print.h new file mode 100644 index 0000000000..fbf002295b --- /dev/null +++ b/modules/gdscript/gd_pretty_print.h @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* gd_pretty_print.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +#ifndef GD_PRETTY_PRINT_H +#define GD_PRETTY_PRINT_H + + + + +class GDPrettyPrint { +public: + GDPrettyPrint(); +}; + +#endif // GD_PRETTY_PRINT_H diff --git a/modules/gdscript/gd_script.cpp b/modules/gdscript/gd_script.cpp new file mode 100644 index 0000000000..5679e1e066 --- /dev/null +++ b/modules/gdscript/gd_script.cpp @@ -0,0 +1,2222 @@ +/*************************************************************************/ +/* gd_script.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_script.h" +#include "globals.h" +#include "global_constants.h" +#include "gd_compiler.h" +#include "os/file_access.h" + +/* TODO: + + *populate globals + *do checks as close to debugger as possible (but don't do debugger) + *const check plz + *check arguments and default arguments in GDFunction + -get property list in instance? + *missing opcodes + -const checks + -make thread safe + */ + + + +Variant *GDFunction::_get_variant(int p_address,GDInstance *p_instance,GDScript *p_script,Variant &self, Variant *p_stack,String& r_error) const{ + + int address = p_address&ADDR_MASK; + + //sequential table (jump table generated by compiler) + switch((p_address&ADDR_TYPE_MASK)>>ADDR_BITS) { + + case ADDR_TYPE_SELF: { + + if (!p_instance) { + r_error="Cannot access self without instance."; + return NULL; + } + return &self; + } break; + case ADDR_TYPE_MEMBER: { + //member indexing is O(1) + if (!p_instance) { + r_error="Cannot access member without instance."; + return NULL; + } + return &p_instance->members[address]; + } break; + case ADDR_TYPE_CLASS_CONSTANT: { + + //todo change to index! + GDScript *s=p_script; + ERR_FAIL_INDEX_V(address,_global_names_count,NULL); + const StringName *sn = &_global_names_ptr[address]; + + while(s) { + Map<StringName,Variant>::Element *E=s->constants.find(*sn); + if (E) { + return &E->get(); + } + s=s->_base; + } + + + ERR_EXPLAIN("GDCompiler bug.."); + ERR_FAIL_V(NULL); + } break; + case ADDR_TYPE_LOCAL_CONSTANT: { + ERR_FAIL_INDEX_V(address,_constant_count,NULL); + return &_constants_ptr[address]; + } break; + case ADDR_TYPE_STACK: + case ADDR_TYPE_STACK_VARIABLE: { + ERR_FAIL_INDEX_V(address,_stack_size,NULL); + return &p_stack[address]; + } break; + case ADDR_TYPE_GLOBAL: { + + + ERR_FAIL_INDEX_V(address,GDScriptLanguage::get_singleton()->get_global_array_size(),NULL); + + + return &GDScriptLanguage::get_singleton()->get_global_array()[address]; + } break; + case ADDR_TYPE_NIL: { + return &nil; + } break; + } + + ERR_EXPLAIN("Bad Code! (Addressing Mode)"); + ERR_FAIL_V(NULL); + return NULL; +} + + +String GDFunction::_get_call_error(const Variant::CallError& p_err, const String& p_where,const Variant**argptrs) const { + + + + String err_text; + + if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) { + int errorarg=p_err.argument; + err_text="Invalid type in "+p_where+". Cannot convert argument "+itos(errorarg+1)+" from "+Variant::get_type_name(argptrs[errorarg]->get_type())+" to "+Variant::get_type_name(p_err.expected)+"."; + } else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { + err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments."; + } else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { + err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments."; + } else if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) { + err_text="Invalid call. Unexisting "+p_where+"."; + } else if (p_err.error==Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) { + err_text="Attempt to call "+p_where+" on a null instance."; + } else { + err_text="Bug, call error: #"+itos(p_err.error); + } + + return err_text; + +} + +static String _get_var_type(const Variant* p_type) { + + String basestr; + + if (p_type->get_type()==Variant::OBJECT) { + Object *bobj = *p_type; + if (!bobj) { + basestr = "null instance"; + } else { +#ifdef DEBUG_ENABLED + if (ObjectDB::instance_validate(bobj)) { + if (bobj->get_script_instance()) + basestr= bobj->get_type()+" ("+bobj->get_script_instance()->get_script()->get_path().get_file()+")"; + else + basestr = bobj->get_type(); + } else { + basestr="previously freed instance"; + } + +#else + basestr="Object"; +#endif + } + + } else { + basestr = Variant::get_type_name(p_type->get_type()); + } + + return basestr; + +} + +Variant GDFunction::call(GDInstance *p_instance,const Variant **p_args, int p_argcount,Variant::CallError& r_err) { + + + if (!_code_ptr) { + + return Variant(); + } + + r_err.error=Variant::CallError::CALL_OK; + + Variant self; + Variant retvalue; + Variant *stack = NULL; + Variant **call_args; + int defarg=0; + +#ifdef DEBUG_ENABLED + + //GDScriptLanguage::get_singleton()->calls++; + +#endif + + if (p_argcount!=_argument_count) { + + if (p_argcount>_argument_count) { + + r_err.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_err.argument=_argument_count; + + return Variant(); + } else if (p_argcount < _argument_count - _default_arg_count) { + + r_err.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_err.argument=_argument_count - _default_arg_count; + return Variant(); + } else { + + defarg=_argument_count-p_argcount; + } + } + + uint32_t alloca_size = sizeof(Variant*)*_call_size + sizeof(Variant)*_stack_size; + + if (alloca_size) { + + uint8_t *aptr = (uint8_t*)alloca(alloca_size); + + if (_stack_size) { + + stack=(Variant*)aptr; + for(int i=0;i<p_argcount;i++) + memnew_placement(&stack[i],Variant(*p_args[i])); + for(int i=p_argcount;i<_stack_size;i++) + memnew_placement(&stack[i],Variant); + } else { + stack=NULL; + } + + if (_call_size) { + + call_args = (Variant**)&aptr[sizeof(Variant)*_stack_size]; + } else { + + call_args=NULL; + } + + + } else { + stack=NULL; + call_args=NULL; + } + + + GDScript *_class; + + if (p_instance) { + if (p_instance->base_ref && static_cast<Reference*>(p_instance->owner)->is_referenced()) { + + self=REF(static_cast<Reference*>(p_instance->owner)); + } else { + self=p_instance->owner; + } + _class=p_instance->script.ptr(); + } else { + _class=_script; + } + + int ip=0; + int line=_initial_line; + String err_text; + + + +#ifdef DEBUG_ENABLED + + if (ScriptDebugger::get_singleton()) + GDScriptLanguage::get_singleton()->enter_function(p_instance,this,stack,&ip,&line); + +#define CHECK_SPACE(m_space)\ + ERR_BREAK((ip+m_space)>_code_size) + +#define GET_VARIANT_PTR(m_v,m_code_ofs) \ + Variant *m_v; \ + m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text);\ + if (!m_v)\ + break; + + +#else +#define CHECK_SPACE(m_space) +#define GET_VARIANT_PTR(m_v,m_code_ofs) \ + Variant *m_v; \ + m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text); + +#endif + + + + bool exit_ok=false; + + while(ip<_code_size) { + + + int last_opcode=_code_ptr[ip]; + switch(_code_ptr[ip]) { + + case OPCODE_OPERATOR: { + + CHECK_SPACE(5); + + bool valid; + Variant::Operator op = (Variant::Operator)_code_ptr[ip+1]; + ERR_BREAK(op>=Variant::OP_MAX); + + GET_VARIANT_PTR(a,2); + GET_VARIANT_PTR(b,3); + GET_VARIANT_PTR(dst,4); + + Variant::evaluate(op,*a,*b,*dst,valid); + if (!valid) { + if (false && dst->get_type()==Variant::STRING) { + //return a string when invalid with the error + err_text=*dst; + } else { + err_text="Invalid operands '"+Variant::get_type_name(a->get_type())+"' and '"+Variant::get_type_name(b->get_type())+"' in operator '"+Variant::get_operator_name(op)+"'."; + } + break; + } + + ip+=5; + + } continue; + case OPCODE_EXTENDS_TEST: { + + CHECK_SPACE(4); + + GET_VARIANT_PTR(a,1); + GET_VARIANT_PTR(b,2); + GET_VARIANT_PTR(dst,3); + +#ifdef DEBUG_ENABLED + + if (a->get_type()!=Variant::OBJECT || a->operator Object*()==NULL) { + + err_text="Left operand of 'extends' is not an instance of anything."; + break; + + } + if (b->get_type()!=Variant::OBJECT || b->operator Object*()==NULL) { + + err_text="Right operand of 'extends' is not a class."; + break; + + } +#endif + + + Object *obj_A = *a; + Object *obj_B = *b; + + + GDScript *scr_B = obj_B->cast_to<GDScript>(); + + bool extends_ok=false; + + if (scr_B) { + //if B is a script, the only valid condition is that A has an instance which inherits from the script + //in other situation, this shoul return false. + + if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language()==GDScriptLanguage::get_singleton()) { + + GDInstance *ins = static_cast<GDInstance*>(obj_A->get_script_instance()); + GDScript *cmp = ins->script.ptr(); + //bool found=false; + while(cmp) { + + if (cmp==scr_B) { + //inherits from script, all ok + extends_ok=true; + break; + + } + + cmp=cmp->_base; + } + + } + } else { + + GDNativeClass *nc= obj_B->cast_to<GDNativeClass>(); + + if (!nc) { + + err_text="Right operand of 'extends' is not a class (type: '"+obj_B->get_type()+"')."; + break; + } + + extends_ok=ObjectTypeDB::is_type(obj_A->get_type_name(),nc->get_name()); + } + + *dst=extends_ok; + ip+=4; + + } continue; + case OPCODE_SET: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(dst,1); + GET_VARIANT_PTR(index,2); + GET_VARIANT_PTR(value,3); + + bool valid; + dst->set(*index,*value,&valid); + + if (!valid) { + String v = index->operator String(); + if (v!="") { + v="'"+v+"'"; + } else { + v="of type '"+_get_var_type(index)+"'"; + } + err_text="Invalid set index "+v+" (on base: '"+_get_var_type(dst)+"')."; + break; + } + + ip+=4; + } continue; + case OPCODE_GET: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(src,1); + GET_VARIANT_PTR(index,2); + GET_VARIANT_PTR(dst,3); + + bool valid; + *dst = src->get(*index,&valid); + + if (!valid) { + String v = index->operator String(); + if (v!="") { + v="'"+v+"'"; + } else { + v="of type '"+_get_var_type(index)+"'"; + } + err_text="Invalid get index "+v+" (on base: '"+_get_var_type(src)+"')."; + break; + } + ip+=4; + } continue; + case OPCODE_SET_NAMED: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(dst,1); + GET_VARIANT_PTR(value,3); + + int indexname = _code_ptr[ip+2]; + + ERR_BREAK(indexname<0 || indexname>=_global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; + dst->set_named(*index,*value,&valid); + + if (!valid) { + String err_type; + err_text="Invalid set index '"+String(*index)+"' (on base: '"+_get_var_type(dst)+"')."; + break; + } + + ip+=4; + } continue; + case OPCODE_GET_NAMED: { + + + CHECK_SPACE(3); + + GET_VARIANT_PTR(src,1); + GET_VARIANT_PTR(dst,3); + + int indexname = _code_ptr[ip+2]; + + ERR_BREAK(indexname<0 || indexname>=_global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; + *dst = src->get_named(*index,&valid); + + if (!valid) { + err_text="Invalid get index '"+index->operator String()+"' (on base: '"+_get_var_type(src)+"')."; + break; + } + + ip+=4; + } continue; + case OPCODE_ASSIGN: { + + CHECK_SPACE(3); + GET_VARIANT_PTR(dst,1); + GET_VARIANT_PTR(src,2); + + *dst = *src; + + ip+=3; + + } continue; + case OPCODE_ASSIGN_TRUE: { + + CHECK_SPACE(2); + GET_VARIANT_PTR(dst,1); + + *dst = true; + + ip+=2; + } continue; + case OPCODE_ASSIGN_FALSE: { + + CHECK_SPACE(2); + GET_VARIANT_PTR(dst,1); + + *dst = false; + + ip+=2; + } continue; + case OPCODE_CONSTRUCT: { + + CHECK_SPACE(2); + Variant::Type t=Variant::Type(_code_ptr[ip+1]); + int argc=_code_ptr[ip+2]; + CHECK_SPACE(argc+2); + Variant **argptrs = call_args; + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,3+i); + argptrs[i]=v; + } + + GET_VARIANT_PTR(dst,3+argc); + Variant::CallError err; + *dst = Variant::construct(t,(const Variant**)argptrs,argc,err); + + if (err.error!=Variant::CallError::CALL_OK) { + + err_text=_get_call_error(err,"'"+Variant::get_type_name(t)+"' constructor",(const Variant**)argptrs); + break; + } + + ip+=4+argc; + //construct a basic type + } continue; + case OPCODE_CONSTRUCT_ARRAY: { + + CHECK_SPACE(1); + int argc=_code_ptr[ip+1]; + Array array(true); //arrays are always shared + array.resize(argc); + CHECK_SPACE(argc+2); + + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,2+i); + array[i]=*v; + + } + + GET_VARIANT_PTR(dst,2+argc); + + *dst=array; + + ip+=3+argc; + + } continue; + case OPCODE_CONSTRUCT_DICTIONARY: { + + CHECK_SPACE(1); + int argc=_code_ptr[ip+1]; + Dictionary dict(true); //arrays are always shared + + CHECK_SPACE(argc*2+2); + + for(int i=0;i<argc;i++) { + + GET_VARIANT_PTR(k,2+i*2+0); + GET_VARIANT_PTR(v,2+i*2+1); + dict[*k]=*v; + + } + + GET_VARIANT_PTR(dst,2+argc*2); + + *dst=dict; + + ip+=3+argc*2; + + } continue; + case OPCODE_CALL_RETURN: + case OPCODE_CALL: { + + + CHECK_SPACE(4); + bool call_ret = _code_ptr[ip]==OPCODE_CALL_RETURN; + + int argc=_code_ptr[ip+1]; + GET_VARIANT_PTR(base,2); + int nameg=_code_ptr[ip+3]; + + ERR_BREAK(nameg<0 || nameg>=_global_names_count); + const StringName *methodname = &_global_names_ptr[nameg]; + + ERR_BREAK(argc<0); + ip+=4; + CHECK_SPACE(argc+1); + Variant **argptrs = call_args; + + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,i); + argptrs[i]=v; + } + + Variant::CallError err; + if (call_ret) { + + GET_VARIANT_PTR(ret,argc); + *ret = base->call(*methodname,(const Variant**)argptrs,argc,err); + } else { + + base->call(*methodname,(const Variant**)argptrs,argc,err); + } + + if (err.error!=Variant::CallError::CALL_OK) { + + + String methodstr = *methodname; + String basestr = _get_var_type(base); + + if (methodstr=="call") { + if (argc>=1) { + methodstr=String(*argptrs[0])+" (via call)"; + if (err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) { + err.argument-=1; + } + } + } + err_text=_get_call_error(err,"function '"+methodstr+"' in base '"+basestr+"'",(const Variant**)argptrs); + break; + } + + //_call_func(NULL,base,*methodname,ip,argc,p_instance,stack); + ip+=argc+1; + + } continue; + case OPCODE_CALL_BUILT_IN: { + + CHECK_SPACE(4); + + GDFunctions::Function func = GDFunctions::Function(_code_ptr[ip+1]); + int argc=_code_ptr[ip+2]; + ERR_BREAK(argc<0); + + ip+=3; + CHECK_SPACE(argc+1); + Variant **argptrs = call_args; + + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,i); + argptrs[i]=v; + } + + GET_VARIANT_PTR(dst,argc); + + Variant::CallError err; + + GDFunctions::call(func,(const Variant**)argptrs,argc,*dst,err); + + if (err.error!=Variant::CallError::CALL_OK) { + + + String methodstr = GDFunctions::get_func_name(func); + err_text=_get_call_error(err,"built-in function '"+methodstr+"'",(const Variant**)argptrs); + break; + } + ip+=argc+1; + + } continue; + case OPCODE_CALL_SELF: { + + + } break; + case OPCODE_CALL_SELF_BASE: { + + CHECK_SPACE(2); + int self_fun = _code_ptr[ip+1]; +#ifdef DEBUG_ENABLED + + if (self_fun<0 || self_fun>=_global_names_count) { + + err_text="compiler bug, function name not found"; + break; + } +#endif + const StringName *methodname = &_global_names_ptr[self_fun]; + + int argc=_code_ptr[ip+2]; + + CHECK_SPACE(2+argc+1); + + Variant **argptrs = call_args; + + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,i+3); + argptrs[i]=v; + } + + GET_VARIANT_PTR(dst,argc+3); + + const GDScript *gds = _script; + + + const Map<StringName,GDFunction>::Element *E=NULL; + while (gds->base.ptr()) { + gds=gds->base.ptr(); + E=gds->member_functions.find(*methodname); + if (E) + break; + } + + Variant::CallError err; + + if (E) { + + *dst=((GDFunction*)&E->get())->call(p_instance,(const Variant**)argptrs,argc,err); + } else if (gds->native.ptr()) { + + if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) { + + MethodBind *mb = ObjectTypeDB::get_method(gds->native->get_name(),*methodname); + if (!mb) { + err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } else { + *dst=mb->call(p_instance->owner,(const Variant**)argptrs,argc,err); + } + } else { + err.error=Variant::CallError::CALL_OK; + } + } else { + + if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) { + err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } else { + err.error=Variant::CallError::CALL_OK; + } + } + + + if (err.error!=Variant::CallError::CALL_OK) { + + + String methodstr = *methodname; + err_text=_get_call_error(err,"function '"+methodstr+"'",(const Variant**)argptrs); + + break; + } + + ip+=4+argc; + + } continue; + case OPCODE_JUMP: { + + CHECK_SPACE(2); + int to = _code_ptr[ip+1]; + + ERR_BREAK(to<0 || to>_code_size); + ip=to; + + } continue; + case OPCODE_JUMP_IF: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(test,1); + + bool valid; + bool result = test->booleanize(valid); +#ifdef DEBUG_ENABLED + if (!valid) { + + err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type()); + break; + } +#endif + if (result) { + int to = _code_ptr[ip+2]; + ERR_BREAK(to<0 || to>_code_size); + ip=to; + continue; + } + ip+=3; + } continue; + case OPCODE_JUMP_IF_NOT: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(test,1); + + bool valid; + bool result = test->booleanize(valid); +#ifdef DEBUG_ENABLED + if (!valid) { + + err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type()); + break; + } +#endif + if (!result) { + int to = _code_ptr[ip+2]; + ERR_BREAK(to<0 || to>_code_size); + ip=to; + continue; + } + ip+=3; + } continue; + case OPCODE_JUMP_TO_DEF_ARGUMENT: { + + CHECK_SPACE(2); + ip=_default_arg_ptr[defarg]; + + } continue; + case OPCODE_RETURN: { + + CHECK_SPACE(2); + GET_VARIANT_PTR(r,1); + retvalue=*r; + exit_ok=true; + + } break; + case OPCODE_ITERATE_BEGIN: { + + CHECK_SPACE(8); //space for this an regular iterate + + GET_VARIANT_PTR(counter,1); + GET_VARIANT_PTR(container,2); + + bool valid; + if (!container->iter_init(*counter,valid)) { + if (!valid) { + err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"'."; + break; + } + int jumpto=_code_ptr[ip+3]; + ERR_BREAK(jumpto<0 || jumpto>_code_size); + ip=jumpto; + continue; + } + GET_VARIANT_PTR(iterator,4); + + + *iterator=container->iter_get(*counter,valid); + if (!valid) { + err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"'."; + break; + } + + + ip+=5; //skip regular iterate which is always next + + } continue; + case OPCODE_ITERATE: { + + CHECK_SPACE(4); + + GET_VARIANT_PTR(counter,1); + GET_VARIANT_PTR(container,2); + + bool valid; + if (!container->iter_next(*counter,valid)) { + if (!valid) { + err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"' (type changed since first iteration?)."; + break; + } + int jumpto=_code_ptr[ip+3]; + ERR_BREAK(jumpto<0 || jumpto>_code_size); + ip=jumpto; + continue; + } + GET_VARIANT_PTR(iterator,4); + + *iterator=container->iter_get(*counter,valid); + if (!valid) { + err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"' (but was obtained on first iteration?)."; + break; + } + + ip+=5; //loop again + } continue; + case OPCODE_ASSERT: { + CHECK_SPACE(2); + GET_VARIANT_PTR(test,1); + +#ifdef DEBUG_ENABLED + bool valid; + bool result = test->booleanize(valid); + + + if (!valid) { + + err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type()); + break; + } + + + if (!result) { + + err_text="Assertion failed."; + break; + } + +#endif + + ip+=2; + } continue; + case OPCODE_LINE: { + CHECK_SPACE(2); + + line=_code_ptr[ip+1]; + ip+=2; + + if (ScriptDebugger::get_singleton()) { + // line + bool do_break=false; + + if (ScriptDebugger::get_singleton()->get_lines_left()>0) { + + if (ScriptDebugger::get_singleton()->get_depth()<=0) + ScriptDebugger::get_singleton()->set_lines_left( ScriptDebugger::get_singleton()->get_lines_left() -1 ); + if (ScriptDebugger::get_singleton()->get_lines_left()<=0) + do_break=true; + } + + if (ScriptDebugger::get_singleton()->is_breakpoint(line,source)) + do_break=true; + + if (do_break) { + GDScriptLanguage::get_singleton()->debug_break("Breakpoint",true); + } + + ScriptDebugger::get_singleton()->line_poll(); + + } + } continue; + case OPCODE_END: { + + exit_ok=true; + break; + + } break; + default: { + + err_text="Illegal opcode "+itos(_code_ptr[ip])+" at address "+itos(ip); + } break; + + } + + if (exit_ok) + break; + //error + // function, file, line, error, explanation + String err_file; + if (p_instance) + err_file=p_instance->script->path; + else if (_class) + err_file=_class->path; + if (err_file=="") + err_file="<built-in>"; + String err_func = name; + if (p_instance && p_instance->script->name!="") + err_func=p_instance->script->name+"."+err_func; + int err_line=line; + if (err_text=="") { + err_text="Internal Script Error! - opcode #"+itos(last_opcode)+" (report please)."; + } + + if (!GDScriptLanguage::get_singleton()->debug_break(err_text,false)) { + // debugger break did not happen + + _err_print_error(err_func.utf8().get_data(),err_file.utf8().get_data(),err_line,err_text.utf8().get_data()); + } + + + break; + } + + if (ScriptDebugger::get_singleton()) + GDScriptLanguage::get_singleton()->exit_function(); + + + if (_stack_size) { + //free stack + for(int i=0;i<_stack_size;i++) + stack[i].~Variant(); + } + + return retvalue; + +} + +const int* GDFunction::get_code() const { + + return _code_ptr; +} +int GDFunction::get_code_size() const{ + + return _code_size; +} + +Variant GDFunction::get_constant(int p_idx) const { + + ERR_FAIL_INDEX_V(p_idx,constants.size(),"<errconst>"); + return constants[p_idx]; +} + +StringName GDFunction::get_global_name(int p_idx) const { + + ERR_FAIL_INDEX_V(p_idx,global_names.size(),"<errgname>"); + return global_names[p_idx]; +} + +int GDFunction::get_default_argument_count() const { + + return default_arguments.size(); +} +int GDFunction::get_default_argument_addr(int p_arg) const{ + + ERR_FAIL_INDEX_V(p_arg,default_arguments.size(),-1); + return default_arguments[p_arg]; +} + + +StringName GDFunction::get_name() const { + + return name; +} + +int GDFunction::get_max_stack_size() const { + + return _stack_size; +} + +struct _GDFKC { + + int order; + List<int> pos; +}; + +struct _GDFKCS { + + int order; + StringName id; + int pos; + + bool operator<(const _GDFKCS &p_r) const { + + return order<p_r.order; + } +}; + +void GDFunction::debug_get_stack_member_state(int p_line,List<Pair<StringName,int> > *r_stackvars) const { + + + int oc=0; + Map<StringName,_GDFKC> sdmap; + for( const List<StackDebug>::Element *E=stack_debug.front();E;E=E->next()) { + + const StackDebug &sd=E->get(); + if (sd.line>p_line) + break; + + if (sd.added) { + + if (!sdmap.has(sd.identifier)) { + _GDFKC d; + d.order=oc++; + d.pos.push_back(sd.pos); + sdmap[sd.identifier]=d; + + } else { + sdmap[sd.identifier].pos.push_back(sd.pos); + } + } else { + + + ERR_CONTINUE(!sdmap.has(sd.identifier)); + + sdmap[sd.identifier].pos.pop_back(); + if (sdmap[sd.identifier].pos.empty()) + sdmap.erase(sd.identifier); + } + + } + + + List<_GDFKCS> stackpositions; + for(Map<StringName,_GDFKC>::Element *E=sdmap.front();E;E=E->next() ) { + + _GDFKCS spp; + spp.id=E->key(); + spp.order=E->get().order; + spp.pos=E->get().pos.back()->get(); + stackpositions.push_back(spp); + } + + stackpositions.sort(); + + for(List<_GDFKCS>::Element *E=stackpositions.front();E;E=E->next()) { + + Pair<StringName,int> p; + p.first=E->get().id; + p.second=E->get().pos; + r_stackvars->push_back(p); + } + + +} + +#if 0 +void GDFunction::clear() { + + name=StringName(); + constants.clear(); + _stack_size=0; + code.clear(); + _constants_ptr=NULL; + _constant_count=0; + _global_names_ptr=NULL; + _global_names_count=0; + _code_ptr=NULL; + _code_size=0; + +} +#endif +GDFunction::GDFunction() { + + _stack_size=0; + _call_size=0; + name="<anonymous>"; + +} + +GDNativeClass::GDNativeClass(const StringName& p_name) { + + name=p_name; +} + +/*void GDNativeClass::call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount){ + + +}*/ + + +bool GDNativeClass::_get(const StringName& p_name,Variant &r_ret) const { + + bool ok; + int v = ObjectTypeDB::get_integer_constant(name, p_name, &ok); + + if (ok) { + r_ret=v; + return true; + } else { + return false; + } +} + + +void GDNativeClass::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("new"),&GDNativeClass::_new); + +} + +Variant GDNativeClass::_new() { + + Object *o = instance(); + if (!o) { + ERR_EXPLAIN("Class type: '"+String(name)+"' is not instantiable."); + ERR_FAIL_COND_V(!o,Variant()); + } + + Reference *ref = o->cast_to<Reference>(); + if (ref) { + return REF(ref); + } else { + return o; + } + +} + +Object *GDNativeClass::instance() { + + return ObjectTypeDB::instance(name); +} + + + +GDInstance* GDScript::_create_instance(const Variant** p_args,int p_argcount,Object *p_owner,bool p_isref) { + + + /* STEP 1, CREATE */ + + GDInstance* instance = memnew( GDInstance ); + instance->base_ref=p_isref; + instance->members.resize(member_indices.size()); + instance->script=Ref<GDScript>(this); + instance->owner=p_owner; + instance->owner->set_script_instance(instance); + + /* STEP 2, INITIALIZE AND CONSRTUCT */ + + instances.insert(instance->owner); + + Variant::CallError err; + initializer->call(instance,p_args,p_argcount,err); + + if (err.error!=Variant::CallError::CALL_OK) { + instance->script=Ref<GDScript>(); + instances.erase(p_owner); + memdelete(instance); + ERR_FAIL_COND_V(err.error!=Variant::CallError::CALL_OK, NULL); //error consrtucting + } + + //@TODO make thread safe + return instance; + +} + +Variant GDScript::_new(const Variant** p_args,int p_argcount,Variant::CallError& r_error) { + + /* STEP 1, CREATE */ + + r_error.error=Variant::CallError::CALL_OK; + REF ref; + Object *owner=NULL; + + GDScript *_baseptr=this; + while (_baseptr->_base) { + _baseptr=_baseptr->_base; + } + + if (_baseptr->native.ptr()) { + owner=_baseptr->native->instance(); + } else { + owner=memnew( Reference ); //by default, no base means use reference + } + + Reference *r=owner->cast_to<Reference>(); + if (r) { + ref=REF(r); + } + + + GDInstance* instance = _create_instance(p_args,p_argcount,owner,r!=NULL); + if (!instance) { + if (ref.is_null()) { + memdelete(owner); //no owner, sorry + } + return Variant(); + } + + if (ref.is_valid()) { + return ref; + } else { + return owner; + } +} + +bool GDScript::can_instance() const { + + return valid; //any script in GDscript can instance +} + +StringName GDScript::get_instance_base_type() const { + + if (native.is_valid()) + return native->get_name(); + if (base.is_valid()) + return base->get_instance_base_type(); + return StringName(); +} + +struct _GDScriptMemberSort { + + int index; + StringName name; + _FORCE_INLINE_ bool operator<(const _GDScriptMemberSort& p_member) const { return index < p_member.index; } + +}; + + +#ifdef TOOLS_ENABLED + + +void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { + + placeholders.erase(p_placeholder); +} + +void GDScript::_update_placeholder(PlaceHolderScriptInstance *p_placeholder) { + + + List<PropertyInfo> plist; + GDScript *scr=this; + + Map<StringName,Variant> default_values; + while(scr) { + + Vector<_GDScriptMemberSort> msort; + for(Map<StringName,PropertyInfo>::Element *E=scr->member_info.front();E;E=E->next()) { + + _GDScriptMemberSort ms; + ERR_CONTINUE(!scr->member_indices.has(E->key())); + ms.index=scr->member_indices[E->key()]; + ms.name=E->key(); + + msort.push_back(ms); + + } + + msort.sort(); + msort.invert(); + for(int i=0;i<msort.size();i++) { + + plist.push_front(scr->member_info[msort[i].name]); + if (scr->member_default_values.has(msort[i].name)) + default_values[msort[i].name]=scr->member_default_values[msort[i].name]; + else { + Variant::CallError err; + default_values[msort[i].name]=Variant::construct(scr->member_info[msort[i].name].type,NULL,0,err); + } + } + + scr=scr->_base; + } + + + p_placeholder->update(plist,default_values); + +} +#endif +ScriptInstance* GDScript::instance_create(Object *p_this) { + + if (!tool && !ScriptServer::is_scripting_enabled()) { + +#ifdef TOOLS_ENABLED + + //instance a fake script for editing the values + //plist.invert(); + + /*print_line("CREATING PLACEHOLDER"); + for(List<PropertyInfo>::Element *E=plist.front();E;E=E->next()) { + print_line(E->get().name); + }*/ + PlaceHolderScriptInstance *si = memnew( PlaceHolderScriptInstance(GDScriptLanguage::get_singleton(),Ref<Script>(this),p_this) ); + placeholders.insert(si); + _update_placeholder(si); + return si; +#else + return NULL; +#endif + } + + GDScript *top=this; + while(top->_base) + top=top->_base; + + if (top->native.is_valid()) { + if (!ObjectTypeDB::is_type(p_this->get_type_name(),top->native->get_name())) { + + if (ScriptDebugger::get_singleton()) { + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),0,"Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_type()+"'"); + } + ERR_EXPLAIN("Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_type()+"'"); + ERR_FAIL_V(NULL); + + } + } + + return _create_instance(NULL,0,p_this,p_this->cast_to<Reference>()); + +} +bool GDScript::instance_has(const Object *p_this) const { + + return instances.has((Object*)p_this); +} + +bool GDScript::has_source_code() const { + + return source!=""; +} +String GDScript::get_source_code() const { + + return source; +} +void GDScript::set_source_code(const String& p_code) { + + source=p_code; + +} + +void GDScript::_set_subclass_path(Ref<GDScript>& p_sc,const String& p_path) { + + p_sc->path=p_path; + for(Map<StringName,Ref<GDScript> >::Element *E=p_sc->subclasses.front();E;E=E->next()) { + + _set_subclass_path(E->get(),p_path); + } +} + +Error GDScript::reload() { + + + ERR_FAIL_COND_V(instances.size(),ERR_ALREADY_IN_USE); + + String basedir=path; + + if (basedir=="") + basedir==get_path(); + + if (basedir!="") + basedir=basedir.get_base_dir(); + + + + valid=false; + GDParser parser; + Error err = parser.parse(source,basedir); + if (err) { + if (ScriptDebugger::get_singleton()) { + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),parser.get_error_line(),"Parser Error: "+parser.get_error()); + } + _err_print_error("GDScript::reload",path.empty()?"built-in":(const char*)path.utf8().get_data(),parser.get_error_line(),("Parse Error: "+parser.get_error()).utf8().get_data()); + ERR_FAIL_V(ERR_PARSE_ERROR); + } + + GDCompiler compiler; + err = compiler.compile(&parser,this); + + if (err) { + if (ScriptDebugger::get_singleton()) { + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),compiler.get_error_line(),"Parser Error: "+compiler.get_error()); + } + _err_print_error("GDScript::reload",path.empty()?"built-in":(const char*)path.utf8().get_data(),compiler.get_error_line(),("Compile Error: "+compiler.get_error()).utf8().get_data()); + ERR_FAIL_V(ERR_COMPILATION_FAILED); + } + + valid=true; + + for(Map<StringName,Ref<GDScript> >::Element *E=subclasses.front();E;E=E->next()) { + + _set_subclass_path(E->get(),path); + } + +#ifdef TOOLS_ENABLED + for (Set<PlaceHolderScriptInstance*>::Element *E=placeholders.front();E;E=E->next()) { + + _update_placeholder(E->get()); + } +#endif + return OK; +} + +String GDScript::get_node_type() const { + + return ""; // ? +} + +ScriptLanguage *GDScript::get_language() const { + + return GDScriptLanguage::get_singleton(); +} + + +Variant GDScript::call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) { + + + GDScript *top=this; + while(top) { + + Map<StringName,GDFunction>::Element *E=top->member_functions.find(p_method); + if (E) { + + if (!E->get().is_static()) { + WARN_PRINT(String("Can't call non-static function: '"+String(p_method)+"' in script.").utf8().get_data()); + } + + return E->get().call(NULL,p_args,p_argcount,r_error); + } + top=top->_base; + } + + //none found, regular + + return Script::call(p_method,p_args,p_argcount,r_error); + +} + +bool GDScript::_get(const StringName& p_name,Variant &r_ret) const { + + { + + + const GDScript *top=this; + while(top) { + + { + const Map<StringName,Variant>::Element *E=top->constants.find(p_name); + if (E) { + + r_ret= E->get(); + return true; + } + } + + { + const Map<StringName,Ref<GDScript> >::Element *E=subclasses.find(p_name); + if (E) { + + r_ret=E->get(); + return true; + } + } + top=top->_base; + } + + if (p_name==GDScriptLanguage::get_singleton()->strings._script_source) { + + r_ret=get_source_code(); + return true; + } + } + + + + return false; + +} +bool GDScript::_set(const StringName& p_name, const Variant& p_value) { + + if (p_name==GDScriptLanguage::get_singleton()->strings._script_source) { + + set_source_code(p_value); + reload(); + } else + return false; + + return true; +} + +void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { + + p_properties->push_back( PropertyInfo(Variant::STRING,"script/source",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR) ); +} + + +void GDScript::_bind_methods() { + + ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"new",&GDScript::_new,MethodInfo("new")); + +} + +Error GDScript::load_source_code(const String& p_path) { + + + DVector<uint8_t> sourcef; + Error err; + FileAccess *f=FileAccess::open(p_path,FileAccess::READ,&err); + if (err) { + + ERR_FAIL_COND_V(err,err); + } + + int len = f->get_len(); + sourcef.resize(len+1); + DVector<uint8_t>::Write w = sourcef.write(); + int r = f->get_buffer(w.ptr(),len); + f->close(); + memdelete(f); + ERR_FAIL_COND_V(r!=len,ERR_CANT_OPEN); + w[len]=0; + + String s; + if (s.parse_utf8((const char*)w.ptr())) { + + ERR_EXPLAIN("Script '"+p_path+"' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); + ERR_FAIL_V(ERR_INVALID_DATA); + } + + source=s; + path=p_path; + return OK; + +} + + +const Map<StringName,GDFunction>& GDScript::debug_get_member_functions() const { + + return member_functions; +} + + + +StringName GDScript::debug_get_member_by_index(int p_idx) const { + + + for(const Map<StringName,int>::Element *E=member_indices.front();E;E=E->next()) { + + if (E->get()==p_idx) + return E->key(); + } + + return "<error>"; +} + + +Ref<GDScript> GDScript::get_base() const { + + return base; +} + +GDScript::GDScript() { + + + valid=false; + subclass_count=0; + initializer=NULL; + _base=NULL; + _owner=NULL; + tool=false; +} + + + + + +////////////////////////////// +// INSTANCE // +////////////////////////////// + + +bool GDInstance::set(const StringName& p_name, const Variant& p_value) { + + //member + { + const Map<StringName,int>::Element *E = script->member_indices.find(p_name); + if (E) { + members[E->get()]=p_value; + return true; + + } + } + + GDScript *sptr=script.ptr(); + while(sptr) { + + + Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set); + if (E) { + + Variant name=p_name; + const Variant *args[2]={&name,&p_value}; + + Variant::CallError err; + Variant ret = E->get().call(this,(const Variant**)args,2,err); + if (err.error==Variant::CallError::CALL_OK && ret.get_type()==Variant::BOOL && ret.operator bool()) + return true; + } + sptr = sptr->_base; + } + + return false; +} + +bool GDInstance::get(const StringName& p_name, Variant &r_ret) const { + + + + const GDScript *sptr=script.ptr(); + while(sptr) { + + { + const Map<StringName,int>::Element *E = script->member_indices.find(p_name); + if (E) { + r_ret=members[E->get()]; + return true; //index found + + } + } + + { + const Map<StringName,Variant>::Element *E = script->constants.find(p_name); + if (E) { + r_ret=E->get(); + return true; //index found + + } + } + + { + const Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get); + if (E) { + + Variant name=p_name; + const Variant *args[1]={&name}; + + Variant::CallError err; + Variant ret = const_cast<GDFunction*>(&E->get())->call(const_cast<GDInstance*>(this),(const Variant**)args,1,err); + if (err.error==Variant::CallError::CALL_OK && ret.get_type()!=Variant::NIL) { + r_ret=ret; + return true; + } + } + } + sptr = sptr->_base; + } + + return false; + +} +void GDInstance::get_property_list(List<PropertyInfo> *p_properties) const { + // exported members, not doen yet! + + const GDScript *sptr=script.ptr(); + List<PropertyInfo> props; + + while(sptr) { + + + const Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list); + if (E) { + + + Variant::CallError err; + Variant ret = const_cast<GDFunction*>(&E->get())->call(const_cast<GDInstance*>(this),NULL,0,err); + if (err.error==Variant::CallError::CALL_OK) { + + if (ret.get_type()!=Variant::ARRAY) { + + ERR_EXPLAIN("Wrong type for _get_property list, must be an array of dictionaries."); + ERR_FAIL(); + } + Array arr = ret; + for(int i=0;i<arr.size();i++) { + + Dictionary d = arr[i]; + ERR_CONTINUE(!d.has("name")); + ERR_CONTINUE(!d.has("type")); + PropertyInfo pinfo; + pinfo.type = Variant::Type( d["type"].operator int()); + ERR_CONTINUE(pinfo.type<0 || pinfo.type>=Variant::VARIANT_MAX ); + pinfo.name = d["name"]; + ERR_CONTINUE(pinfo.name==""); + if (d.has("hint")) + pinfo.hint=PropertyHint(d["hint"].operator int()); + if (d.has("hint_string")) + pinfo.hint_string=d["hint_string"]; + if (d.has("usage")) + pinfo.usage=d["usage"]; + + props.push_back(pinfo); + + } + + } + } + + //instance a fake script for editing the values + + Vector<_GDScriptMemberSort> msort; + for(Map<StringName,PropertyInfo>::Element *E=sptr->member_info.front();E;E=E->next()) { + + _GDScriptMemberSort ms; + ERR_CONTINUE(!sptr->member_indices.has(E->key())); + ms.index=sptr->member_indices[E->key()]; + ms.name=E->key(); + msort.push_back(ms); + + } + + msort.sort(); + msort.invert(); + for(int i=0;i<msort.size();i++) { + + props.push_front(sptr->member_info[msort[i].name]); + + } +#if 0 + if (sptr->member_functions.has("_get_property_list")) { + + Variant::CallError err; + GDFunction *f = const_cast<GDFunction*>(&sptr->member_functions["_get_property_list"]); + Variant plv = f->call(const_cast<GDInstance*>(this),NULL,0,err); + + if (plv.get_type()!=Variant::ARRAY) { + + ERR_PRINT("_get_property_list: expected array returned"); + } else { + + Array pl=plv; + + for(int i=0;i<pl.size();i++) { + + Dictionary p = pl[i]; + PropertyInfo pinfo; + if (!p.has("name")) { + ERR_PRINT("_get_property_list: expected 'name' key of type string.") + continue; + } + if (!p.has("type")) { + ERR_PRINT("_get_property_list: expected 'type' key of type integer.") + continue; + } + pinfo.name=p["name"]; + pinfo.type=Variant::Type(int(p["type"])); + if (p.has("hint")) + pinfo.hint=PropertyHint(int(p["hint"])); + if (p.has("hint_string")) + pinfo.hint_string=p["hint_string"]; + if (p.has("usage")) + pinfo.usage=p["usage"]; + + + props.push_back(pinfo); + } + } + } +#endif + + sptr = sptr->_base; + } + + //props.invert(); + + for (List<PropertyInfo>::Element *E=props.front();E;E=E->next()) { + + p_properties->push_back(E->get()); + } +} + +void GDInstance::get_method_list(List<MethodInfo> *p_list) const { + + const GDScript *sptr=script.ptr(); + while(sptr) { + + for (Map<StringName,GDFunction>::Element *E = sptr->member_functions.front();E;E=E->next()) { + + MethodInfo mi; + mi.name=E->key(); + for(int i=0;i<E->get().get_argument_count();i++) + mi.arguments.push_back(PropertyInfo(Variant::NIL,"arg"+itos(i))); + p_list->push_back(mi); + } + sptr = sptr->_base; + } + +} + +bool GDInstance::has_method(const StringName& p_method) const { + + const GDScript *sptr=script.ptr(); + while(sptr) { + const Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method); + if (E) + return true; + sptr = sptr->_base; + } + + return false; +} +Variant GDInstance::call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) { + + //printf("calling %ls:%i method %ls\n", script->get_path().c_str(), -1, String(p_method).c_str()); + + GDScript *sptr=script.ptr(); + while(sptr) { + Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method); + if (E) { + return E->get().call(this,p_args,p_argcount,r_error); + } + sptr = sptr->_base; + } + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return Variant(); +} + +void GDInstance::call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount) { + + GDScript *sptr=script.ptr(); + Variant::CallError ce; + + while(sptr) { + Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method); + if (E) { + E->get().call(this,p_args,p_argcount,ce); + } + sptr = sptr->_base; + } + +} + + +void GDInstance::_ml_call_reversed(GDScript *sptr,const StringName& p_method,const Variant** p_args,int p_argcount) { + + if (sptr->_base) + _ml_call_reversed(sptr->_base,p_method,p_args,p_argcount); + + Variant::CallError ce; + + Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method); + if (E) { + E->get().call(this,p_args,p_argcount,ce); + } + +} + +void GDInstance::call_multilevel_reversed(const StringName& p_method,const Variant** p_args,int p_argcount) { + + if (script.ptr()) { + _ml_call_reversed(script.ptr(),p_method,p_args,p_argcount); + } +} + +void GDInstance::notification(int p_notification) { + + //notification is not virutal, it gets called at ALL levels just like in C. + Variant value=p_notification; + const Variant *args[1]={&value }; + + GDScript *sptr=script.ptr(); + while(sptr) { + Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification); + if (E) { + Variant::CallError err; + E->get().call(this,args,1,err); + if (err.error!=Variant::CallError::CALL_OK) { + //print error about notification call + + } + } + sptr = sptr->_base; + } + +} + +Ref<Script> GDInstance::get_script() const { + + return script; +} + +ScriptLanguage *GDInstance::get_language() { + + return GDScriptLanguage::get_singleton(); +} + + +GDInstance::GDInstance() { + owner=NULL; + base_ref=false; +} + +GDInstance::~GDInstance() { + if (script.is_valid() && owner) { + script->instances.erase(owner); + } +} + +/************* SCRIPT LANGUAGE **************/ +/************* SCRIPT LANGUAGE **************/ +/************* SCRIPT LANGUAGE **************/ +/************* SCRIPT LANGUAGE **************/ +/************* SCRIPT LANGUAGE **************/ + +GDScriptLanguage *GDScriptLanguage::singleton=NULL; + + +String GDScriptLanguage::get_name() const { + + return "GDScript"; +} + +/* LANGUAGE FUNCTIONS */ + +void GDScriptLanguage::_add_global(const StringName& p_name,const Variant& p_value) { + + + if (globals.has(p_name)) { + //overwrite existing + global_array[globals[p_name]]=p_value; + return; + } + globals[p_name]=global_array.size(); + global_array.push_back(p_value); + _global_array=global_array.ptr(); +} + +void GDScriptLanguage::init() { + + + //populate global constants + int gcc=GlobalConstants::get_global_constant_count(); + for(int i=0;i<gcc;i++) { + + _add_global(StaticCString::create(GlobalConstants::get_global_constant_name(i)),GlobalConstants::get_global_constant_value(i)); + } + + _add_global(StaticCString::create("PI"),Math_PI); + + //populate native classes + + List<String> class_list; + ObjectTypeDB::get_type_list(&class_list); + for(List<String>::Element *E=class_list.front();E;E=E->next()) { + + StringName n = E->get(); + String s = String(n); + if (s.begins_with("_")) + n=s.substr(1,s.length()); + + if (globals.has(n)) + continue; + Ref<GDNativeClass> nc = memnew( GDNativeClass(E->get()) ); + _add_global(n,nc); + } + + //populate singletons + + List<Globals::Singleton> singletons; + Globals::get_singleton()->get_singletons(&singletons); + for(List<Globals::Singleton>::Element *E=singletons.front();E;E=E->next()) { + + _add_global(E->get().name,E->get().ptr); + } +} + +String GDScriptLanguage::get_type() const { + + return "GDScript"; +} +String GDScriptLanguage::get_extension() const { + + return "gd"; +} +Error GDScriptLanguage::execute_file(const String& p_path) { + + // ?? + return OK; +} +void GDScriptLanguage::finish() { + + +} + + +void GDScriptLanguage::frame() { + +// print_line("calls: "+itos(calls)); + calls=0; +} + +/* EDITOR FUNCTIONS */ +void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { + + static const char *_reserved_words[]={ + "break", + "class", + "continue", + "const", + "else", + "elif", + "enum", + "extends" , + "for" , + "func" , + "if" , + "in" , + "varl", + "null" , + "return" , + "self" , + "while" , + "true" , + "false" , + "tool", + "var", + "pass", + "and", + "or", + "export", + 0}; + + + const char **w=_reserved_words; + + + while (*w) { + + p_words->push_back(*w); + w++; + } + + for(int i=0;i<GDFunctions::FUNC_MAX;i++) { + p_words->push_back(GDFunctions::get_func_name(GDFunctions::Function(i))); + } + +} + +GDScriptLanguage::GDScriptLanguage() { + + calls=0; + ERR_FAIL_COND(singleton); + singleton=this; + strings._init = StaticCString::create("_init"); + strings._notification = StaticCString::create("_notification"); + strings._set= StaticCString::create("_set"); + strings._get= StaticCString::create("_get"); + strings._get_property_list= StaticCString::create("_get_property_list"); + strings._script_source=StaticCString::create("script/source"); + _debug_parse_err_line=-1; + _debug_parse_err_file=""; + + _debug_call_stack_pos=0; + int dmcs=GLOBAL_DEF("debug/script_max_call_stack",1024); + if (ScriptDebugger::get_singleton()) { + //debugging enabled! + + _debug_max_call_stack = dmcs; + if (_debug_max_call_stack<1024) + _debug_max_call_stack=1024; + _call_stack = memnew_arr( CallLevel, _debug_max_call_stack+1 ); + + } else { + _debug_max_call_stack=0; + _call_stack=NULL; + } + +} + + +GDScriptLanguage::~GDScriptLanguage() { + + if (_call_stack) { + memdelete_arr(_call_stack); + } + singleton=NULL; +} + +/*************** RESOURCE ***************/ + +RES ResourceFormatLoaderGDScript::load(const String &p_path,const String& p_original_path) { + + GDScript *script = memnew( GDScript ); + + Ref<GDScript> scriptres(script); + + Error err = script->load_source_code(p_path); + + if (err!=OK) { + + ERR_FAIL_COND_V(err!=OK, RES()); + } + + script->set_script_path(p_original_path); // script needs this. + script->set_path(p_original_path); + //script->set_name(p_path.get_file()); + + script->reload(); + + return scriptres; +} +void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("gd"); +} + +bool ResourceFormatLoaderGDScript::handles_type(const String& p_type) const { + + return (p_type=="Script" || p_type=="GDScript"); +} + +String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const { + + if (p_path.extension().to_lower()=="gd") + return "GDScript"; + return ""; +} + + +Error ResourceFormatSaverGDScript::save(const String &p_path,const RES& p_resource,uint32_t p_flags) { + + Ref<GDScript> sqscr = p_resource; + ERR_FAIL_COND_V(sqscr.is_null(),ERR_INVALID_PARAMETER); + + String source = sqscr->get_source_code(); + + Error err; + FileAccess *file = FileAccess::open(p_path,FileAccess::WRITE,&err); + + + if (err) { + + ERR_FAIL_COND_V(err,err); + } + + file->store_string(source); + + file->close(); + memdelete(file); + return OK; +} + +void ResourceFormatSaverGDScript::get_recognized_extensions(const RES& p_resource,List<String> *p_extensions) const { + + if (p_resource->cast_to<GDScript>()) { + p_extensions->push_back("gd"); + } + +} +bool ResourceFormatSaverGDScript::recognize(const RES& p_resource) const { + + return p_resource->cast_to<GDScript>()!=NULL; +} diff --git a/modules/gdscript/gd_script.h b/modules/gdscript/gd_script.h new file mode 100644 index 0000000000..70dec4e8ee --- /dev/null +++ b/modules/gdscript/gd_script.h @@ -0,0 +1,473 @@ +/*************************************************************************/ +/* gd_script.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +#ifndef GD_SCRIPT_H +#define GD_SCRIPT_H + +#include "script_language.h" +#include "io/resource_loader.h" +#include "io/resource_saver.h" +#include "os/thread.h" +#include "pair.h" +class GDInstance; +class GDScript; + +class GDFunction { +public: + + enum Opcode { + OPCODE_OPERATOR, + OPCODE_EXTENDS_TEST, + OPCODE_SET, + OPCODE_GET, + OPCODE_SET_NAMED, + OPCODE_GET_NAMED, + OPCODE_ASSIGN, + OPCODE_ASSIGN_TRUE, + OPCODE_ASSIGN_FALSE, + OPCODE_CONSTRUCT, //only for basic types!! + OPCODE_CONSTRUCT_ARRAY, + OPCODE_CONSTRUCT_DICTIONARY, + OPCODE_CALL, + OPCODE_CALL_RETURN, + OPCODE_CALL_BUILT_IN, + OPCODE_CALL_SELF, + OPCODE_CALL_SELF_BASE, + OPCODE_JUMP, + OPCODE_JUMP_IF, + OPCODE_JUMP_IF_NOT, + OPCODE_JUMP_TO_DEF_ARGUMENT, + OPCODE_RETURN, + OPCODE_ITERATE_BEGIN, + OPCODE_ITERATE, + OPCODE_ASSERT, + OPCODE_LINE, + OPCODE_END + }; + + enum Address { + ADDR_BITS=24, + ADDR_MASK=((1<<ADDR_BITS)-1), + ADDR_TYPE_MASK=~ADDR_MASK, + ADDR_TYPE_SELF=0, + ADDR_TYPE_MEMBER=1, + ADDR_TYPE_CLASS_CONSTANT=2, + ADDR_TYPE_LOCAL_CONSTANT=3, + ADDR_TYPE_STACK=4, + ADDR_TYPE_STACK_VARIABLE=5, + ADDR_TYPE_GLOBAL=6, + ADDR_TYPE_NIL=7 + }; + + struct StackDebug { + + int line; + int pos; + bool added; + StringName identifier; + }; + +private: +friend class GDCompiler; + + StringName source; + + mutable Variant nil; + mutable Variant *_constants_ptr; + int _constant_count; + const StringName *_global_names_ptr; + int _global_names_count; + const int *_default_arg_ptr; + int _default_arg_count; + const int *_code_ptr; + int _code_size; + int _argument_count; + int _stack_size; + int _call_size; + int _initial_line; + bool _static; + GDScript *_script; + + StringName name; + Vector<Variant> constants; + Vector<StringName> global_names; + Vector<int> default_arguments; + + Vector<int> code; + + List<StackDebug> stack_debug; + + _FORCE_INLINE_ Variant *_get_variant(int p_address,GDInstance *p_instance,GDScript *p_script,Variant &self,Variant *p_stack,String& r_error) const; + _FORCE_INLINE_ String _get_call_error(const Variant::CallError& p_err, const String& p_where,const Variant**argptrs) const; + + +public: + + + _FORCE_INLINE_ bool is_static() const { return _static; } + + const int* get_code() const; //used for debug + int get_code_size() const; + Variant get_constant(int p_idx) const; + StringName get_global_name(int p_idx) const; + StringName get_name() const; + int get_max_stack_size() const; + int get_default_argument_count() const; + int get_default_argument_addr(int p_idx) const; + GDScript *get_script() const { return _script; } + + void debug_get_stack_member_state(int p_line,List<Pair<StringName,int> > *r_stackvars) const; + + _FORCE_INLINE_ bool is_empty() const { return _code_size==0; } + + int get_argument_count() const { return _argument_count; } + Variant call(GDInstance *p_instance,const Variant **p_args, int p_argcount,Variant::CallError& r_err); + + GDFunction(); +}; + + +class GDNativeClass : public Reference { + + OBJ_TYPE(GDNativeClass,Reference); + + StringName name; +protected: + + bool _get(const StringName& p_name,Variant &r_ret) const; + static void _bind_methods(); + +public: + + _FORCE_INLINE_ const StringName& get_name() const { return name; } + Variant _new(); + Object *instance(); + GDNativeClass(const StringName& p_name); +}; + + +class GDScript : public Script { + + + OBJ_TYPE(GDScript,Script); + bool tool; + bool valid; + + +friend class GDInstance; +friend class GDFunction; +friend class GDCompiler; +friend class GDFunctions; + Ref<GDNativeClass> native; + Ref<GDScript> base; + GDScript *_base; //fast pointer access + GDScript *_owner; //for subclasses + + Set<StringName> members; //members are just indices to the instanced script. + Map<StringName,Variant> constants; + Map<StringName,GDFunction> member_functions; + Map<StringName,int> member_indices; //members are just indices to the instanced script. + Map<StringName,Ref<GDScript> > subclasses; + +#ifdef TOOLS_ENABLED + Map<StringName,Variant> member_default_values; +#endif + Map<StringName,PropertyInfo> member_info; + + GDFunction *initializer; //direct pointer to _init , faster to locate + + int subclass_count; + Set<Object*> instances; + //exported members + String source; + String path; + String name; + + + GDInstance* _create_instance(const Variant** p_args,int p_argcount,Object *p_owner,bool p_isref); + + void _set_subclass_path(Ref<GDScript>& p_sc,const String& p_path); + +#ifdef TOOLS_ENABLED + Set<PlaceHolderScriptInstance*> placeholders; + void _update_placeholder(PlaceHolderScriptInstance *p_placeholder); + virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); +#endif + + +protected: + bool _get(const StringName& p_name,Variant &r_ret) const; + bool _set(const StringName& p_name, const Variant& p_value); + void _get_property_list(List<PropertyInfo> *p_properties) const; + + Variant call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error); +// void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount); + + static void _bind_methods(); +public: + + + const Map<StringName,Ref<GDScript> >& get_subclasses() const { return subclasses; } + const Map<StringName,Variant >& get_constants() const { return constants; } + const Set<StringName>& get_members() const { return members; } + const Map<StringName,GDFunction>& get_member_functions() const { return member_functions; } + const Ref<GDNativeClass>& get_native() const { return native; } + + + bool is_tool() const { return tool; } + Ref<GDScript> get_base() const; + + const Map<StringName,int>& debug_get_member_indices() const { return member_indices; } + const Map<StringName,GDFunction>& debug_get_member_functions() const; //this is debug only + StringName debug_get_member_by_index(int p_idx) const; + + Variant _new(const Variant** p_args,int p_argcount,Variant::CallError& r_error); + virtual bool can_instance() const; + + virtual StringName get_instance_base_type() const; // this may not work in all scripts, will return empty if so + virtual ScriptInstance* instance_create(Object *p_this); + virtual bool instance_has(const Object *p_this) const; + + virtual bool has_source_code() const; + virtual String get_source_code() const; + virtual void set_source_code(const String& p_code); + virtual Error reload(); + + virtual String get_node_type() const; + void set_script_path(const String& p_path) { path=p_path; } //because subclasses need a path too... + Error load_source_code(const String& p_path); + + virtual ScriptLanguage *get_language() const; + + GDScript(); +}; + +class GDInstance : public ScriptInstance { +friend class GDScript; +friend class GDFunction; +friend class GDFunctions; + + Object *owner; + Ref<GDScript> script; + Vector<Variant> members; + bool base_ref; + + void _ml_call_reversed(GDScript *sptr,const StringName& p_method,const Variant** p_args,int p_argcount); + +public: + + virtual bool set(const StringName& p_name, const Variant& p_value); + virtual bool get(const StringName& p_name, Variant &r_ret) const; + virtual void get_property_list(List<PropertyInfo> *p_properties) const; + + virtual void get_method_list(List<MethodInfo> *p_list) const; + virtual bool has_method(const StringName& p_method) const; + virtual Variant call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error); + virtual void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount); + virtual void call_multilevel_reversed(const StringName& p_method,const Variant** p_args,int p_argcount); + + Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; } + + virtual void notification(int p_notification); + + virtual Ref<Script> get_script() const; + + virtual ScriptLanguage *get_language(); + + void set_path(const String& p_path); + + + GDInstance(); + ~GDInstance(); + +}; + +class GDScriptLanguage : public ScriptLanguage { + + static GDScriptLanguage *singleton; + + Variant* _global_array; + Vector<Variant> global_array; + Map<StringName,int> globals; + + + struct CallLevel { + + Variant *stack; + GDFunction *function; + GDInstance *instance; + int *ip; + int *line; + + }; + + + int _debug_parse_err_line; + String _debug_parse_err_file; + String _debug_error; + int _debug_call_stack_pos; + int _debug_max_call_stack; + CallLevel *_call_stack; + + void _add_global(const StringName& p_name,const Variant& p_value); + + +public: + + int calls; + + bool debug_break(const String& p_error,bool p_allow_continue=true); + bool debug_break_parse(const String& p_file, int p_line,const String& p_error); + + _FORCE_INLINE_ void enter_function(GDInstance *p_instance,GDFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) { + + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now + + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() +1 ); + + if (_debug_call_stack_pos >= _debug_max_call_stack) { + //stack overflow + _debug_error="Stack Overflow (Stack Size: "+itos(_debug_max_call_stack)+")"; + ScriptDebugger::get_singleton()->debug(this); + return; + } + + _call_stack[_debug_call_stack_pos].stack=p_stack; + _call_stack[_debug_call_stack_pos].instance=p_instance; + _call_stack[_debug_call_stack_pos].function=p_function; + _call_stack[_debug_call_stack_pos].ip=p_ip; + _call_stack[_debug_call_stack_pos].line=p_line; + _debug_call_stack_pos++; + } + + _FORCE_INLINE_ void exit_function() { + + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now + + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() -1 ); + + if (_debug_call_stack_pos==0) { + + _debug_error="Stack Underflow (Engine Bug)"; + ScriptDebugger::get_singleton()->debug(this); + return; + } + + _debug_call_stack_pos--; + } + + + struct { + + StringName _init; + StringName _notification; + StringName _set; + StringName _get; + StringName _get_property_list; + StringName _script_source; + + } strings; + + + _FORCE_INLINE_ int get_global_array_size() const { return global_array.size(); } + _FORCE_INLINE_ Variant* get_global_array() { return _global_array; } + _FORCE_INLINE_ const Map<StringName,int>& get_global_map() { return globals; } + + _FORCE_INLINE_ static GDScriptLanguage *get_singleton() { return singleton; } + + virtual String get_name() const; + + /* LANGUAGE FUNCTIONS */ + virtual void init(); + virtual String get_type() const; + virtual String get_extension() const; + virtual Error execute_file(const String& p_path) ; + virtual void finish(); + + /* EDITOR FUNCTIONS */ + virtual void get_reserved_words(List<String> *p_words) const; + virtual void get_comment_delimiters(List<String> *p_delimiters) const; + virtual void get_string_delimiters(List<String> *p_delimiters) const; + virtual String get_template(const String& p_class_name, const String& p_base_class_name) const; + virtual bool validate(const String& p_script,int &r_line_error,int &r_col_error,String& r_test_error, const String& p_path="",List<String> *r_functions=NULL) const; + virtual Script *create_script() const; + virtual bool has_named_classes() const; + virtual int find_function(const String& p_function,const String& p_code) const; + virtual String make_function(const String& p_class,const String& p_name,const StringArray& p_args) const; + virtual Error complete_keyword(const String& p_code, int p_line, const String& p_base_path,const String& p_keyword, List<String>* r_options); + + /* DEBUGGER FUNCTIONS */ + + virtual String debug_get_error() const; + virtual int debug_get_stack_level_count() const; + virtual int debug_get_stack_level_line(int p_level) const; + virtual String debug_get_stack_level_function(int p_level) const; + virtual String debug_get_stack_level_source(int p_level) const; + virtual void debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); + virtual void debug_get_stack_level_members(int p_level,List<String> *p_members, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); + virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); + virtual String debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems=-1,int p_max_depth=-1); + + virtual void frame(); + + virtual void get_public_functions(List<MethodInfo> *p_functions) const; + virtual void get_public_constants(List<Pair<String,Variant> > *p_constants) const; + + /* LOADER FUNCTIONS */ + + virtual void get_recognized_extensions(List<String> *p_extensions) const; + + GDScriptLanguage(); + ~GDScriptLanguage(); +}; + + +class ResourceFormatLoaderGDScript : public ResourceFormatLoader { +public: + + virtual RES load(const String &p_path,const String& p_original_path=""); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; + +}; + +class ResourceFormatSaverGDScript : public ResourceFormatSaver { +public: + + virtual Error save(const String &p_path,const RES& p_resource,uint32_t p_flags=0); + virtual void get_recognized_extensions(const RES& p_resource,List<String> *p_extensions) const; + virtual bool recognize(const RES& p_resource) const; + +}; + +#endif // GD_SCRIPT_H diff --git a/modules/gdscript/gd_tokenizer.cpp b/modules/gdscript/gd_tokenizer.cpp new file mode 100644 index 0000000000..f7320799a5 --- /dev/null +++ b/modules/gdscript/gd_tokenizer.cpp @@ -0,0 +1,973 @@ +/*************************************************************************/ +/* gd_tokenizer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_tokenizer.h" +#include "print_string.h" +#include "gd_functions.h" +const char* GDTokenizer::token_names[TK_MAX]={ +"Empty", +"Identifier", +"Constant", +"Self", +"Built-In Type", +"Built-In Func", +"In", +"'=='", +"'!='", +"'<'", +"'<='", +"'>'", +"'>='", +"'and'", +"'or'", +"'not'", +"'+'", +"'-'", +"'*'", +"'/'", +"'%'", +"'<<'", +"'>>'", +"'='", +"'+='", +"'-='", +"'*='", +"'/='", +"'%='", +"'<<='", +"'>>='", +"'&='", +"'|='", +"'^='", +"'&'", +"'|'", +"'^'", +"'~'", +//"Plus Plus", +//"Minus Minus", +"if", +"elif", +"else", +"for", +"do", +"while", +"switch", +"case", +"break", +"continue", +"pass", +"return", +"func", +"class", +"extends", +"tool", +"static", +"export", +"const", +"var", +"preload", +"assert", +"'['", +"']'", +"'{'", +"'}'", +"'('", +"')'", +"','", +"';'", +"'.'", +"'?'", +"':'", +"'\\n'", +"Error", +"EOF"}; + +const char *GDTokenizer::get_token_name(Token p_token) { + + ERR_FAIL_INDEX_V(p_token,TK_MAX,"<error>"); + return token_names[p_token]; +} + +static bool _is_text_char(CharType c) { + + return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='_'; +} + +static bool _is_number(CharType c) { + + return (c>='0' && c<='9'); +} + +static bool _is_hex(CharType c) { + + return (c>='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F'); +} + +void GDTokenizer::_make_token(Token p_type) { + + TokenData &tk=tk_rb[tk_rb_pos]; + + tk.type=p_type; + tk.line=line; + tk.col=column; + + tk_rb_pos=(tk_rb_pos+1)%TK_RB_SIZE; +} +void GDTokenizer::_make_identifier(const StringName& p_identifier) { + + TokenData &tk=tk_rb[tk_rb_pos]; + + tk.type=TK_IDENTIFIER; + tk.identifier=p_identifier; + tk.line=line; + tk.col=column; + + tk_rb_pos=(tk_rb_pos+1)%TK_RB_SIZE; + +} + +void GDTokenizer::_make_built_in_func(GDFunctions::Function p_func) { + + TokenData &tk=tk_rb[tk_rb_pos]; + + tk.type=TK_BUILT_IN_FUNC; + tk.func=p_func; + tk.line=line; + tk.col=column; + + tk_rb_pos=(tk_rb_pos+1)%TK_RB_SIZE; + +} +void GDTokenizer::_make_constant(const Variant& p_constant) { + + TokenData &tk=tk_rb[tk_rb_pos]; + + tk.type=TK_CONSTANT; + tk.constant=p_constant; + tk.line=line; + tk.col=column; + + tk_rb_pos=(tk_rb_pos+1)%TK_RB_SIZE; + +} + +void GDTokenizer::_make_type(const Variant::Type& p_type) { + + + TokenData &tk=tk_rb[tk_rb_pos]; + + tk.type=TK_BUILT_IN_TYPE; + tk.vtype=p_type; + tk.line=line; + tk.col=column; + + tk_rb_pos=(tk_rb_pos+1)%TK_RB_SIZE; + +} + + +void GDTokenizer::_make_error(const String& p_error) { + + error_flag=true; + last_error=p_error; + + TokenData &tk=tk_rb[tk_rb_pos]; + tk.type=TK_ERROR; + tk.constant=p_error; + tk.line=line; + tk.col=column; + tk_rb_pos=(tk_rb_pos+1)%TK_RB_SIZE; + +} + + +void GDTokenizer::_make_newline(int p_spaces) { + + TokenData &tk=tk_rb[tk_rb_pos]; + tk.type=TK_NEWLINE; + tk.constant=p_spaces; + tk.line=line; + tk.col=column; + tk_rb_pos=(tk_rb_pos+1)%TK_RB_SIZE; +} + +void GDTokenizer::_advance() { + + if (error_flag) { + //parser broke + _make_error(last_error); + return; + } + + if (code_pos>=len) { + _make_token(TK_EOF); + return; + } +#define GETCHAR(m_ofs) ((m_ofs+code_pos)>=len?0:_code[m_ofs+code_pos]) +#define INCPOS(m_amount) { code_pos+=m_amount; column+=m_amount; } + while (true) { + + + bool is_node_path=false; + + switch(GETCHAR(0)) { + case 0: + _make_token(TK_EOF); + break; + case '\t': + case '\r': + case ' ': + INCPOS(1); + continue; + case '\n': { + line++; + INCPOS(1); + column=0; + int i=0; + while(GETCHAR(i)==' ' || GETCHAR(i)=='\t') { + i++; + } + + _make_newline(i); + return; + } +#if 1 //py style tokenizer + case '#': { // line comment skip + + while(GETCHAR(0)!='\n') { + code_pos++; + if (GETCHAR(0)==0) { //end of file + _make_error("Unterminated Comment"); + return; + } + } + INCPOS(1); + column=0; + line++; + int i=0; + while(GETCHAR(i)==' ' || GETCHAR(i)=='\t') { + i++; + } + _make_newline(i); + return; + + } break; +#endif + case '/': { + + switch(GETCHAR(1)) { +#if 0 // c style tokenizer + case '*': { // block comment + int pos = code_pos+2; + int new_line=line; + int new_col=column+2; + + while(true) { + if (_code[pos]=='0') { + _make_error("Unterminated Comment"); + code_pos=pos; + return; + } + if (_code[pos]=='*' && _code[pos+1]=='/') { + new_col+=2; + pos+=2; //compensate + break; + } else if (_code[pos]=='\n') { + new_line++; + new_col=0; + } else { + new_col++; + } + pos++; + } + + column=new_col; + line=new_line; + code_pos=pos; + continue; + + } break; + case '/': { // line comment skip + + while(GETCHAR(0)!='\n') { + code_pos++; + if (GETCHAR(0)==0) { //end of file + _make_error("Unterminated Comment"); + return; + } + } + INCPOS(1); + column=0; + line++; + continue; + + } break; +#endif + case '=': { // diveq + + _make_token(TK_OP_ASSIGN_DIV); + INCPOS(1); + + } break; + default: + _make_token(TK_OP_DIV); + + } + } break; + case '=': { + if (GETCHAR(1)=='=') { + _make_token(TK_OP_EQUAL); + INCPOS(1); + + } else + _make_token(TK_OP_ASSIGN); + + } break; + case '<': { + if (GETCHAR(1)=='=') { + + _make_token(TK_OP_LESS_EQUAL); + INCPOS(1); + } else if (GETCHAR(1)=='<') { + if (GETCHAR(2)=='=') { + _make_token(TK_OP_ASSIGN_SHIFT_LEFT); + INCPOS(1); + } else { + _make_token(TK_OP_SHIFT_LEFT); + } + INCPOS(1); + } else + _make_token(TK_OP_LESS); + + } break; + case '>': { + if (GETCHAR(1)=='=') { + _make_token(TK_OP_GREATER_EQUAL); + INCPOS(1); + } else if (GETCHAR(1)=='>') { + if (GETCHAR(2)=='=') { + _make_token(TK_OP_ASSIGN_SHIFT_RIGHT); + INCPOS(1); + + } else { + _make_token(TK_OP_SHIFT_RIGHT); + } + INCPOS(1); + } else { + _make_token(TK_OP_GREATER); + } + + } break; + case '!': { + if (GETCHAR(1)=='=') { + _make_token(TK_OP_NOT_EQUAL); + INCPOS(1); + } else { + _make_token(TK_OP_NOT); + } + + } break; + //case '"' //string - no strings in shader + //case '\'' //string - no strings in shader + case '{': + _make_token(TK_CURLY_BRACKET_OPEN); + break; + case '}': + _make_token(TK_CURLY_BRACKET_CLOSE); + break; + case '[': + _make_token(TK_BRACKET_OPEN); + break; + case ']': + _make_token(TK_BRACKET_CLOSE); + break; + case '(': + _make_token(TK_PARENTHESIS_OPEN); + break; + case ')': + _make_token(TK_PARENTHESIS_CLOSE); + break; + case ',': + _make_token(TK_COMMA); + break; + case ';': + _make_token(TK_SEMICOLON); + break; + case '?': + _make_token(TK_QUESTION_MARK); + break; + case ':': + _make_token(TK_COLON); //for methods maybe but now useless. + break; + case '^': { + if (GETCHAR(1)=='=') { + _make_token(TK_OP_ASSIGN_BIT_XOR); + INCPOS(1); + } else { + _make_token(TK_OP_BIT_XOR); + } + + } break; + case '~': + _make_token(TK_OP_BIT_INVERT); + break; + case '&': { + if (GETCHAR(1)=='&') { + + _make_token(TK_OP_AND); + INCPOS(1); + } else if (GETCHAR(1)=='=') { + _make_token(TK_OP_ASSIGN_BIT_AND); + INCPOS(1); + } else { + _make_token(TK_OP_BIT_AND); + } + } break; + case '|': { + if (GETCHAR(1)=='|') { + + _make_token(TK_OP_OR); + INCPOS(1); + } else if (GETCHAR(1)=='=') { + _make_token(TK_OP_ASSIGN_BIT_OR); + INCPOS(1); + } else { + _make_token(TK_OP_BIT_OR); + } + } break; + case '*': { + + if (GETCHAR(1)=='=') { + _make_token(TK_OP_ASSIGN_MUL); + INCPOS(1); + } else { + _make_token(TK_OP_MUL); + } + } break; + case '+': { + + if (GETCHAR(1)=='=') { + _make_token(TK_OP_ASSIGN_ADD); + INCPOS(1); + //} else if (GETCHAR(1)=='+') { + // _make_token(TK_OP_PLUS_PLUS); + // INCPOS(1); + } else { + _make_token(TK_OP_ADD); + } + + } break; + case '-': { + + if (GETCHAR(1)=='=') { + _make_token(TK_OP_ASSIGN_SUB); + INCPOS(1); + //} else if (GETCHAR(1)=='-') { + // _make_token(TK_OP_MINUS_MINUS); + // INCPOS(1); + } else { + _make_token(TK_OP_SUB); + } + } break; + case '%': { + + if (GETCHAR(1)=='=') { + _make_token(TK_OP_ASSIGN_MOD); + INCPOS(1); + } else { + _make_token(TK_OP_MOD); + } + } break; + case '@': + if (CharType(GETCHAR(1))!='"') { + _make_error("Unexpected '@'"); + return; + } + INCPOS(1); + is_node_path=true; + case '"': { + + int i=1; + String str; + while(true) { + if (CharType(GETCHAR(i)==0)) { + + _make_error("Unterminated String"); + return; + } else if (CharType(GETCHAR(i)=='"')) { + break; + } else if (CharType(GETCHAR(i)=='\\')) { + //escaped characters... + i++; + CharType next = GETCHAR(i); + if (next==0) { + _make_error("Unterminated String"); + return; + } + CharType res=0; + + switch(next) { + + case 'a': res=7; break; + case 'b': res=8; break; + case 't': res=9; break; + case 'n': res=10; break; + case 'v': res=11; break; + case 'f': res=12; break; + case 'r': res=13; break; + case '\'': res='\''; break; + case '\"': res='\"'; break; + case '\\': res='\\'; break; + case 'x': { + //hexnumbarh - oct is deprecated + + int read=0; + for(int j=0;j<4;j++) { + CharType c = GETCHAR(i+j); + if (c==0) { + _make_error("Unterminated String"); + return; + } + if (!_is_hex(c)) { + if (j==0 || !(j&1)) { + _make_error("Malformed hex constant in string"); + return; + } else + break; + } + CharType v; + if (c>='0' && c<='9') { + v=c-'0'; + } else if (c>='a' && c<='f') { + v=c-'a'; + v+=10; + } else if (c>='A' && c<='F') { + v=c-'A'; + v+=10; + } else { + ERR_PRINT("BUG"); + v=0; + } + + res<<=4; + res|=v; + + read++; + } + i+=read-1; + + + } break; + default: { + + _make_error("Invalid escape sequence"); + return; + } break; + } + + str+=res; + + } else { + str+=CharType(GETCHAR(i)); + } + i++; + } + INCPOS(i); + + if (is_node_path) { + _make_constant(NodePath(str)); + } else { + _make_constant(str); + } + + } break; + default: { + + if (_is_number(GETCHAR(0)) || (GETCHAR(0)=='.' && _is_number(GETCHAR(1)))) { + // parse number + bool period_found=false; + bool exponent_found=false; + bool hexa_found=false; + bool sign_found=false; + + String str; + int i=0; + + while(true) { + if (GETCHAR(i)=='.') { + if (period_found || exponent_found) { + _make_error("Invalid numeric constant at '.'"); + return; + } + period_found=true; + } else if (GETCHAR(i)=='x') { + if (hexa_found || str.length()!=1 || !( (i==1 && str[0]=='0') || (i==2 && str[1]=='0' && str[0]=='-') ) ) { + _make_error("Invalid numeric constant at 'x'"); + return; + } + hexa_found=true; + } else if (!hexa_found && GETCHAR(i)=='e') { + if (hexa_found || exponent_found) { + _make_error("Invalid numeric constant at 'e'"); + return; + } + exponent_found=true; + } else if (_is_number(GETCHAR(i))) { + //all ok + } else if (hexa_found && _is_hex(GETCHAR(i))) { + + } else if ((GETCHAR(i)=='-' || GETCHAR(i)=='+') && exponent_found) { + if (sign_found) { + _make_error("Invalid numeric constant at '-'"); + return; + } + sign_found=true; + } else + break; + + str+=CharType(GETCHAR(i)); + i++; + } + + if (!( _is_number(str[str.length()-1]) || (hexa_found && _is_hex(str[str.length()-1])))) { + _make_error("Invalid numeric constant: "+str); + return; + } + + INCPOS(str.length()); + if (hexa_found) { + int val = str.hex_to_int(); + _make_constant(val); + } else if (period_found) { + real_t val = str.to_double(); + //print_line("*%*%*%*% to convert: "+str+" result: "+rtos(val)); + _make_constant(val); + } else { + int val = str.to_int(); + _make_constant(val); + + } + + return; + } + + if (GETCHAR(0)=='.') { + //parse period + _make_token(TK_PERIOD); + break; + } + + if (_is_text_char(GETCHAR(0))) { + // parse identifier + String str; + str+=CharType(GETCHAR(0)); + + int i=1; + while(_is_text_char(GETCHAR(i))) { + str+=CharType(GETCHAR(i)); + i++; + } + + bool identifier=false; + + if (str=="null") { + _make_constant(Variant()); + + } else if (str=="true") { + _make_constant(true); + + } else if (str=="false") { + _make_constant(false); + } else { + + bool found=false; + + struct _bit { Variant::Type type; const char *text;}; + //built in types + + static const _bit type_list[]={ + //types + {Variant::BOOL,"bool"}, + {Variant::INT,"int"}, + {Variant::REAL,"float"}, + {Variant::STRING,"String"}, + {Variant::VECTOR2,"vec2"}, + {Variant::VECTOR2,"Vector2"}, + {Variant::RECT2,"Rect2"}, + {Variant::MATRIX32,"Matrix32"}, + {Variant::MATRIX32,"mat32"}, + {Variant::VECTOR3,"vec3"}, + {Variant::VECTOR3,"Vector3"}, + {Variant::_AABB,"AABB"}, + {Variant::_AABB,"Rect3"}, + {Variant::PLANE,"Plane"}, + {Variant::QUAT,"Quat"}, + {Variant::MATRIX3,"mat3"}, + {Variant::MATRIX3,"Matrix3"}, + {Variant::TRANSFORM,"trn"}, + {Variant::TRANSFORM,"Transform"}, + {Variant::COLOR,"Color"}, + {Variant::IMAGE,"Image"}, + {Variant::_RID,"RID"}, + {Variant::OBJECT,"Object"}, + {Variant::INPUT_EVENT,"InputEvent"}, + {Variant::DICTIONARY,"dict"}, + {Variant::DICTIONARY,"Dictionary"}, + {Variant::ARRAY,"Array"}, + {Variant::RAW_ARRAY,"RawArray"}, + {Variant::INT_ARRAY,"IntArray"}, + {Variant::REAL_ARRAY,"FloatArray"}, + {Variant::STRING_ARRAY,"StringArray"}, + {Variant::VECTOR2_ARRAY,"Vector2Array"}, + {Variant::VECTOR3_ARRAY,"Vector3Array"}, + {Variant::COLOR_ARRAY,"ColorArray"}, + {Variant::VARIANT_MAX,NULL}, + }; + + { + + + int idx=0; + + while(type_list[idx].text) { + + if (str==type_list[idx].text) { + _make_type(type_list[idx].type); + found=true; + break; + } + idx++; + } + } + + if (!found) { + + //built in func? + + for(int i=0;i<GDFunctions::FUNC_MAX;i++) { + + if (str==GDFunctions::get_func_name(GDFunctions::Function(i))) { + + _make_built_in_func(GDFunctions::Function(i)); + found=true; + break; + } + } + + //keywor + } + + if (!found) { + + + struct _kws { Token token; const char *text;}; + + static const _kws keyword_list[]={ + //ops + {TK_OP_IN,"in"}, + {TK_OP_NOT,"not"}, + {TK_OP_OR,"or"}, + {TK_OP_AND,"and"}, + //func + {TK_PR_FUNCTION,"func"}, + {TK_PR_FUNCTION,"function"}, + {TK_PR_CLASS,"class"}, + {TK_PR_EXTENDS,"extends"}, + {TK_PR_TOOL,"tool"}, + {TK_PR_STATIC,"static"}, + {TK_PR_EXPORT,"export"}, + {TK_PR_VAR,"var"}, + {TK_PR_PRELOAD,"preload"}, + {TK_PR_ASSERT,"assert"}, + {TK_PR_CONST,"const"}, + //controlflow + {TK_CF_IF,"if"}, + {TK_CF_ELIF,"elif"}, + {TK_CF_ELSE,"else"}, + {TK_CF_FOR,"for"}, + {TK_CF_WHILE,"while"}, + {TK_CF_DO,"do"}, + {TK_CF_SWITCH,"switch"}, + {TK_CF_BREAK,"break"}, + {TK_CF_CONTINUE,"continue"}, + {TK_CF_RETURN,"return"}, + {TK_CF_PASS,"pass"}, + {TK_SELF,"self"}, + {TK_ERROR,NULL} + }; + + int idx=0; + found=false; + + while(keyword_list[idx].text) { + + if (str==keyword_list[idx].text) { + _make_token(keyword_list[idx].token); + found=true; + break; + } + idx++; + } + } + + if (!found) + identifier=true; + } + + + if (identifier) { + _make_identifier(str); + } + INCPOS(str.length()); + return; + } + + _make_error("Unknown character"); + return; + + } break; + } + + INCPOS(1); + break; + } + +} + +void GDTokenizer::set_code(const String& p_code) { + + code=p_code; + len = p_code.length(); + if (len) { + _code=&code[0]; + } else { + _code=NULL; + } + code_pos=0; + line=1; //it is stand-ar-ized that lines begin in 1 in code.. + column=0; + tk_rb_pos=0; + error_flag=false; + last_error=""; + for(int i=0;i<MAX_LOOKAHEAD+1;i++) + _advance(); +} + +GDTokenizer::Token GDTokenizer::get_token(int p_offset) const { + ERR_FAIL_COND_V( p_offset <= -MAX_LOOKAHEAD, TK_ERROR); + ERR_FAIL_COND_V( p_offset >= MAX_LOOKAHEAD, TK_ERROR); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD -1)%TK_RB_SIZE; + return tk_rb[ofs].type; +} + +int GDTokenizer::get_token_line(int p_offset) const { + ERR_FAIL_COND_V( p_offset <= -MAX_LOOKAHEAD, -1); + ERR_FAIL_COND_V( p_offset >= MAX_LOOKAHEAD, -1); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD -1)%TK_RB_SIZE; + return tk_rb[ofs].line; +} + +int GDTokenizer::get_token_column(int p_offset) const { + ERR_FAIL_COND_V( p_offset <= -MAX_LOOKAHEAD, -1); + ERR_FAIL_COND_V( p_offset >= MAX_LOOKAHEAD, -1); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD -1)%TK_RB_SIZE; + return tk_rb[ofs].col; +} + +const Variant& GDTokenizer::get_token_constant(int p_offset) const { + ERR_FAIL_COND_V( p_offset <= -MAX_LOOKAHEAD, tk_rb[0].constant); + ERR_FAIL_COND_V( p_offset >= MAX_LOOKAHEAD, tk_rb[0].constant); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD -1)%TK_RB_SIZE; + ERR_FAIL_COND_V(tk_rb[ofs].type!=TK_CONSTANT,tk_rb[0].constant); + return tk_rb[ofs].constant; +} +StringName GDTokenizer::get_token_identifier(int p_offset) const { + + ERR_FAIL_COND_V( p_offset <= -MAX_LOOKAHEAD, StringName()); + ERR_FAIL_COND_V( p_offset >= MAX_LOOKAHEAD, StringName()); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD -1)%TK_RB_SIZE; + ERR_FAIL_COND_V(tk_rb[ofs].type!=TK_IDENTIFIER,StringName()); + return tk_rb[ofs].identifier; + +} + +GDFunctions::Function GDTokenizer::get_token_built_in_func(int p_offset) const { + + ERR_FAIL_COND_V( p_offset <= -MAX_LOOKAHEAD, GDFunctions::FUNC_MAX); + ERR_FAIL_COND_V( p_offset >= MAX_LOOKAHEAD, GDFunctions::FUNC_MAX); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD -1)%TK_RB_SIZE; + ERR_FAIL_COND_V(tk_rb[ofs].type!=TK_BUILT_IN_FUNC,GDFunctions::FUNC_MAX); + return tk_rb[ofs].func; + +} + +Variant::Type GDTokenizer::get_token_type(int p_offset) const { + + ERR_FAIL_COND_V( p_offset <= -MAX_LOOKAHEAD, Variant::NIL); + ERR_FAIL_COND_V( p_offset >= MAX_LOOKAHEAD, Variant::NIL); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD -1)%TK_RB_SIZE; + ERR_FAIL_COND_V(tk_rb[ofs].type!=TK_BUILT_IN_TYPE,Variant::NIL); + return tk_rb[ofs].vtype; + +} + + +int GDTokenizer::get_token_line_indent(int p_offset) const { + + ERR_FAIL_COND_V( p_offset <= -MAX_LOOKAHEAD, 0); + ERR_FAIL_COND_V( p_offset >= MAX_LOOKAHEAD, 0); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD -1)%TK_RB_SIZE; + ERR_FAIL_COND_V(tk_rb[ofs].type!=TK_NEWLINE,0); + return tk_rb[ofs].constant; + +} + +String GDTokenizer::get_token_error(int p_offset) const { + + ERR_FAIL_COND_V( p_offset <= -MAX_LOOKAHEAD, String()); + ERR_FAIL_COND_V( p_offset >= MAX_LOOKAHEAD, String()); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD -1)%TK_RB_SIZE; + ERR_FAIL_COND_V(tk_rb[ofs].type!=TK_ERROR,String()); + return tk_rb[ofs].constant; +} + +void GDTokenizer::advance(int p_amount) { + + ERR_FAIL_COND( p_amount <=0 ); + for(int i=0;i<p_amount;i++) + _advance(); +} diff --git a/modules/gdscript/gd_tokenizer.h b/modules/gdscript/gd_tokenizer.h new file mode 100644 index 0000000000..24ee2be7ad --- /dev/null +++ b/modules/gdscript/gd_tokenizer.h @@ -0,0 +1,181 @@ +/*************************************************************************/ +/* gd_tokenizer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +#ifndef GD_TOKENIZER_H +#define GD_TOKENIZER_H + +#include "ustring.h" +#include "variant.h" +#include "string_db.h" +#include "gd_functions.h" +class GDTokenizer { +public: + + enum Token { + + TK_EMPTY, + TK_IDENTIFIER, + TK_CONSTANT, + TK_SELF, + TK_BUILT_IN_TYPE, + TK_BUILT_IN_FUNC, + TK_OP_IN, + TK_OP_EQUAL, + TK_OP_NOT_EQUAL, + TK_OP_LESS, + TK_OP_LESS_EQUAL, + TK_OP_GREATER, + TK_OP_GREATER_EQUAL, + TK_OP_AND, + TK_OP_OR, + TK_OP_NOT, + TK_OP_ADD, + TK_OP_SUB, + TK_OP_MUL, + TK_OP_DIV, + TK_OP_MOD, + TK_OP_SHIFT_LEFT, + TK_OP_SHIFT_RIGHT, + TK_OP_ASSIGN, + TK_OP_ASSIGN_ADD, + TK_OP_ASSIGN_SUB, + TK_OP_ASSIGN_MUL, + TK_OP_ASSIGN_DIV, + TK_OP_ASSIGN_MOD, + TK_OP_ASSIGN_SHIFT_LEFT, + TK_OP_ASSIGN_SHIFT_RIGHT, + TK_OP_ASSIGN_BIT_AND, + TK_OP_ASSIGN_BIT_OR, + TK_OP_ASSIGN_BIT_XOR, + TK_OP_BIT_AND, + TK_OP_BIT_OR, + TK_OP_BIT_XOR, + TK_OP_BIT_INVERT, + //TK_OP_PLUS_PLUS, + //TK_OP_MINUS_MINUS, + TK_CF_IF, + TK_CF_ELIF, + TK_CF_ELSE, + TK_CF_FOR, + TK_CF_DO, + TK_CF_WHILE, + TK_CF_SWITCH, + TK_CF_CASE, + TK_CF_BREAK, + TK_CF_CONTINUE, + TK_CF_PASS, + TK_CF_RETURN, + TK_PR_FUNCTION, + TK_PR_CLASS, + TK_PR_EXTENDS, + TK_PR_TOOL, + TK_PR_STATIC, + TK_PR_EXPORT, + TK_PR_CONST, + TK_PR_VAR, + TK_PR_PRELOAD, + TK_PR_ASSERT, + TK_BRACKET_OPEN, + TK_BRACKET_CLOSE, + TK_CURLY_BRACKET_OPEN, + TK_CURLY_BRACKET_CLOSE, + TK_PARENTHESIS_OPEN, + TK_PARENTHESIS_CLOSE, + TK_COMMA, + TK_SEMICOLON, + TK_PERIOD, + TK_QUESTION_MARK, + TK_COLON, + TK_NEWLINE, + TK_ERROR, + TK_EOF, + TK_MAX + }; + + + +private: + + static const char* token_names[TK_MAX]; + enum { + MAX_LOOKAHEAD=4, + TK_RB_SIZE=MAX_LOOKAHEAD*2+1 + + }; + + struct TokenData { + Token type; + StringName identifier; //for identifier types + Variant constant; //for constant types + union { + Variant::Type vtype; //for type types + GDFunctions::Function func; //function for built in functions + }; + int line,col; + TokenData() { type = TK_EMPTY; line=col=0; vtype=Variant::NIL; } + }; + + void _make_token(Token p_type); + void _make_newline(int p_spaces=0); + void _make_identifier(const StringName& p_identifier); + void _make_built_in_func(GDFunctions::Function p_func); + void _make_constant(const Variant& p_constant); + void _make_type(const Variant::Type& p_type); + void _make_error(const String& p_error); + + String code; + int len; + int code_pos; + const CharType *_code; + int line; + int column; + TokenData tk_rb[TK_RB_SIZE*2+1]; + int tk_rb_pos; + String last_error; + bool error_flag; + + void _advance(); +public: + + static const char *get_token_name(Token p_token); + + void set_code(const String& p_code); + Token get_token(int p_offset=0) const; + const Variant& get_token_constant(int p_offset=0) const; + StringName get_token_identifier(int p_offset=0) const; + GDFunctions::Function get_token_built_in_func(int p_offset=0) const; + Variant::Type get_token_type(int p_offset=0) const; + int get_token_line(int p_offset=0) const; + int get_token_column(int p_offset=0) const; + int get_token_line_indent(int p_offset=0) const; + + String get_token_error(int p_offset=0) const; + void advance(int p_amount=1); +}; + +#endif // TOKENIZER_H diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp new file mode 100644 index 0000000000..d2d7bf426a --- /dev/null +++ b/modules/gdscript/register_types.cpp @@ -0,0 +1,46 @@ +/*************************************************/ +/* register_script_types.cpp */ +/*************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/*************************************************/ +/* Source code within this file is: */ +/* (c) 2007-2010 Juan Linietsky, Ariel Manzur */ +/* All Rights Reserved. */ +/*************************************************/ + +#include "register_types.h" + +#include "gd_script.h" +#include "io/resource_loader.h" + +GDScriptLanguage *script_language_gd=NULL; +ResourceFormatLoaderGDScript *resource_loader_gd=NULL; +ResourceFormatSaverGDScript *resource_saver_gd=NULL; + +void register_gdscript_types() { + + + script_language_gd=memnew( GDScriptLanguage ); + script_language_gd->init(); + ScriptServer::register_language(script_language_gd); + ObjectTypeDB::register_type<GDScript>(); + resource_loader_gd=memnew( ResourceFormatLoaderGDScript ); + ResourceLoader::add_resource_format_loader(resource_loader_gd); + resource_saver_gd=memnew( ResourceFormatSaverGDScript ); + ResourceSaver::add_resource_format_saver(resource_saver_gd); + +} +void unregister_gdscript_types() { + + + + + if (script_language_gd) + memdelete( script_language_gd ); + if (resource_loader_gd) + memdelete( resource_loader_gd ); + if (resource_saver_gd) + memdelete( resource_saver_gd ); + +}
\ No newline at end of file diff --git a/modules/gdscript/register_types.h b/modules/gdscript/register_types.h new file mode 100644 index 0000000000..ff7c2734df --- /dev/null +++ b/modules/gdscript/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +void register_gdscript_types(); +void unregister_gdscript_types(); diff --git a/modules/multiscript/SCsub b/modules/multiscript/SCsub new file mode 100644 index 0000000000..d20da72b72 --- /dev/null +++ b/modules/multiscript/SCsub @@ -0,0 +1,7 @@ +Import('env') + +env.add_source_files(env.modules_sources,"*.cpp") + +Export('env') + + diff --git a/modules/multiscript/config.py b/modules/multiscript/config.py new file mode 100644 index 0000000000..f9bd7da08d --- /dev/null +++ b/modules/multiscript/config.py @@ -0,0 +1,11 @@ + + +def can_build(platform): + return True + + +def configure(env): + pass + + + diff --git a/modules/multiscript/multi_script.cpp b/modules/multiscript/multi_script.cpp new file mode 100644 index 0000000000..1924cf2a6e --- /dev/null +++ b/modules/multiscript/multi_script.cpp @@ -0,0 +1,498 @@ +/*************************************************************************/ +/* multi_script.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "multi_script.h" + +bool MultiScriptInstance::set(const StringName& p_name, const Variant& p_value) { + + ScriptInstance **sarr = instances.ptr(); + int sc = instances.size(); + + for(int i=0;i<sc;i++) { + + if (!sarr[i]) + continue; + + bool found = sarr[i]->set(p_name,p_value); + if (found) + return true; + } + + if (String(p_name).begins_with("script_")) { + bool valid; + owner->set(p_name,p_value,&valid); + return valid; + } + return false; + +} + +bool MultiScriptInstance::get(const StringName& p_name, Variant &r_ret) const{ + + ScriptInstance **sarr = instances.ptr(); + int sc = instances.size(); + + for(int i=0;i<sc;i++) { + + if (!sarr[i]) + continue; + + bool found = sarr[i]->get(p_name,r_ret); + if (found) + return true; + } + if (String(p_name).begins_with("script_")) { + bool valid; + r_ret=owner->get(p_name,&valid); + return valid; + } + return false; + +} +void MultiScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const{ + + ScriptInstance **sarr = instances.ptr(); + int sc = instances.size(); + + + Set<String> existing; + + for(int i=0;i<sc;i++) { + + if (!sarr[i]) + continue; + + List<PropertyInfo> pl; + sarr[i]->get_property_list(&pl); + + for(List<PropertyInfo>::Element *E=pl.front();E;E=E->next()) { + + if (existing.has(E->get().name)) + continue; + + p_properties->push_back(E->get()); + existing.insert(E->get().name); + } + } + + p_properties->push_back( PropertyInfo(Variant::NIL,"Scripts",PROPERTY_HINT_NONE,String(),PROPERTY_USAGE_CATEGORY) ); + + for(int i=0;i<owner->scripts.size();i++) { + + p_properties->push_back( PropertyInfo(Variant::OBJECT,"script_"+String::chr('a'+i),PROPERTY_HINT_RESOURCE_TYPE,"Script",PROPERTY_USAGE_EDITOR) ); + + } + + if (owner->scripts.size()<25) { + + p_properties->push_back( PropertyInfo(Variant::OBJECT,"script_"+String::chr('a'+(owner->scripts.size())),PROPERTY_HINT_RESOURCE_TYPE,"Script",PROPERTY_USAGE_EDITOR) ); + } + +} + +void MultiScriptInstance::get_method_list(List<MethodInfo> *p_list) const{ + + ScriptInstance **sarr = instances.ptr(); + int sc = instances.size(); + + + Set<StringName> existing; + + for(int i=0;i<sc;i++) { + + if (!sarr[i]) + continue; + + List<MethodInfo> ml; + sarr[i]->get_method_list(&ml); + + for(List<MethodInfo>::Element *E=ml.front();E;E=E->next()) { + + if (existing.has(E->get().name)) + continue; + + p_list->push_back(E->get()); + existing.insert(E->get().name); + } + } + +} +bool MultiScriptInstance::has_method(const StringName& p_method) const{ + + ScriptInstance **sarr = instances.ptr(); + int sc = instances.size(); + + for(int i=0;i<sc;i++) { + + if (!sarr[i]) + continue; + + if (sarr[i]->has_method(p_method)) + return true; + } + + return false; + +} + +Variant MultiScriptInstance::call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) { + + ScriptInstance **sarr = instances.ptr(); + int sc = instances.size(); + + for(int i=0;i<sc;i++) { + + if (!sarr[i]) + continue; + + Variant r = sarr[i]->call(p_method,p_args,p_argcount,r_error); + if (r_error.error==Variant::CallError::CALL_OK) + return r; + else if (r_error.error!=Variant::CallError::CALL_ERROR_INVALID_METHOD) + return r; + } + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return Variant(); + +} + +void MultiScriptInstance::call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount){ + + ScriptInstance **sarr = instances.ptr(); + int sc = instances.size(); + + for(int i=0;i<sc;i++) { + + if (!sarr[i]) + continue; + + sarr[i]->call_multilevel(p_method,p_args,p_argcount); + } + + +} +void MultiScriptInstance::notification(int p_notification){ + + ScriptInstance **sarr = instances.ptr(); + int sc = instances.size(); + + for(int i=0;i<sc;i++) { + + if (!sarr[i]) + continue; + + sarr[i]->notification(p_notification); + } + +} + + +Ref<Script> MultiScriptInstance::get_script() const { + + return owner; +} + +ScriptLanguage *MultiScriptInstance::get_language() { + + return MultiScriptLanguage::get_singleton(); +} + +MultiScriptInstance::~MultiScriptInstance() { + + owner->remove_instance(object); +} + + +/////////////////// + + +bool MultiScript::is_tool() const { + + for(int i=0;i<scripts.size();i++) { + + if (scripts[i]->is_tool()) + return true; + } + + return false; +} + +bool MultiScript::_set(const StringName& p_name, const Variant& p_value) { + + _THREAD_SAFE_METHOD_ + + String s = String(p_name); + if (s.begins_with("script_")) { + + int idx = s[7]; + if (idx==0) + return false; + idx-='a'; + + ERR_FAIL_COND_V(idx<0,false); + + Ref<Script> s = p_value; + + if (idx<scripts.size()) { + + + if (s.is_null()) + remove_script(idx); + else + set_script(idx,s); + } else if (idx==scripts.size()) { + if (s.is_null()) + return false; + add_script(s); + } else + return false; + + return true; + } + + return false; +} + +bool MultiScript::_get(const StringName& p_name,Variant &r_ret) const{ + + _THREAD_SAFE_METHOD_ + + String s = String(p_name); + if (s.begins_with("script_")) { + + int idx = s[7]; + if (idx==0) + return false; + idx-='a'; + + ERR_FAIL_COND_V(idx<0,false); + + if (idx<scripts.size()) { + + r_ret=get_script(idx); + return true; + } else if (idx==scripts.size()) { + r_ret=Ref<Script>(); + return true; + } + } + + return false; +} +void MultiScript::_get_property_list( List<PropertyInfo> *p_list) const{ + + _THREAD_SAFE_METHOD_ + + for(int i=0;i<scripts.size();i++) { + + p_list->push_back( PropertyInfo(Variant::OBJECT,"script_"+String::chr('a'+i),PROPERTY_HINT_RESOURCE_TYPE,"Script") ); + + } + + if (scripts.size()<25) { + + p_list->push_back( PropertyInfo(Variant::OBJECT,"script_"+String::chr('a'+(scripts.size())),PROPERTY_HINT_RESOURCE_TYPE,"Script") ); + } +} + +void MultiScript::set_script(int p_idx,const Ref<Script>& p_script ) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_INDEX(p_idx,scripts.size()); + ERR_FAIL_COND( p_script.is_null() ); + + scripts[p_idx]=p_script; + Ref<Script> s=p_script; + + for (Map<Object*,MultiScriptInstance*>::Element *E=instances.front();E;E=E->next()) { + + + MultiScriptInstance*msi=E->get(); + ScriptInstance *si = msi->instances[p_idx]; + if (si) { + msi->instances[p_idx]=NULL; + memdelete(si); + } + + if (p_script->can_instance()) + msi->instances[p_idx]=s->instance_create(msi->object); + + } + + +} + + +Ref<Script> MultiScript::get_script(int p_idx) const{ + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_INDEX_V(p_idx,scripts.size(),Ref<Script>()); + + return scripts[p_idx]; + +} +void MultiScript::add_script(const Ref<Script>& p_script){ + + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND( p_script.is_null() ); + scripts.push_back(p_script); + Ref<Script> s=p_script; + + for (Map<Object*,MultiScriptInstance*>::Element *E=instances.front();E;E=E->next()) { + + + MultiScriptInstance*msi=E->get(); + + if (p_script->can_instance()) + msi->instances.push_back( s->instance_create(msi->object) ); + else + msi->instances.push_back(NULL); + + msi->object->_change_notify(); + + } + + + _change_notify(); +} + + +void MultiScript::remove_script(int p_idx) { + + _THREAD_SAFE_METHOD_ + + ERR_FAIL_INDEX(p_idx,scripts.size()); + + scripts.remove(p_idx); + + for (Map<Object*,MultiScriptInstance*>::Element *E=instances.front();E;E=E->next()) { + + + MultiScriptInstance*msi=E->get(); + ScriptInstance *si = msi->instances[p_idx]; + msi->instances.remove(p_idx); + if (si) { + memdelete(si); + } + + msi->object->_change_notify(); + } + + +} + + +void MultiScript::remove_instance(Object *p_object) { + + _THREAD_SAFE_METHOD_ + instances.erase(p_object); +} + +bool MultiScript::can_instance() const { + + return true; +} + +StringName MultiScript::get_instance_base_type() const { + + return StringName(); +} +ScriptInstance* MultiScript::instance_create(Object *p_this) { + + _THREAD_SAFE_METHOD_ + MultiScriptInstance *msi = memnew( MultiScriptInstance ); + msi->object=p_this; + msi->owner=this; + for(int i=0;i<scripts.size();i++) { + + ScriptInstance *si; + + if (scripts[i]->can_instance()) + si = scripts[i]->instance_create(p_this); + else + si=NULL; + + msi->instances.push_back(si); + } + + instances[p_this]=msi; + p_this->_change_notify(); + return msi; +} +bool MultiScript::instance_has(const Object *p_this) const { + + _THREAD_SAFE_METHOD_ + return instances.has((Object*)p_this); +} + +bool MultiScript::has_source_code() const { + + return false; +} +String MultiScript::get_source_code() const { + + return ""; +} +void MultiScript::set_source_code(const String& p_code) { + + +} +Error MultiScript::reload() { + + for(int i=0;i<scripts.size();i++) + scripts[i]->reload(); + + return OK; +} + +String MultiScript::get_node_type() const { + + return ""; +} + +void MultiScript::_bind_methods() { + + +} + +ScriptLanguage *MultiScript::get_language() const { + + return MultiScriptLanguage::get_singleton(); +} + + +/////////////// + +MultiScript::MultiScript() { +} + + +MultiScriptLanguage *MultiScriptLanguage::singleton=NULL; diff --git a/modules/multiscript/multi_script.h b/modules/multiscript/multi_script.h new file mode 100644 index 0000000000..87d4b4e4c8 --- /dev/null +++ b/modules/multiscript/multi_script.h @@ -0,0 +1,158 @@ +/*************************************************************************/ +/* multi_script.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +#ifndef MULTI_SCRIPT_H +#define MULTI_SCRIPT_H + +#include "script_language.h" +#include "os/thread_safe.h" + +class MultiScript; + +class MultiScriptInstance : public ScriptInstance { +friend class MultiScript; + mutable Vector<ScriptInstance*> instances; + Object *object; + mutable MultiScript *owner; + +public: + virtual bool set(const StringName& p_name, const Variant& p_value); + virtual bool get(const StringName& p_name, Variant &r_ret) const; + virtual void get_property_list(List<PropertyInfo> *p_properties) const; + + virtual void get_method_list(List<MethodInfo> *p_list) const; + virtual bool has_method(const StringName& p_method) const; + virtual Variant call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error); + virtual void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount); + virtual void notification(int p_notification); + + + virtual Ref<Script> get_script() const; + + virtual ScriptLanguage *get_language(); + virtual ~MultiScriptInstance(); +}; + + +class MultiScript : public Script { + + _THREAD_SAFE_CLASS_ +friend class MultiScriptInstance; + OBJ_TYPE( MultiScript,Script); + + Vector<Ref<Script> > scripts; + + Map<Object*,MultiScriptInstance*> instances; +protected: + + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name,Variant &r_ret) const; + void _get_property_list( List<PropertyInfo> *p_list) const; + + static void _bind_methods(); + +public: + + void remove_instance(Object *p_object); + virtual bool can_instance() const; + + virtual StringName get_instance_base_type() const; + virtual ScriptInstance* instance_create(Object *p_this); + virtual bool instance_has(const Object *p_this) const; + + virtual bool has_source_code() const; + virtual String get_source_code() const; + virtual void set_source_code(const String& p_code); + virtual Error reload(); + + virtual bool is_tool() const; + + virtual String get_node_type() const; + + + void set_script(int p_idx,const Ref<Script>& p_script ); + Ref<Script> get_script(int p_idx) const; + void remove_script(int p_idx); + void add_script(const Ref<Script>& p_script); + + virtual ScriptLanguage *get_language() const; + + MultiScript(); +}; + + +class MultiScriptLanguage : public ScriptLanguage { + + static MultiScriptLanguage *singleton; +public: + + static _FORCE_INLINE_ MultiScriptLanguage *get_singleton() { return singleton; } + virtual String get_name() const { return "MultiScript"; } + + /* LANGUAGE FUNCTIONS */ + virtual void init() {} + virtual String get_type() const { return "MultiScript"; } + virtual String get_extension() const { return ""; } + virtual Error execute_file(const String& p_path) { return OK; } + virtual void finish() {} + + /* EDITOR FUNCTIONS */ + virtual void get_reserved_words(List<String> *p_words) const {} + virtual void get_comment_delimiters(List<String> *p_delimiters) const {} + virtual void get_string_delimiters(List<String> *p_delimiters) const {} + virtual String get_template(const String& p_class_name, const String& p_base_class_name) const { return ""; } + virtual bool validate(const String& p_script, int &r_line_error,int &r_col_error,String& r_test_error,const String& p_path="",List<String>* r_fn=NULL) const { return true; } + virtual Script *create_script() const { return memnew( MultiScript ); } + virtual bool has_named_classes() const { return false; } + virtual int find_function(const String& p_function,const String& p_code) const { return -1; } + virtual String make_function(const String& p_class,const String& p_name,const StringArray& p_args) const { return ""; } + + /* DEBUGGER FUNCTIONS */ + + virtual String debug_get_error() const { return ""; } + virtual int debug_get_stack_level_count() const { return 0; } + virtual int debug_get_stack_level_line(int p_level) const { return 0; } + virtual String debug_get_stack_level_function(int p_level) const { return ""; } + virtual String debug_get_stack_level_source(int p_level) const { return ""; } + virtual void debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1) {} + virtual void debug_get_stack_level_members(int p_level,List<String> *p_members, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1) {} + virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1) {} + virtual String debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems=-1,int p_max_depth=-1) { return ""; } + + /* LOADER FUNCTIONS */ + + virtual void get_recognized_extensions(List<String> *p_extensions) const {} + virtual void get_public_functions(List<MethodInfo> *p_functions) const {} + virtual void get_public_constants(List<Pair<String,Variant> > *p_constants) const {} + + MultiScriptLanguage() { singleton=this; } + virtual ~MultiScriptLanguage() {}; +}; + + +#endif // MULTI_SCRIPT_H diff --git a/modules/multiscript/register_types.cpp b/modules/multiscript/register_types.cpp new file mode 100644 index 0000000000..4b25656723 --- /dev/null +++ b/modules/multiscript/register_types.cpp @@ -0,0 +1,32 @@ +/*************************************************/ +/* register_script_types.cpp */ +/*************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/*************************************************/ +/* Source code within this file is: */ +/* (c) 2007-2010 Juan Linietsky, Ariel Manzur */ +/* All Rights Reserved. */ +/*************************************************/ + +#include "register_types.h" + +#include "multi_script.h" +#include "io/resource_loader.h" + +static MultiScriptLanguage *script_multi_script=NULL; + +void register_multiscript_types() { + + + script_multi_script = memnew( MultiScriptLanguage ); + ScriptServer::register_language(script_multi_script); + ObjectTypeDB::register_type<MultiScript>(); + + +} +void unregister_multiscript_types() { + + if (script_multi_script); + memdelete(script_multi_script); +} diff --git a/modules/multiscript/register_types.h b/modules/multiscript/register_types.h new file mode 100644 index 0000000000..d137b1c63f --- /dev/null +++ b/modules/multiscript/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +void register_multiscript_types(); +void unregister_multiscript_types(); |