Browse Source

Merge pull request #6281 from bojidar-bg/gdscript-ternary-operator

Ternary operator in GDScript (a if x else b)
Juan Linietsky 9 years ago
parent
commit
78f92dbcb9
3 changed files with 126 additions and 15 deletions
  1. 40 0
      modules/gdscript/gd_compiler.cpp
  2. 83 15
      modules/gdscript/gd_parser.cpp
  3. 3 0
      modules/gdscript/gd_parser.h

+ 40 - 0
modules/gdscript/gd_compiler.cpp

@@ -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);
 					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;
 				//unary operators
 				case GDParser::OperatorNode::OP_NEG: { if (!_create_unary_operator(codegen,on,Variant::OP_NEGATE,p_stack_level)) return -1;} break;

+ 83 - 15
modules/gdscript/gd_parser.cpp

@@ -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_XOR: op=OperatorNode::OP_BIT_XOR ; 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;
 		}
 
@@ -958,6 +960,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
 		int next_op=-1;
 		int min_priority=0xFFFFF;
 		bool is_unary=false;
+		bool is_ternary=false;
 
 		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;
 
 			bool unary=false;
+			bool ternary=false;
+			bool error=false;
 
 			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_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;
+				
+				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=15; break;
+				case OperatorNode::OP_ASSIGN_ADD: priority=15; break;
+				case OperatorNode::OP_ASSIGN_SUB: priority=15; break;
+				case OperatorNode::OP_ASSIGN_MUL: priority=15; break;
+				case OperatorNode::OP_ASSIGN_DIV: priority=15; break;
+				case OperatorNode::OP_ASSIGN_MOD: priority=15; break;
+				case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority=15; break;
+				case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority=15; break;
+				case OperatorNode::OP_ASSIGN_BIT_AND: priority=15; break;
+				case OperatorNode::OP_ASSIGN_BIT_OR: priority=15; break;
+				case OperatorNode::OP_ASSIGN_BIT_XOR: priority=15; break;
 
 
 				default: {
@@ -1030,11 +1037,16 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
 			}
 
 			if (priority<min_priority) {
+				if(error) {
+					_set_error("Unexpected operator");
+					return NULL;
+				}
 				// < is used for left to right (default)
 				// <= is used for right to left
 				next_op=i;
 				min_priority=priority;
 				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 {
 
 			if (next_op <1 || next_op>=(expression.size()-1)) {

+ 3 - 0
modules/gdscript/gd_parser.h

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