Ternary operator in GDScript (a if x else b)

Fixes #1961
This commit is contained in:
Bojidar Marinov 2016-08-25 21:18:35 +03:00
parent 41a58f7935
commit 9f66f59477
No known key found for this signature in database
GPG key ID: 4D546A8F1E091856
3 changed files with 125 additions and 14 deletions

View file

@ -661,6 +661,46 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre
codegen.opcodes.push_back(p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); 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; return p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS;
} break;
// ternary operators
case GDParser::OperatorNode::OP_TERNARY_IF: {
// x IF a ELSE y 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.alloc_stack(p_stack_level); //it will be used..
codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN);
codegen.opcodes.push_back(p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS);
codegen.opcodes.push_back(res);
codegen.opcodes.push_back(GDFunction::OPCODE_JUMP);
int jump_past_pos=codegen.opcodes.size();
codegen.opcodes.push_back(0);
codegen.opcodes[jump_fail_pos]=codegen.opcodes.size();
res = _parse_expression(codegen,on->arguments[2],p_stack_level);
if (res<0)
return res;
codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN);
codegen.opcodes.push_back(p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS);
codegen.opcodes.push_back(res);
codegen.opcodes[jump_past_pos]=codegen.opcodes.size();
return p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS;
} break; } break;
//unary operators //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_NEG: { if (!_create_unary_operator(codegen,on,Variant::OP_NEGATE,p_stack_level)) return -1;} break;

View file

@ -936,6 +936,8 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
case GDTokenizer::TK_OP_BIT_OR: op=OperatorNode::OP_BIT_OR ; 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_OP_BIT_XOR: op=OperatorNode::OP_BIT_XOR ; break;
case GDTokenizer::TK_PR_EXTENDS: op=OperatorNode::OP_EXTENDS; break; case GDTokenizer::TK_PR_EXTENDS: op=OperatorNode::OP_EXTENDS; break;
case GDTokenizer::TK_CF_IF: op=OperatorNode::OP_TERNARY_IF; break;
case GDTokenizer::TK_CF_ELSE: op=OperatorNode::OP_TERNARY_ELSE; break;
default: valid=false; break; default: valid=false; break;
} }
@ -958,6 +960,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
int next_op=-1; int next_op=-1;
int min_priority=0xFFFFF; int min_priority=0xFFFFF;
bool is_unary=false; bool is_unary=false;
bool is_ternary=false;
for(int i=0;i<expression.size();i++) { for(int i=0;i<expression.size();i++) {
@ -971,6 +974,8 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
int priority; int priority;
bool unary=false; bool unary=false;
bool ternary=false;
bool error=false;
switch(expression[i].op) { switch(expression[i].op) {
@ -1001,25 +1006,27 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
case OperatorNode::OP_EQUAL: priority=8; break; case OperatorNode::OP_EQUAL: priority=8; break;
case OperatorNode::OP_NOT_EQUAL: priority=8; break; case OperatorNode::OP_NOT_EQUAL: priority=8; break;
case OperatorNode::OP_IN: priority=10; break; case OperatorNode::OP_IN: priority=10; break;
case OperatorNode::OP_NOT: priority=11; unary=true; break; case OperatorNode::OP_NOT: priority=11; unary=true; break;
case OperatorNode::OP_AND: priority=12; break; case OperatorNode::OP_AND: priority=12; break;
case OperatorNode::OP_OR: priority=13; break; case OperatorNode::OP_OR: priority=13; break;
// ?: = 10 case OperatorNode::OP_TERNARY_IF: priority=14; ternary=true; break;
case OperatorNode::OP_TERNARY_ELSE: priority=14; error=true; break; // Errors out when found without IF (since IF would consume it)
case OperatorNode::OP_ASSIGN: priority=14; break; case OperatorNode::OP_ASSIGN: priority=15; break;
case OperatorNode::OP_ASSIGN_ADD: priority=14; break; case OperatorNode::OP_ASSIGN_ADD: priority=15; break;
case OperatorNode::OP_ASSIGN_SUB: priority=14; break; case OperatorNode::OP_ASSIGN_SUB: priority=15; break;
case OperatorNode::OP_ASSIGN_MUL: priority=14; break; case OperatorNode::OP_ASSIGN_MUL: priority=15; break;
case OperatorNode::OP_ASSIGN_DIV: priority=14; break; case OperatorNode::OP_ASSIGN_DIV: priority=15; break;
case OperatorNode::OP_ASSIGN_MOD: priority=14; break; case OperatorNode::OP_ASSIGN_MOD: priority=15; break;
case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority=14; break; case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority=15; break;
case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority=14; break; case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority=15; break;
case OperatorNode::OP_ASSIGN_BIT_AND: priority=14; break; case OperatorNode::OP_ASSIGN_BIT_AND: priority=15; break;
case OperatorNode::OP_ASSIGN_BIT_OR: priority=14; break; case OperatorNode::OP_ASSIGN_BIT_OR: priority=15; break;
case OperatorNode::OP_ASSIGN_BIT_XOR: priority=14; break; case OperatorNode::OP_ASSIGN_BIT_XOR: priority=15; break;
default: { default: {
@ -1030,11 +1037,16 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
} }
if (priority<min_priority) { if (priority<min_priority) {
if(error) {
_set_error("Unexpected operator");
return NULL;
}
// < is used for left to right (default) // < is used for left to right (default)
// <= is used for right to left // <= is used for right to left
next_op=i; next_op=i;
min_priority=priority; min_priority=priority;
is_unary=unary; is_unary=unary;
is_ternary=ternary;
} }
} }
@ -1075,6 +1087,62 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
} }
} else if(is_ternary) {
if (next_op <1 || next_op>=(expression.size()-1)) {
_set_error("Parser bug..");
ERR_FAIL_V(NULL);
}
if(next_op>=(expression.size()-2) || expression[next_op+2].op != OperatorNode::OP_TERNARY_ELSE) {
_set_error("Expected else after ternary if.");
ERR_FAIL_V(NULL);
}
if(next_op>=(expression.size()-3)) {
_set_error("Expected value after ternary else.");
ERR_FAIL_V(NULL);
}
OperatorNode *op = alloc_node<OperatorNode>();
op->op=expression[next_op].op;
op->line=op_line; //line might have been changed from a \n
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("Unexpected two consecutive operators after ternary if.");
return NULL;
}
if (expression[next_op+3].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("Unexpected two consecutive operators after ternary else.");
return NULL;
}
op->arguments.push_back(expression[next_op+1].node); //next expression goes as first
op->arguments.push_back(expression[next_op-1].node); //left expression goes as when-true
op->arguments.push_back(expression[next_op+3].node); //expression after next goes as when-false
//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);
expression.remove(next_op);
expression.remove(next_op);
} else { } else {
if (next_op <1 || next_op>=(expression.size()-1)) { if (next_op <1 || next_op>=(expression.size()-1)) {

View file

@ -247,6 +247,9 @@ public:
OP_BIT_AND, OP_BIT_AND,
OP_BIT_OR, OP_BIT_OR,
OP_BIT_XOR, OP_BIT_XOR,
//ternary operators
OP_TERNARY_IF,
OP_TERNARY_ELSE,
}; };
Operator op; Operator op;