mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-31 05:31:01 +00:00 
			
		
		
		
	Merge pull request #6845 from karroffel/master
Adds pattern matching to GDScript
This commit is contained in:
		
						commit
						4261880c94
					
				
					 6 changed files with 696 additions and 1 deletions
				
			
		|  | @ -1099,6 +1099,71 @@ Error GDCompiler::_parse_block(CodeGen& codegen,const GDParser::BlockNode *p_blo | |||
| 
 | ||||
| 				switch(cf->cf_type) { | ||||
| 
 | ||||
| 					case GDParser::ControlFlowNode::CF_MATCH: { | ||||
| 						GDParser::MatchNode *match = cf->match; | ||||
| 						 | ||||
| 						GDParser::IdentifierNode *id = memnew(GDParser::IdentifierNode); | ||||
| 						id->name = "#match_value"; | ||||
| 						 | ||||
| 						// var #match_value
 | ||||
| 						// copied because there is no _parse_statement :(
 | ||||
| 						codegen.add_stack_identifier(id->name, p_stack_level++); | ||||
| 						codegen.alloc_stack(p_stack_level); | ||||
| 						new_identifiers++; | ||||
| 
 | ||||
| 						GDParser::OperatorNode *op = memnew(GDParser::OperatorNode); | ||||
| 						op->op=GDParser::OperatorNode::OP_ASSIGN; | ||||
| 						op->arguments.push_back(id); | ||||
| 						op->arguments.push_back(match->val_to_match); | ||||
| 						 | ||||
| 						int ret = _parse_expression(codegen, op, p_stack_level); | ||||
| 						if (ret < 0) { | ||||
| 							return ERR_PARSE_ERROR; | ||||
| 						} | ||||
| 						 | ||||
| 						// break address
 | ||||
| 						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); // break addr
 | ||||
| 						 | ||||
| 						for (int j = 0; j < match->compiled_pattern_branches.size(); j++) { | ||||
| 							GDParser::MatchNode::CompiledPatternBranch branch = match->compiled_pattern_branches[j]; | ||||
| 							 | ||||
| 							// jump over continue
 | ||||
| 							// jump unconditionally
 | ||||
| 							// continue address
 | ||||
| 							// compile the condition
 | ||||
| 							int ret = _parse_expression(codegen, branch.compiled_pattern, p_stack_level); | ||||
| 							if (ret < 0) { | ||||
| 								return ERR_PARSE_ERROR; | ||||
| 							} | ||||
| 							 | ||||
| 							codegen.opcodes.push_back(GDFunction::OPCODE_JUMP_IF); | ||||
| 							codegen.opcodes.push_back(ret); | ||||
| 							codegen.opcodes.push_back(codegen.opcodes.size() + 3); | ||||
| 							int continue_addr = codegen.opcodes.size(); | ||||
| 							codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); | ||||
| 							codegen.opcodes.push_back(0); | ||||
| 							 | ||||
| 							 | ||||
| 							 | ||||
| 							Error err = _parse_block(codegen, branch.body, p_stack_level, p_break_addr, continue_addr); | ||||
| 							if (err) { | ||||
| 								return ERR_PARSE_ERROR; | ||||
| 							} | ||||
| 							 | ||||
| 							codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); | ||||
| 							codegen.opcodes.push_back(break_addr); | ||||
| 							 | ||||
| 							codegen.opcodes[continue_addr + 1] = codegen.opcodes.size(); | ||||
| 						} | ||||
| 						 | ||||
| 						codegen.opcodes[break_addr + 1] = codegen.opcodes.size(); | ||||
| 						 | ||||
| 						 | ||||
| 					} break; | ||||
| 					 | ||||
| 					case GDParser::ControlFlowNode::CF_IF: { | ||||
| 
 | ||||
|  |  | |||
|  | @ -1679,6 +1679,542 @@ bool GDParser::_recover_from_completion() { | |||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| GDParser::PatternNode *GDParser::_parse_pattern(bool p_static) | ||||
| { | ||||
| 	 | ||||
| 	PatternNode *pattern = alloc_node<PatternNode>(); | ||||
| 	 | ||||
| 	GDTokenizer::Token token = tokenizer->get_token(); | ||||
| 	if (error_set) | ||||
| 		return NULL; | ||||
| 	 | ||||
| 	if (token == GDTokenizer::TK_EOF) { | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	 | ||||
| 	switch (token) { | ||||
| 		// dictionary
 | ||||
| 		case GDTokenizer::TK_BRACKET_OPEN: { | ||||
| 			tokenizer->advance(); | ||||
| 			pattern->pt_type = GDParser::PatternNode::PT_ARRAY; | ||||
| 			while (true) { | ||||
| 				 | ||||
| 				if (tokenizer->get_token() == GDTokenizer::TK_BRACKET_CLOSE) { | ||||
| 					tokenizer->advance(); | ||||
| 					break; | ||||
| 				} | ||||
| 				 | ||||
| 				if (tokenizer->get_token() == GDTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDTokenizer::TK_PERIOD) { | ||||
| 					// match everything
 | ||||
| 					tokenizer->advance(2); | ||||
| 					PatternNode *sub_pattern = alloc_node<PatternNode>(); | ||||
| 					sub_pattern->pt_type = GDParser::PatternNode::PT_IGNORE_REST; | ||||
| 					pattern->array.push_back(sub_pattern); | ||||
| 					if (tokenizer->get_token() == GDTokenizer::TK_COMMA && tokenizer->get_token(1) == GDTokenizer::TK_BRACKET_CLOSE) { | ||||
| 						tokenizer->advance(2); | ||||
| 						break; | ||||
| 					} else if (tokenizer->get_token() == GDTokenizer::TK_BRACKET_CLOSE) { | ||||
| 						tokenizer->advance(1); | ||||
| 						break; | ||||
| 					} else { | ||||
| 						_set_error("'..' pattern only allowed at the end of an array pattern"); | ||||
| 						return NULL; | ||||
| 					} | ||||
| 				} | ||||
| 				 | ||||
| 				PatternNode *sub_pattern = _parse_pattern(p_static); | ||||
| 				if (!sub_pattern) { | ||||
| 					return NULL; | ||||
| 				} | ||||
| 				 | ||||
| 				pattern->array.push_back(sub_pattern); | ||||
| 				 | ||||
| 				if (tokenizer->get_token() == GDTokenizer::TK_COMMA) { | ||||
| 					tokenizer->advance(); | ||||
| 					continue; | ||||
| 				} else if (tokenizer->get_token() == GDTokenizer::TK_BRACKET_CLOSE) { | ||||
| 					tokenizer->advance(); | ||||
| 					break; | ||||
| 				} else { | ||||
| 					_set_error("Not a valid pattern"); | ||||
| 					return NULL; | ||||
| 				} | ||||
| 			} | ||||
| 		} break; | ||||
| 		// bind
 | ||||
| 		case GDTokenizer::TK_PR_VAR: { | ||||
| 			tokenizer->advance(); | ||||
| 			pattern->pt_type = GDParser::PatternNode::PT_BIND; | ||||
| 			pattern->bind = tokenizer->get_token_identifier(); | ||||
| 			tokenizer->advance(); | ||||
| 		} break; | ||||
| 		// array
 | ||||
| 		case GDTokenizer::TK_CURLY_BRACKET_OPEN: { | ||||
| 			tokenizer->advance(); | ||||
| 			pattern->pt_type = GDParser::PatternNode::PT_DICTIONARY; | ||||
| 			while (true) { | ||||
| 				 | ||||
| 				if (tokenizer->get_token() == GDTokenizer::TK_CURLY_BRACKET_CLOSE) { | ||||
| 					tokenizer->advance(); | ||||
| 					break; | ||||
| 				} | ||||
| 				 | ||||
| 				if (tokenizer->get_token() == GDTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDTokenizer::TK_PERIOD) { | ||||
| 					// match everything
 | ||||
| 					tokenizer->advance(2); | ||||
| 					PatternNode *sub_pattern = alloc_node<PatternNode>(); | ||||
| 					sub_pattern->pt_type = PatternNode::PT_IGNORE_REST; | ||||
| 					pattern->array.push_back(sub_pattern); | ||||
| 					if (tokenizer->get_token() == GDTokenizer::TK_COMMA && tokenizer->get_token(1) == GDTokenizer::TK_CURLY_BRACKET_CLOSE) { | ||||
| 						tokenizer->advance(2); | ||||
| 						break; | ||||
| 					} else if (tokenizer->get_token() == GDTokenizer::TK_CURLY_BRACKET_CLOSE) { | ||||
| 						tokenizer->advance(1); | ||||
| 						break; | ||||
| 					} else { | ||||
| 						_set_error("'..' pattern only allowed at the end of an dictionary pattern"); | ||||
| 						return NULL; | ||||
| 					} | ||||
| 				} | ||||
| 				 | ||||
| 				Node *key = _parse_and_reduce_expression(pattern, p_static); | ||||
| 				if (!key) { | ||||
| 					_set_error("Not a valid key in pattern"); | ||||
| 					return NULL; | ||||
| 				} | ||||
| 				 | ||||
| 				if (key->type != GDParser::Node::TYPE_CONSTANT) { | ||||
| 					_set_error("Not a constant expression as key"); | ||||
| 					return NULL; | ||||
| 				} | ||||
| 				 | ||||
| 				if (tokenizer->get_token() == GDTokenizer::TK_COLON) { | ||||
| 					tokenizer->advance(); | ||||
| 					 | ||||
| 					PatternNode *value = _parse_pattern(p_static); | ||||
| 					if (!value) { | ||||
| 						_set_error("Expected pattern in dictionary value"); | ||||
| 						return NULL; | ||||
| 					} | ||||
| 					 | ||||
| 					pattern->dictionary.insert(static_cast<ConstantNode*>(key), value); | ||||
| 				} else { | ||||
| 					pattern->dictionary.insert(static_cast<ConstantNode*>(key), NULL); | ||||
| 				} | ||||
| 				 | ||||
| 				 | ||||
| 				if (tokenizer->get_token() == GDTokenizer::TK_COMMA) { | ||||
| 					tokenizer->advance(); | ||||
| 					continue; | ||||
| 				} else if (tokenizer->get_token() == GDTokenizer::TK_CURLY_BRACKET_CLOSE) { | ||||
| 					tokenizer->advance(); | ||||
| 					break; | ||||
| 				} else { | ||||
| 					_set_error("Not a valid pattern"); | ||||
| 					return NULL; | ||||
| 				} | ||||
| 			} | ||||
| 		} break; | ||||
| 		// all the constants like strings and numbers
 | ||||
| 		default: { | ||||
| 			Node *value = _parse_and_reduce_expression(pattern, p_static); | ||||
| 			if (error_set) { | ||||
| 				return NULL; | ||||
| 			} | ||||
| 			if (value->type == Node::TYPE_IDENTIFIER && static_cast<IdentifierNode*>(value)->name == "_") { | ||||
| 				// wildcard pattern
 | ||||
| 				pattern->pt_type = PatternNode::PT_WILDCARD; | ||||
| 				break; | ||||
| 			} | ||||
| 			 | ||||
| 			if (value->type != Node::TYPE_IDENTIFIER && value->type != Node::TYPE_CONSTANT) { | ||||
| 				_set_error("Only constant expressions or variables allowed in a pattern"); | ||||
| 				return NULL; | ||||
| 			} | ||||
| 			 | ||||
| 			pattern->pt_type = PatternNode::PT_CONSTANT; | ||||
| 			pattern->constant = value; | ||||
| 		} break; | ||||
| 	} | ||||
| 	 | ||||
| 	return pattern; | ||||
| } | ||||
| 
 | ||||
| void GDParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode*> &p_branches, bool p_static) | ||||
| { | ||||
| 	int indent_level = tab_level.back()->get(); | ||||
| 	 | ||||
| 	while (true) { | ||||
| 		 | ||||
| 		while (tokenizer->get_token() == GDTokenizer::TK_NEWLINE && _parse_newline()); | ||||
| 		 | ||||
| 		// GDTokenizer::Token token = tokenizer->get_token();
 | ||||
| 		if (error_set) | ||||
| 			return; | ||||
| 		 | ||||
| 		if (indent_level > tab_level.back()->get()) { | ||||
| 			return; // go back a level
 | ||||
| 		} | ||||
| 		 | ||||
| 		if (pending_newline!=-1) { | ||||
| 			pending_newline=-1; | ||||
| 		} | ||||
| 		 | ||||
| 		PatternBranchNode *branch = alloc_node<PatternBranchNode>(); | ||||
| 		 | ||||
| 		branch->patterns.push_back(_parse_pattern(p_static)); | ||||
| 		if (!branch->patterns[0]) { | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		while (tokenizer->get_token() == GDTokenizer::TK_COMMA) { | ||||
| 			tokenizer->advance(); | ||||
| 			branch->patterns.push_back(_parse_pattern(p_static)); | ||||
| 			if (!branch->patterns[branch->patterns.size() - 1]) { | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		if(!_enter_indent_block()) { | ||||
| 			_set_error("Expected block in pattern branch"); | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		branch->body = alloc_node<BlockNode>(); | ||||
| 		branch->body->parent_block = p_block; | ||||
| 		p_block->sub_blocks.push_back(branch->body); | ||||
| 		current_block = branch->body; | ||||
| 		 | ||||
| 		_parse_block(branch->body, p_static); | ||||
| 		 | ||||
| 		current_block = p_block; | ||||
| 		 | ||||
| 		p_branches.push_back(branch); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GDParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node*> &p_bindings) | ||||
| { | ||||
| 	switch (p_pattern->pt_type) { | ||||
| 		case PatternNode::PT_CONSTANT: { | ||||
| 			 | ||||
| 			// typecheck
 | ||||
| 			BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); | ||||
| 			typeof_node->function = GDFunctions::TYPE_OF; | ||||
| 			 | ||||
| 			OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); | ||||
| 			typeof_match_value->op = OperatorNode::OP_CALL; | ||||
| 			typeof_match_value->arguments.push_back(typeof_node); | ||||
| 			typeof_match_value->arguments.push_back(p_node_to_match); | ||||
| 			 | ||||
| 			OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>(); | ||||
| 			typeof_pattern_value->op = OperatorNode::OP_CALL; | ||||
| 			typeof_pattern_value->arguments.push_back(typeof_node); | ||||
| 			typeof_pattern_value->arguments.push_back(p_pattern->constant); | ||||
| 			 | ||||
| 			OperatorNode *type_comp = alloc_node<OperatorNode>(); | ||||
| 			type_comp->op = OperatorNode::OP_EQUAL; | ||||
| 			type_comp->arguments.push_back(typeof_match_value); | ||||
| 			type_comp->arguments.push_back(typeof_pattern_value); | ||||
| 			 | ||||
| 			 | ||||
| 			// comare the actual values
 | ||||
| 			OperatorNode *value_comp = alloc_node<OperatorNode>(); | ||||
| 			value_comp->op = OperatorNode::OP_EQUAL; | ||||
| 			value_comp->arguments.push_back(p_pattern->constant); | ||||
| 			value_comp->arguments.push_back(p_node_to_match); | ||||
| 			 | ||||
| 			 | ||||
| 			OperatorNode *comparison = alloc_node<OperatorNode>(); | ||||
| 			comparison->op = OperatorNode::OP_AND; | ||||
| 			comparison->arguments.push_back(type_comp); | ||||
| 			comparison->arguments.push_back(value_comp); | ||||
| 			 | ||||
| 			p_resulting_node = comparison; | ||||
| 			 | ||||
| 		} break; | ||||
| 		case PatternNode::PT_BIND: { | ||||
| 			p_bindings[p_pattern->bind] = p_node_to_match; | ||||
| 			 | ||||
| 			// a bind always matches
 | ||||
| 			ConstantNode *true_value = alloc_node<ConstantNode>(); | ||||
| 			true_value->value = Variant(true); | ||||
| 			p_resulting_node = true_value; | ||||
| 		} break; | ||||
| 		case PatternNode::PT_ARRAY: { | ||||
| 			 | ||||
| 			bool open_ended = false; | ||||
| 	 | ||||
| 			if (p_pattern->array.size() > 0) { | ||||
| 				if (p_pattern->array[p_pattern->array.size() - 1]->pt_type == PatternNode::PT_IGNORE_REST) { | ||||
| 					open_ended = true; | ||||
| 				} | ||||
| 			} | ||||
| 			 | ||||
| 			// typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() >= length
 | ||||
| 			// typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length
 | ||||
| 			 | ||||
| 			{ | ||||
| 				// typecheck
 | ||||
| 				BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); | ||||
| 				typeof_node->function = GDFunctions::TYPE_OF; | ||||
| 				 | ||||
| 				OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); | ||||
| 				typeof_match_value->op = OperatorNode::OP_CALL; | ||||
| 				typeof_match_value->arguments.push_back(typeof_node); | ||||
| 				typeof_match_value->arguments.push_back(p_node_to_match); | ||||
| 				 | ||||
| 				IdentifierNode *typeof_array = alloc_node<IdentifierNode>(); | ||||
| 				typeof_array->name = "TYPE_ARRAY"; | ||||
| 				 | ||||
| 				OperatorNode *type_comp = alloc_node<OperatorNode>(); | ||||
| 				type_comp->op = OperatorNode::OP_EQUAL; | ||||
| 				type_comp->arguments.push_back(typeof_match_value); | ||||
| 				type_comp->arguments.push_back(typeof_array); | ||||
| 				 | ||||
| 				 | ||||
| 				// size
 | ||||
| 				ConstantNode *length = alloc_node<ConstantNode>(); | ||||
| 				length->value = Variant(open_ended ? p_pattern->array.size() - 1 : p_pattern->array.size()); | ||||
| 				 | ||||
| 				OperatorNode *call = alloc_node<OperatorNode>(); | ||||
| 				call->op = OperatorNode::OP_CALL; | ||||
| 				call->arguments.push_back(p_node_to_match); | ||||
| 				 | ||||
| 				IdentifierNode *size = alloc_node<IdentifierNode>(); | ||||
| 				size->name = "size"; | ||||
| 				call->arguments.push_back(size); | ||||
| 				 | ||||
| 				OperatorNode *length_comparison = alloc_node<OperatorNode>(); | ||||
| 				length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL; | ||||
| 				length_comparison->arguments.push_back(call); | ||||
| 				length_comparison->arguments.push_back(length); | ||||
| 				 | ||||
| 				OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); | ||||
| 				type_and_length_comparison->op = OperatorNode::OP_AND; | ||||
| 				type_and_length_comparison->arguments.push_back(type_comp); | ||||
| 				type_and_length_comparison->arguments.push_back(length_comparison); | ||||
| 				 | ||||
| 				p_resulting_node = type_and_length_comparison; | ||||
| 			} | ||||
| 			 | ||||
| 			 | ||||
| 			 | ||||
| 			for (int i = 0; i < p_pattern->array.size(); i++) { | ||||
| 				PatternNode *pattern = p_pattern->array[i]; | ||||
| 				 | ||||
| 				Node *condition = NULL; | ||||
| 				 | ||||
| 				ConstantNode *index = alloc_node<ConstantNode>(); | ||||
| 				index->value = Variant(i); | ||||
| 				 | ||||
| 				OperatorNode *indexed_value = alloc_node<OperatorNode>(); | ||||
| 				indexed_value->op = OperatorNode::OP_INDEX; | ||||
| 				indexed_value->arguments.push_back(p_node_to_match); | ||||
| 				indexed_value->arguments.push_back(index); | ||||
| 				 | ||||
| 				_generate_pattern(pattern, indexed_value, condition, p_bindings); | ||||
| 				 | ||||
| 				// concatenate all the patterns with &&
 | ||||
| 				OperatorNode *and_node = alloc_node<OperatorNode>(); | ||||
| 				and_node->op = OperatorNode::OP_AND; | ||||
| 				and_node->arguments.push_back(p_resulting_node); | ||||
| 				and_node->arguments.push_back(condition); | ||||
| 				 | ||||
| 				p_resulting_node = and_node; | ||||
| 			} | ||||
| 			 | ||||
| 			 | ||||
| 		} break; | ||||
| 		case PatternNode::PT_DICTIONARY: { | ||||
| 			 | ||||
| 			bool open_ended = false; | ||||
| 			 | ||||
| 			if (p_pattern->array.size() > 0) { | ||||
| 				open_ended = true; | ||||
| 			} | ||||
| 			 | ||||
| 			// typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() >= length
 | ||||
| 			// typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length
 | ||||
| 			 | ||||
| 			 | ||||
| 			{ | ||||
| 				// typecheck
 | ||||
| 				BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); | ||||
| 				typeof_node->function = GDFunctions::TYPE_OF; | ||||
| 				 | ||||
| 				OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); | ||||
| 				typeof_match_value->op = OperatorNode::OP_CALL; | ||||
| 				typeof_match_value->arguments.push_back(typeof_node); | ||||
| 				typeof_match_value->arguments.push_back(p_node_to_match); | ||||
| 				 | ||||
| 				IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>(); | ||||
| 				typeof_dictionary->name = "TYPE_DICTIONARY"; | ||||
| 				 | ||||
| 				OperatorNode *type_comp = alloc_node<OperatorNode>(); | ||||
| 				type_comp->op = OperatorNode::OP_EQUAL; | ||||
| 				type_comp->arguments.push_back(typeof_match_value); | ||||
| 				type_comp->arguments.push_back(typeof_dictionary); | ||||
| 				 | ||||
| 				// size
 | ||||
| 				ConstantNode *length = alloc_node<ConstantNode>(); | ||||
| 				length->value = Variant(open_ended ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); | ||||
| 				 | ||||
| 				OperatorNode *call = alloc_node<OperatorNode>(); | ||||
| 				call->op = OperatorNode::OP_CALL; | ||||
| 				call->arguments.push_back(p_node_to_match); | ||||
| 				 | ||||
| 				IdentifierNode *size = alloc_node<IdentifierNode>(); | ||||
| 				size->name = "size"; | ||||
| 				call->arguments.push_back(size); | ||||
| 				 | ||||
| 				OperatorNode *length_comparison = alloc_node<OperatorNode>(); | ||||
| 				length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL; | ||||
| 				length_comparison->arguments.push_back(call); | ||||
| 				length_comparison->arguments.push_back(length); | ||||
| 				 | ||||
| 				OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); | ||||
| 				type_and_length_comparison->op = OperatorNode::OP_AND; | ||||
| 				type_and_length_comparison->arguments.push_back(type_comp); | ||||
| 				type_and_length_comparison->arguments.push_back(length_comparison); | ||||
| 				 | ||||
| 				p_resulting_node = type_and_length_comparison; | ||||
| 			} | ||||
| 			 | ||||
| 			 | ||||
| 			 | ||||
| 			for (Map<ConstantNode*, PatternNode*>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) { | ||||
| 				 | ||||
| 				Node *condition = NULL; | ||||
| 				 | ||||
| 				// chech for has, then for pattern
 | ||||
| 				 | ||||
| 				IdentifierNode *has = alloc_node<IdentifierNode>(); | ||||
| 				has->name = "has"; | ||||
| 				 | ||||
| 				OperatorNode *has_call = alloc_node<OperatorNode>(); | ||||
| 				has_call->op = OperatorNode::OP_CALL; | ||||
| 				has_call->arguments.push_back(p_node_to_match); | ||||
| 				has_call->arguments.push_back(has); | ||||
| 				has_call->arguments.push_back(e->key()); | ||||
| 					 | ||||
| 				 | ||||
| 				if (e->value()) { | ||||
| 					 | ||||
| 					OperatorNode *indexed_value = alloc_node<OperatorNode>(); | ||||
| 					indexed_value->op = OperatorNode::OP_INDEX; | ||||
| 					indexed_value->arguments.push_back(p_node_to_match); | ||||
| 					indexed_value->arguments.push_back(e->key()); | ||||
| 					 | ||||
| 					_generate_pattern(e->value(), indexed_value, condition, p_bindings); | ||||
| 					 | ||||
| 					OperatorNode *has_and_pattern = alloc_node<OperatorNode>(); | ||||
| 					has_and_pattern->op = OperatorNode::OP_AND; | ||||
| 					has_and_pattern->arguments.push_back(has_call); | ||||
| 					has_and_pattern->arguments.push_back(condition); | ||||
| 					 | ||||
| 					condition = has_and_pattern; | ||||
| 
 | ||||
| 				} else { | ||||
| 					condition = has_call; | ||||
| 				} | ||||
| 				 | ||||
| 				 | ||||
| 				 | ||||
| 				// concatenate all the patterns with &&
 | ||||
| 				OperatorNode *and_node = alloc_node<OperatorNode>(); | ||||
| 				and_node->op = OperatorNode::OP_AND; | ||||
| 				and_node->arguments.push_back(p_resulting_node); | ||||
| 				and_node->arguments.push_back(condition); | ||||
| 				 | ||||
| 				p_resulting_node = and_node; | ||||
| 			} | ||||
| 			 | ||||
| 		} break; | ||||
| 		case PatternNode::PT_IGNORE_REST: | ||||
| 		case PatternNode::PT_WILDCARD: { | ||||
| 			// simply generate a `true`
 | ||||
| 			ConstantNode *true_value = alloc_node<ConstantNode>(); | ||||
| 			true_value->value = Variant(true); | ||||
| 			p_resulting_node = true_value; | ||||
| 		} break; | ||||
| 		default: { | ||||
| 			 | ||||
| 		} break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void GDParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement) | ||||
| { | ||||
| 	IdentifierNode *id = alloc_node<IdentifierNode>(); | ||||
| 	id->name = "#match_value"; | ||||
| 	 | ||||
| 	for (int i = 0; i < p_match_statement->branches.size(); i++) { | ||||
| 		 | ||||
| 		PatternBranchNode *branch = p_match_statement->branches[i]; | ||||
| 		 | ||||
| 		MatchNode::CompiledPatternBranch compiled_branch; | ||||
| 		compiled_branch.compiled_pattern = NULL; | ||||
| 		 | ||||
| 		Map<StringName, Node*> binding; | ||||
| 		 | ||||
| 		for (int j = 0; j < branch->patterns.size(); j++) { | ||||
| 			PatternNode *pattern = branch->patterns[j]; | ||||
| 			 | ||||
| 			Map<StringName, Node*> bindings; | ||||
| 			Node *resulting_node; | ||||
| 			_generate_pattern(pattern, id, resulting_node, bindings); | ||||
| 			 | ||||
| 			if (!binding.empty() && !bindings.empty()) { | ||||
| 				_set_error("Multipatterns can't contain bindings"); | ||||
| 				return; | ||||
| 			} else { | ||||
| 				binding = bindings; | ||||
| 			} | ||||
| 			 | ||||
| 			if (compiled_branch.compiled_pattern) { | ||||
| 				OperatorNode *or_node = alloc_node<OperatorNode>(); | ||||
| 				or_node->op = OperatorNode::OP_OR; | ||||
| 				or_node->arguments.push_back(compiled_branch.compiled_pattern); | ||||
| 				or_node->arguments.push_back(resulting_node); | ||||
| 				 | ||||
| 				compiled_branch.compiled_pattern = or_node; | ||||
| 			} else { | ||||
| 				// single pattern | first one
 | ||||
| 				compiled_branch.compiled_pattern = resulting_node; | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 		// prepare the body ...hehe
 | ||||
| 		for (Map<StringName, Node*>::Element *e = binding.front(); e; e = e->next()) { | ||||
| 			LocalVarNode *local_var = alloc_node<LocalVarNode>(); | ||||
| 			local_var->name = e->key(); | ||||
| 			local_var->assign = e->value(); | ||||
| 
 | ||||
| 			 | ||||
| 			IdentifierNode *id = alloc_node<IdentifierNode>(); | ||||
| 			id->name = local_var->name; | ||||
| 
 | ||||
| 			OperatorNode *op = alloc_node<OperatorNode>(); | ||||
| 			op->op=OperatorNode::OP_ASSIGN; | ||||
| 			op->arguments.push_back(id); | ||||
| 			op->arguments.push_back(local_var->assign); | ||||
| 			 | ||||
| 			branch->body->statements.push_front(op); | ||||
| 			branch->body->statements.push_front(local_var); | ||||
| 		} | ||||
| 		 | ||||
| 		compiled_branch.body = branch->body; | ||||
| 		 | ||||
| 		 | ||||
| 		p_match_statement->compiled_pattern_branches.push_back(compiled_branch); | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
| 
 | ||||
| void GDParser::_parse_block(BlockNode *p_block,bool p_static) { | ||||
| 
 | ||||
| 	int indent_level = tab_level.back()->get(); | ||||
|  | @ -2165,6 +2701,46 @@ void GDParser::_parse_block(BlockNode *p_block,bool p_static) { | |||
| 				} | ||||
| 
 | ||||
| 
 | ||||
| 			} break; | ||||
| 			case GDTokenizer::TK_CF_MATCH: { | ||||
| 				 | ||||
| 				tokenizer->advance(); | ||||
| 				 | ||||
| 				MatchNode *match_node = alloc_node<MatchNode>(); | ||||
| 				 | ||||
| 				Node *val_to_match = _parse_and_reduce_expression(p_block, p_static); | ||||
| 				 | ||||
| 				if (!val_to_match) { | ||||
| 					if (_recover_from_completion()) { | ||||
| 						break; | ||||
| 					} | ||||
| 					return; | ||||
| 				} | ||||
| 				 | ||||
| 				match_node->val_to_match = val_to_match; | ||||
| 				 | ||||
| 				if (!_enter_indent_block()) { | ||||
| 					_set_error("Expected indented pattern matching block after 'match'"); | ||||
| 					return; | ||||
| 				} | ||||
| 				 | ||||
| 				BlockNode *compiled_branches = alloc_node<BlockNode>(); | ||||
| 				compiled_branches->parent_block = p_block; | ||||
| 				compiled_branches->parent_class = p_block->parent_class; | ||||
| 				 | ||||
| 				p_block->sub_blocks.push_back(compiled_branches); | ||||
| 				 | ||||
| 				_parse_pattern_block(compiled_branches, match_node->branches, p_static); | ||||
| 				 | ||||
| 				_transform_match_statment(compiled_branches, match_node); | ||||
| 				 | ||||
| 				ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>(); | ||||
| 				match_cf_node->cf_type = ControlFlowNode::CF_MATCH; | ||||
| 				match_cf_node->match = match_node; | ||||
| 				 | ||||
| 				p_block->statements.push_back(match_cf_node); | ||||
| 				 | ||||
| 				_end_statement(); | ||||
| 			} break; | ||||
| 			case GDTokenizer::TK_PR_ASSERT: { | ||||
| 
 | ||||
|  |  | |||
|  | @ -259,6 +259,44 @@ public: | |||
| 		OperatorNode() { type=TYPE_OPERATOR; } | ||||
| 	}; | ||||
| 	 | ||||
| 	 | ||||
| 	struct PatternNode : public Node { | ||||
| 		 | ||||
| 		enum PatternType { | ||||
| 			PT_CONSTANT, | ||||
| 			PT_BIND, | ||||
| 			PT_DICTIONARY, | ||||
| 			PT_ARRAY, | ||||
| 			PT_IGNORE_REST, | ||||
| 			PT_WILDCARD | ||||
| 		}; | ||||
| 		 | ||||
| 		PatternType pt_type; | ||||
| 		 | ||||
| 		Node *constant; | ||||
| 		StringName bind; | ||||
| 		Map<ConstantNode*, PatternNode*> dictionary; | ||||
| 		Vector<PatternNode*> array; | ||||
| 		 | ||||
| 	}; | ||||
| 	 | ||||
| 	struct PatternBranchNode : public Node { | ||||
| 		Vector<PatternNode*> patterns; | ||||
| 		BlockNode *body; | ||||
| 	}; | ||||
| 	 | ||||
| 	struct MatchNode : public Node { | ||||
| 		Node *val_to_match; | ||||
| 		Vector<PatternBranchNode*> branches; | ||||
| 		 | ||||
| 		struct CompiledPatternBranch { | ||||
| 			Node *compiled_pattern; | ||||
| 			BlockNode *body; | ||||
| 		}; | ||||
| 		 | ||||
| 		Vector<CompiledPatternBranch> compiled_pattern_branches; | ||||
| 	}; | ||||
| 
 | ||||
| 	struct ControlFlowNode : public Node { | ||||
| 		enum CFType { | ||||
| 			CF_IF, | ||||
|  | @ -267,7 +305,8 @@ public: | |||
| 			CF_SWITCH, | ||||
| 			CF_BREAK, | ||||
| 			CF_CONTINUE, | ||||
| 			CF_RETURN | ||||
| 			CF_RETURN, | ||||
| 			CF_MATCH | ||||
| 		}; | ||||
| 
 | ||||
| 		CFType cf_type; | ||||
|  | @ -275,6 +314,8 @@ public: | |||
| 		BlockNode *body; | ||||
| 		BlockNode *body_else; | ||||
| 		 | ||||
| 		MatchNode *match; | ||||
| 
 | ||||
| 		ControlFlowNode *_else; //used for if
 | ||||
| 		ControlFlowNode() { type=TYPE_CONTROL_FLOW; cf_type=CF_IF; body=NULL; body_else=NULL;} | ||||
| 	}; | ||||
|  | @ -452,6 +493,15 @@ private: | |||
| 	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); | ||||
| 
 | ||||
| 	 | ||||
| 	 | ||||
| 	 | ||||
| 	PatternNode *_parse_pattern(bool p_static); | ||||
| 	void _parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode*> &p_branches, bool p_static); | ||||
| 	void _transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement); | ||||
| 	void _generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node*> &p_bindings); | ||||
| 	 | ||||
| 	 | ||||
| 	void _parse_block(BlockNode *p_block,bool p_static); | ||||
| 	void _parse_extends(ClassNode *p_class); | ||||
| 	void _parse_class(ClassNode *p_class); | ||||
|  |  | |||
|  | @ -1938,6 +1938,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const  { | |||
| 		"for", | ||||
| 		"pass", | ||||
| 		"return", | ||||
| 		"match", | ||||
| 		"while", | ||||
| 		"remote", | ||||
| 		"sync", | ||||
|  |  | |||
|  | @ -85,6 +85,7 @@ const char* GDTokenizer::token_names[TK_MAX]={ | |||
| "continue", | ||||
| "pass", | ||||
| "return", | ||||
| "match", | ||||
| "func", | ||||
| "class", | ||||
| "extends", | ||||
|  | @ -894,6 +895,7 @@ void GDTokenizerText::_advance() { | |||
| 								{TK_CF_BREAK,"break"}, | ||||
| 								{TK_CF_CONTINUE,"continue"}, | ||||
| 								{TK_CF_RETURN,"return"}, | ||||
| 								{TK_CF_MATCH, "match"}, | ||||
| 								{TK_CF_PASS,"pass"}, | ||||
| 								{TK_SELF,"self"}, | ||||
| 								{TK_CONST_PI,"PI"}, | ||||
|  |  | |||
|  | @ -92,6 +92,7 @@ public: | |||
| 		TK_CF_CONTINUE, | ||||
| 		TK_CF_PASS, | ||||
| 		TK_CF_RETURN, | ||||
| 		TK_CF_MATCH, | ||||
| 		TK_PR_FUNCTION, | ||||
| 		TK_PR_CLASS, | ||||
| 		TK_PR_EXTENDS, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Juan Linietsky
						Juan Linietsky