Merge pull request #6845 from karroffel/master

Adds pattern matching to GDScript
This commit is contained in:
Juan Linietsky 2017-01-14 17:08:10 -03:00 committed by GitHub
commit 4261880c94
6 changed files with 696 additions and 1 deletions

View File

@ -1099,7 +1099,72 @@ 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: {
#ifdef DEBUG_ENABLED

View File

@ -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: {

View File

@ -258,6 +258,44 @@ public:
Vector<Node*> arguments;
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 {
@ -267,13 +305,16 @@ public:
CF_SWITCH,
CF_BREAK,
CF_CONTINUE,
CF_RETURN
CF_RETURN,
CF_MATCH
};
CFType cf_type;
Vector<Node*> arguments;
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);

View File

@ -1938,6 +1938,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"for",
"pass",
"return",
"match",
"while",
"remote",
"sync",

View File

@ -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"},

View File

@ -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,