Skip to content

Commit 7941854

Browse files
committed
Add support for ufcs chaining
The chaining starts from function call `fun().gun().bun()` or from method call `o.fun().gun()`. It integrates chaining to part of the code that rewrite Cpp2 postfix operators to Cpp2 prefix operators. That gives possibility to use recurrency to go through entire expression and insert UFCS in parts that needs it. After this change the Cpp2 code (taken from `mixed-postfix-expression-custom-formatting.cpp2`) ```cpp test: (a:_) -> std::string = { return call( a, a.b(a.c)*++, "hello", /* polite greeting goes here */ " there", a.d.e( a.f*.g()++, // because f is foobar a.h.i(), a.j(a.k,a.l) ) ); } ``` will generate ```cpp [[nodiscard]] auto test(auto const& a) -> std::string{ return call( a, ++*cpp2::assert_not_null(CPP2_UFCS(b, a, a.c)), "hello", /* polite greeting goes here */ " there", CPP2_UFCS(e, a.d, ++CPP2_UFCS_0(g, *cpp2::assert_not_null(a.f)), // because f is foobar CPP2_UFCS_0(i, a.h), CPP2_UFCS(j, a, a.k, a.l))); } ``` Algorithm skips methods with more than one template argument (even when happen inside chaining - it is done in a way that compile fine). This Cpp2 code: ```cpp fun0()*.fun1(1)++.fun2<1,2>()++; ``` will generate: ```cpp ++(++CPP2_UFCS(fun1, *cpp2::assert_not_null(fun0()), (1))).fun2<1,2>(); ```
1 parent b59f539 commit 7941854

File tree

1 file changed

+97
-83
lines changed

1 file changed

+97
-83
lines changed

source/cppfront.cpp

Lines changed: 97 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,77 +1726,6 @@ class cppfront
17261726
captured_part += "_" + std::to_string(mynum);
17271727
}
17281728

1729-
// Check to see if it's just a function call with "." syntax,
1730-
// and if so use this path to convert it to UFCS
1731-
if (// there's a single-token expression followed by . and (
1732-
n.expr->get_token() && // if the base expression is a single token
1733-
std::ssize(n.ops) >= 2 && // and we're of the form:
1734-
n.ops[0].op->type() == lexeme::Dot && // token . id-expr ( expr-list )
1735-
n.ops[1].op->type() == lexeme::LeftParen &&
1736-
// alpha limitation: if it's a function call with more than one template argument (e.g., x.f<1,2>())
1737-
// the UFCS* macros can't handle that right now, so don't UFCS-size it
1738-
n.ops[0].id_expr->template_args_count() < 2 &&
1739-
// and either there's nothing after that, or there's just a $ after that
1740-
(
1741-
std::ssize(n.ops) == 2 ||
1742-
(std::ssize(n.ops) == 3 && n.ops[2].op->type() == lexeme::Dollar)
1743-
)
1744-
)
1745-
{
1746-
// If we already replaced this with a capture (which contains the UFCS
1747-
// work already done when the capture was computed), emit the capture
1748-
if (!captured_part.empty()) {
1749-
printer.print_cpp2(captured_part, n.position());
1750-
return;
1751-
}
1752-
1753-
// Otherwise, do the UFCS work...
1754-
1755-
// The . has its id_expr
1756-
assert (n.ops[0].id_expr);
1757-
1758-
// The ( has its expr_list and op_close
1759-
assert (n.ops[1].expr_list && n.ops[1].op_close);
1760-
1761-
//--------------------------------------------------------------------
1762-
// TODO: When MSVC supports __VA_OPT__ in standard mode without the
1763-
// experimental /Zc:preprocessor switch, use this single line
1764-
// instead of the dual lines below that special-case _0 args
1765-
// AND: Make the similarly noted change in cpp2util.h
1766-
//
1767-
//printer.print_cpp2("CPP2_UFCS(", n.position());
1768-
1769-
auto ufcs_string = std::string("CPP2_UFCS");
1770-
if (n.ops[0].id_expr->template_args_count() > 0) {
1771-
ufcs_string += "_TEMPLATE";
1772-
}
1773-
// If there are no additional arguments, use the _0 version
1774-
if (n.ops[1].expr_list->expressions.empty()) {
1775-
ufcs_string += "_0";
1776-
}
1777-
printer.print_cpp2(ufcs_string+"(", n.position());
1778-
//--------------------------------------------------------------------
1779-
1780-
// Make the "funcname" the first argument to CPP2_UFCS
1781-
emit(*n.ops[0].id_expr);
1782-
printer.print_cpp2(", ", n.position());
1783-
1784-
// Then make the base expression the second argument
1785-
emit(*n.expr);
1786-
1787-
// Then tack on any additional arguments
1788-
if (!n.ops[1].expr_list->expressions.empty()) {
1789-
printer.print_cpp2(", ", n.position());
1790-
push_need_expression_list_parens(false);
1791-
emit(*n.ops[1].expr_list);
1792-
pop_need_expression_list_parens();
1793-
}
1794-
printer.print_cpp2(")", n.position());
1795-
1796-
// And we're done. This path has handled this node, so return...
1797-
return;
1798-
}
1799-
18001729
// Otherwise, we're going to have to potentially do some work to change
18011730
// some Cpp2 postfix operators to Cpp1 prefix operators, so let's set up...
18021731
auto prefix = std::vector<text_with_pos>{};
@@ -1805,6 +1734,25 @@ class cppfront
18051734
auto last_was_prefixed = false;
18061735
auto saw_dollar = false;
18071736

