Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of match expression #724

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions compiler/code-gen/vertex-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "compiler/code-gen/vertex-compiler.h"

#include "compiler/inferring/primitive-type.h"
#include <iterator>
#include <unordered_map>

Expand Down Expand Up @@ -1147,8 +1148,11 @@ void compile_switch_str(VertexAdaptor<op_switch> root, CodeGenerator &W) {
auto temp_var_strval_of_condition = root->condition_on_switch();
auto temp_var_matched_with_a_case = root->matched_with_one_case();

// because we checked types before in case of match
const char *convert_function = root->is_match ? "" : "f$strval";

W << BEGIN;
W << temp_var_strval_of_condition << " = f$strval (" << root->condition() << ");" << NL;
W << temp_var_strval_of_condition << " = " << convert_function << "(" << root->condition() << ");" << NL;
W << temp_var_matched_with_a_case << " = false;" << NL;

W << "switch (" << temp_var_strval_of_condition << ".hash()) " << BEGIN;
Expand Down Expand Up @@ -1191,7 +1195,9 @@ void compile_switch_str(VertexAdaptor<op_switch> root, CodeGenerator &W) {
}

void compile_switch_int(VertexAdaptor<op_switch> root, CodeGenerator &W) {
W << "switch (f$intval (" << root->condition() << "))" << BEGIN;
// because we checked types before in case of match
const char *convert_function = root->is_match ? "" : "f$intval";
W << "switch (" << convert_function << " (" << root->condition() << "))" << BEGIN;

W << "static_cast<void>(" << root->condition_on_switch() << ");" << NL;
W << "static_cast<void>(" << root->matched_with_one_case() << ");" << NL;
Expand All @@ -1206,7 +1212,7 @@ void compile_switch_int(VertexAdaptor<op_switch> root, CodeGenerator &W) {
if (val->type() == op_int_const) {
const std::string &str = val.as<op_int_const>()->str_val;
W << str;
kphp_error(used.insert(str).second, fmt_format("Switch: repeated cases found [{}]", str));
kphp_error(used.insert(str).second, fmt_format("{}: repeated cases found [{}]", root->is_match ? "Match" : "Switch", str));
} else {
kphp_assert(VertexUtil::is_const_int(val));
W << val;
Expand All @@ -1228,6 +1234,7 @@ void compile_switch_var(VertexAdaptor<op_switch> root, CodeGenerator &W) {

auto temp_var_condition_on_switch = root->condition_on_switch();
auto temp_var_matched_with_a_case = root->matched_with_one_case();
const char *eq_function = root->is_match ? "equals" : "eq2";

W << "do " << BEGIN;
W << temp_var_condition_on_switch << " = " << root->condition() << ";" << NL;
Expand All @@ -1239,7 +1246,7 @@ void compile_switch_var(VertexAdaptor<op_switch> root, CodeGenerator &W) {
VertexAdaptor<op_seq> cmd;
if (auto cs = one_case.try_as<op_case>()) {
cmd = cs->cmd();
W << "if (" << temp_var_matched_with_a_case << " || eq2(" << temp_var_condition_on_switch << ", " << cs->expr() << ")) " << BEGIN;
W << "if (" << temp_var_matched_with_a_case << " || " << eq_function << "(" << temp_var_condition_on_switch << ", " << cs->expr() << ")) " << BEGIN;
W << temp_var_matched_with_a_case << " = true;" << NL;
} else {
if (!default_case_is_the_last) {
Expand Down Expand Up @@ -1278,12 +1285,28 @@ void compile_switch(VertexAdaptor<op_switch> root, CodeGenerator &W) {

for (auto one_case : root->cases()) {
if (one_case->type() == op_default) {
kphp_error_return(!has_default, "Switch: several `default` cases found");
kphp_error_return(!has_default, fmt_format("%s: several `default` cases found", root->is_match ? "Match" : "Switch"));
has_default = true;
continue;
}
}

if (root->is_match) {
const TypeData *cond_type = tinf::get_type(root->condition());
if (!cond_type) {
compile_switch_var(root, W);
return;
}

if (root->kind == SwitchKind::StringSwitch && cond_type->get_real_ptype() == tp_string) {
compile_switch_str(root, W);
} else if (root->kind == SwitchKind::IntSwitch && cond_type->get_real_ptype() == tp_int) {
compile_switch_int(root, W);
} else {
compile_switch_var(root, W);
}
return;
}
if (root->kind == SwitchKind::StringSwitch) {
compile_switch_str(root, W);
} else if (root->kind == SwitchKind::IntSwitch) {
Expand Down
1 change: 1 addition & 0 deletions compiler/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ std::string debugTokenName(TokenType t) {
{tok_as, "tok_as"},
{tok_case, "tok_case"},
{tok_switch, "tok_switch"},
{tok_match, "tok_match"},
{tok_class, "tok_class"},
{tok_interface, "tok_interface"},
{tok_trait, "tok_trait"},
Expand Down
81 changes: 81 additions & 0 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
#include "compiler/data/function-data.h"
#include "compiler/data/lib-data.h"
#include "compiler/data/src-file.h"
#include "compiler/data/vertex-adaptor.h"
#include "compiler/kphp_assert.h"
#include "compiler/lambda-utils.h"
#include "compiler/lexer.h"
#include "compiler/name-gen.h"
#include "compiler/phpdoc.h"
#include "compiler/stage.h"
#include "compiler/token.h"
#include "compiler/type-hint.h"
#include "compiler/utils/string-utils.h"
#include "compiler/vertex.h"
Expand Down Expand Up @@ -659,6 +662,9 @@ VertexPtr GenTree::get_expr_top(bool was_arrow, const PhpDocComment *phpdoc) {
res = get_func_call<op_tuple, op_err>();
CE (!kphp_error(res.as<op_tuple>()->size(), "tuple() must have at least one argument"));
break;
case tok_match:
res = get_match();
break;
case tok_shape:
res = get_shape();
break;
Expand Down Expand Up @@ -1221,6 +1227,81 @@ VertexAdaptor<op_switch> GenTree::get_switch() {
return VertexUtil::create_switch_vertex(cur_function, switch_condition.set_location(location), std::move(cases)).set_location(location);
}


VertexAdaptor<op_match> GenTree::get_match() {
const auto location = auto_location();
next_cur();
CE(expect(tok_oppar, "'('"));
skip_phpdoc_tokens();
const auto match_condition = get_expression();
CE(!kphp_error(match_condition, "Failed to parse 'match' expression"));
CE(expect(tok_clpar, "')'"));

CE(expect(tok_opbrc, "'{'"));
std::vector<VertexPtr> cases;
while (cur->type() != tok_clbrc) {
skip_phpdoc_tokens();
if (cur->type() == tok_comma) {
next_cur();
continue;
}

if (cur->type() == tok_default) {
cases.emplace_back(get_match_default());
} else {
cases.emplace_back(get_match_case());
}
kphp_assert_msg(cases.back(), "Invalid 'match' case!");
}

CE(expect(tok_clbrc, "'}'"));

return VertexAdaptor<op_match>::create(match_condition, std::move(cases)).set_location(location);
}

VertexAdaptor<op_match_case> GenTree::get_match_case() {
const auto location = auto_location();
std::vector<VertexPtr> arms;

VertexPtr cur_expr;
for (cur_expr = get_expression(); cur_expr && cur_expr->type() != op_double_arrow; cur_expr = get_expression()) {
arms.emplace_back(cur_expr);

if (cur->type() == tok_comma) {
next_cur();
continue;
}
}

VertexPtr result;

// trailling comma: "fourty-two", => 42'
if (!cur_expr) {
CE(expect(tok_double_arrow, "'=>'"));
kphp_error(!arms.empty(), "Expected expression before '=>'");
result = get_expression();
} else if (const auto double_arrow = cur_expr.try_as<op_double_arrow>()) {
arms.emplace_back(double_arrow->key());
result = double_arrow->value();
} else {
kphp_fail_msg("Ivalid syntax of 'match' cases!");
}

return VertexAdaptor<op_match_case>::create(VertexAdaptor<op_seq_comma>::create(arms), result).set_location(location);
}

VertexAdaptor<op_match_default> GenTree::get_match_default() {
const auto location = auto_location();
next_cur();

// trailling comma: default, => 42'
if (cur->type() == tok_comma) {
next_cur();
}
CE(expect(tok_double_arrow, "'=>'"));
return VertexAdaptor<op_match_default>::create(get_expression()).set_location(location);
}

VertexAdaptor<op_shape> GenTree::get_shape() {
mkornaukhov03 marked this conversation as resolved.
Show resolved Hide resolved
auto location = auto_location();

Expand Down
3 changes: 3 additions & 0 deletions compiler/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ class GenTree {
VertexAdaptor<op_for> get_for();
VertexAdaptor<op_do> get_do();
VertexAdaptor<op_switch> get_switch();
VertexAdaptor<op_match> get_match();
VertexAdaptor<op_match_case> get_match_case();
VertexAdaptor<op_match_default> get_match_default();
VertexAdaptor<op_shape> get_shape();
VertexPtr get_by_name_construct();
VertexPtr get_member_by_name_after_var(VertexAdaptor<op_var> v_before);
Expand Down
1 change: 1 addition & 0 deletions compiler/keywords.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Array, tok_array
as, tok_as
case, tok_case
switch, tok_switch
match, tok_match
class, tok_class
interface, tok_interface
trait, tok_trait
Expand Down
27 changes: 21 additions & 6 deletions compiler/pipes/collect-main-edges.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "compiler/data/src-file.h"
#include "compiler/data/var-data.h"
#include "compiler/function-pass.h"
#include "compiler/vertex-meta_op_base.h"
#include "compiler/vertex-util.h"
#include "compiler/inferring/edge.h"
#include "compiler/inferring/ifi.h"
Expand All @@ -24,8 +25,9 @@ namespace {

SwitchKind get_switch_kind(VertexAdaptor<op_switch> s) {
int num_const_int_cases = 0;
int num_const_string_cases = 0;
int num_const_non_numeric_string_cases = 0;
int num_value_cases = 0;
int num_const_string_cases = 0;

for (auto one_case : s->cases()) {
if (one_case->type() == op_default) {
Expand All @@ -37,21 +39,26 @@ SwitchKind get_switch_kind(VertexAdaptor<op_switch> s) {
if (VertexUtil::is_const_int(val)) {
num_const_int_cases++;
} else if (auto as_string = val.try_as<op_string>()) {
// In case of op_switch node that was generated from op_match
num_const_string_cases++;
// PHP would use a numerical comparison for strings that look like a number,
// we shouldn't rewrite these switches as a string-only switch
if (!php_is_numeric(as_string->str_val.data())) {
num_const_string_cases++;
num_const_non_numeric_string_cases++;
}
}
}

if (num_value_cases == 0) {
return SwitchKind::EmptySwitch;
}

if (num_const_string_cases == num_value_cases) {
if (s->is_match && num_const_string_cases == num_value_cases) {
return SwitchKind::StringSwitch;
} else if (num_const_int_cases == num_value_cases) {
}
if (num_const_non_numeric_string_cases == num_value_cases) {
return SwitchKind::StringSwitch;
}
if (num_const_int_cases == num_value_cases) {
return SwitchKind::IntSwitch;
}
return SwitchKind::VarSwitch;
Expand Down Expand Up @@ -404,7 +411,15 @@ void CollectMainEdgesPass::on_switch(VertexAdaptor<op_switch> switch_op) {
// int-only and string-only switches separately (these simple switch statements
// form a majority of all switch statements)
switch_op->kind = get_switch_kind(switch_op);
if (switch_op->kind == SwitchKind::IntSwitch) {
if (switch_op->is_match) {
// in case of converted from op_match
create_set(as_lvalue(switch_op->condition_on_switch()->var_id), switch_op->condition());
for (const auto &c : switch_op->cases()) {
if (auto as_case = c.try_as<op_case>()) {
create_set(as_lvalue(switch_op->condition_on_switch()->var_id), as_case->expr());
}
}
} else if (switch_op->kind == SwitchKind::IntSwitch) {
// in case of int-only switch, condition var is discarded, so there is
// no real need in trying to insert an assignment node here
create_type_assign(as_lvalue(switch_op->condition_on_switch()), TypeData::get_type(tp_int));
Expand Down
71 changes: 70 additions & 1 deletion compiler/pipes/gen-tree-postprocess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

#include "compiler/pipes/gen-tree-postprocess.h"

#include <vector>

#include "compiler/compiler-core.h"
#include "compiler/data/class-data.h"
#include "compiler/data/lib-data.h"
#include "compiler/data/src-file.h"
#include "compiler/name-gen.h"
#include "compiler/vertex-util.h"

namespace {
Expand Down Expand Up @@ -247,6 +249,10 @@ VertexPtr GenTreePostprocessPass::on_exit_vertex(VertexPtr root) {
return convert_array_with_spread_operators(array);
}

if (auto match = root.try_as<op_match>()) {
return convert_match(match);
}

return root;
}

Expand Down Expand Up @@ -311,3 +317,66 @@ VertexPtr GenTreePostprocessPass::convert_array_with_spread_operators(VertexAdap

return call;
}

// convert op_match node to op_seq_rval like
// {
// var res;
// switch (...) {
// ...
// ... => { res = ...}
// ...
// }
// res;
// }
// we need this because `match` is expression
VertexPtr GenTreePostprocessPass::convert_match(VertexAdaptor<op_match> match_vertex) {
auto gen_superlocal = [&](const std::string& name_prefix) {
auto v = VertexAdaptor<op_var>::create().set_location(match_vertex);
v->str_val = gen_unique_name(name_prefix);
v->extra_type = op_ex_var_superlocal;
return v;
};

const auto tmp_result_var = gen_superlocal("tmp_result_of_match");
const auto match_condition = match_vertex->condition();
const auto tmp_condition_stored = gen_superlocal("tmp_condition_stored");
const auto matched_with_one = gen_superlocal("matched_with_one");
const auto cases = match_vertex->cases();

std::vector<VertexPtr> switch_arms;
bool has_default = false;
switch_arms.reserve(cases.size());

static const auto case_break = VertexAdaptor<op_break>::create(VertexUtil::create_int_const(1));

for (const auto match_case : cases) {
if (const auto ordinary_case = match_case.try_as<op_match_case>()) {
const auto set_result = VertexAdaptor<op_set>::create(tmp_result_var, ordinary_case->result_expr());
const auto case_body = VertexAdaptor<op_seq>::create(std::vector<VertexPtr>{set_result, case_break.clone()}).set_location(ordinary_case);
for (const auto case_condition : ordinary_case->conditions()->args()) {
switch_arms.emplace_back(VertexAdaptor<op_case>::create(case_condition, case_body));
}
}
else if (const auto default_case = match_case.try_as<op_match_default>()) {
has_default = true;
const auto set_result = VertexAdaptor<op_set>::create(tmp_result_var, default_case->result_expr());
const auto case_body = VertexAdaptor<op_seq>::create(std::vector<VertexPtr>{set_result, case_break.clone()}).set_location(default_case);
switch_arms.emplace_back(VertexAdaptor<op_default>::create(case_body));
}
kphp_error(vk::any_of_equal(match_case->type(), op_match_case, op_match_default), "Internal error: invalid case type in match expression");
}

if (!has_default) {
auto message = VertexAdaptor<op_string>::create();
message->str_val = "unhandled value in match!";
auto emit_error = VertexAdaptor<op_func_call>::create(std::vector{message});
emit_error->set_string("critical_error");
const auto case_body = VertexAdaptor<op_seq>::create(std::vector<VertexPtr>{emit_error, case_break.clone()});
switch_arms.emplace_back(VertexAdaptor<op_default>::create(case_body));
}

auto switch_vertex = VertexAdaptor<op_switch>::create(match_condition, tmp_condition_stored, matched_with_one, switch_arms);
switch_vertex->is_match = true;

return VertexAdaptor<op_seq_rval>::create(switch_vertex, tmp_result_var);
}
3 changes: 3 additions & 0 deletions compiler/pipes/gen-tree-postprocess.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ class GenTreePostprocessPass final : public FunctionPassBase {

// converts the spread operator (...$a) to a call to the array_merge_spread function
static VertexPtr convert_array_with_spread_operators(VertexAdaptor<op_array> array_vertex);
// converts match to a statement expression that contains a switch operator and temporary variable
static VertexPtr convert_match(VertexAdaptor<op_match> match_vertex);

};
1 change: 1 addition & 0 deletions compiler/token.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ enum TokenType {
tok_as,
tok_case,
tok_switch,
tok_match,
tok_class,
tok_interface,
tok_trait,
Expand Down
Loading