1737+
auto args = std::optional<std::vector<text_with_pos>>{};
1738+
1739+
auto print_to_string = [&](auto& i, auto... args) {
1740+
auto print = std::string{};
1741+
printer.emit_to_string(&print);
1742+
emit(i, args...);
1743+
printer.emit_to_string();
1744+
return print;
1745+
};
1746+
auto print_to_text_chunks = [&](auto& i, auto... args) {
1747+
auto text = std::vector<text_with_pos>{};
1748+
printer.emit_to_text_chunks(&text);
1749+
push_need_expression_list_parens(false);
1750+
emit(i, args...);
1751+
pop_need_expression_list_parens();
1752+
printer.emit_to_text_chunks();
1753+
return text;
1754+
};
1755+
18081756
for (auto i = n.ops.rbegin(); i != n.ops.rend(); ++i)
18091757
{
18101758
assert(i->op);
@@ -1828,9 +1776,57 @@ class cppfront
18281776
}
18291777
}
18301778

1779+
// Going backwards if we found LeftParen it might be UFCS
1780+
// expr_list is emited to args variable for future use
1781+
if (i->op->type() == lexeme::LeftParen) {
1782+
1783+
args.emplace();
1784+
1785+
if (!i->expr_list->expressions.empty()) {
1786+
args.emplace(print_to_text_chunks(*i->expr_list));
1787+
}
1788+
1789+
}
1790+
// Going backwards if we found Dot and there is args variable
1791+
// it means that it should be handled by UFCS
1792+
else if( i->op->type() == lexeme::Dot && args
1793+
// don't use UFCS for methods with more than one template argument
1794+
&& i->id_expr && i->id_expr->template_args_count() < 2
1795+
)
1796+
{
1797+
auto funcname = print_to_string(*i->id_expr);
1798+
1799+
//--------------------------------------------------------------------
1800+
// TODO: When MSVC supports __VA_OPT__ in standard mode without the
1801+
// experimental /Zc:preprocessor switch, use this single line
1802+
// instead of the dual lines below that special-case _0 args
1803+
// AND: Make the similarly noted change in cpp2util.h
1804+
//
1805+
//printer.print_cpp2("CPP2_UFCS(", n.position());
1806+
1807+
auto ufcs_string = std::string("CPP2_UFCS");
1808+
if (i->id_expr->template_args_count() > 0) {
1809+
ufcs_string += "_TEMPLATE";
1810+
}
1811+
// If there are no additional arguments, use the _0 version
1812+
if (args.value().empty()) {
1813+
ufcs_string += "_0";
1814+
}
1815+
1816+
prefix.emplace_back(ufcs_string + "(" + funcname + ", ", i->op->position() );
1817+
suffix.emplace_back(")", i->op->position() );
1818+
if (!args.value().empty()) {
1819+
for (auto&& e: args.value()) {
1820+
suffix.push_back(e);
1821+
}
1822+
suffix.emplace_back(", ", i->op->position());
1823+
}
1824+
args.reset();
1825+
}
1826+
18311827
// Handle the Cpp2 postfix operators that are prefix in Cpp1
18321828
//
1833-
if (i->op->type() == lexeme::MinusMinus ||
1829+
else if (i->op->type() == lexeme::MinusMinus ||
18341830
i->op->type() == lexeme::PlusPlus ||
18351831
i->op->type() == lexeme::Multiply ||
18361832
i->op->type() == lexeme::Ampersand ||
@@ -1874,21 +1870,25 @@ class cppfront
18741870
}
18751871

18761872
if (i->id_expr) {
1877-
auto print = std::string{};
1878-
printer.emit_to_string(&print);
1879-
emit(*i->id_expr, false /*not a local name*/);
1880-
printer.emit_to_string();
1873+
1874+
if (args) {
1875+
// if args are stored it means that this is function or method
1876+
// that is not handled by UFCS e.g. that has more than one template argument
1877+
suffix.emplace_back(")", n.position());
1878+
for (auto&& e: args.value()) {
1879+
suffix.push_back(e);
1880+
}
1881+
suffix.emplace_back("(", n.position());
1882+
args.reset();
1883+
}
1884+
1885+
auto print = print_to_string(*i->id_expr, false /*not a local name*/);
18811886
suffix.emplace_back( print, i->id_expr->position() );
18821887
}
18831888

18841889
if (i->expr_list) {
1885-
auto text = std::vector<text_with_pos>{};
1886-
printer.emit_to_text_chunks(&text);
1887-
push_need_expression_list_parens(false);
1888-
emit(*i->expr_list);
1889-
pop_need_expression_list_parens();
1890-
printer.emit_to_text_chunks();
1891-
for (auto&& e: text) {
1890+
auto text = print_to_text_chunks(*i->expr_list);
1891+
for (auto&& e: text) {
18921892
suffix.push_back(e);
18931893
}
18941894
}
@@ -1904,6 +1904,8 @@ class cppfront
19041904
}
19051905
}
19061906

1907+
1908+
19071909
// Print the prefixes (in forward order)
19081910
for (auto& e : prefix) {
19091911
printer.print_cpp2(e.text, n.position());
@@ -1928,6 +1930,18 @@ class cppfront
19281930
}
19291931
suppress_move_from_last_use = false;
19301932

1933+
if (args) {
1934+
// if after printing core expression args is defined
1935+
// it means that the chaining started by function call
1936+
// we need to print its arguments
1937+
suffix.emplace_back(")", n.position());
1938+
for (auto&& e: args.value()) {
1939+
suffix.push_back(e);
1940+
}
1941+
suffix.emplace_back("(", n.position());
1942+
args.reset();
1943+
}
1944+
19311945
// Print the suffixes (in reverse order)
19321946
while (!suffix.empty()) {
19331947
printer.print_cpp2(suffix.back().text, suffix.back().pos);

0 commit comments

Comments
 (0)