Skip to content

Initial range operators implementation #1172

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

Merged
merged 2 commits into from
Jul 22, 2024
Merged
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
20 changes: 8 additions & 12 deletions docs/cpp2/common.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ Cpp2 supports using Cpp1 user-defined literals for compatibility, to support sea

Both **`123.nm()`** and **`123.u8()`** are very similar to user-defined literal syntax, and more general.


## <a id="operators"></a> Operators

Operators have the same precedence and associativity as in Cpp1, but some unary operators that are prefix (always or sometimes) in Cpp1 are postfix (always) in Cpp2.
Expand All @@ -188,7 +189,7 @@ if !vec.empty() {
| `+` | `#!cpp +100` | `#!cpp +100` |
| `-` | `#!cpp -100` | `#!cpp -100` |

The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. For example:
The operators `.`, `..`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, `...`, `..=`, and `$` are postfix. For example:

``` cpp title="Using postfix operators"
// Cpp1 examples, from cppfront's own source code:
Expand All @@ -201,7 +202,7 @@ The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. F

Postfix notation lets the code read fluidly left-to-right, in the same order in which the operators will be applied, and lets declaration syntax be consistent with usage syntax. For more details, see [Design note: Postfix operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators).

> Note: The function call syntax `f(x)` calls a namespace-scope function, or a function object, named `f`. The function call syntax `x.f()` is a unified function call syntax (aka UFCS) that calls a type-scope function in the type of `x` if available, otherwise calls the same as `f(x)`. For details, see [Design note: UFCS](https://github.com/hsutter/cppfront/wiki/Design-note%3A-UFCS).
> Note: The function call syntax `f(x)` calls a namespace-scope function, or a function object, named `f`. The function call syntax `x.f()` is a unified function call syntax (aka UFCS) that calls a type-scope function in the type of `x` if available, otherwise calls the same as `f(x)`. The function call syntax `x..f()` calls a type-scope function only. For details, see [Design note: UFCS](https://github.com/hsutter/cppfront/wiki/Design-note%3A-UFCS).

| Unary operator | Cpp2 example | Cpp1 equivalent |
|---|---|---|
Expand All @@ -213,18 +214,13 @@ Postfix notation lets the code read fluidly left-to-right, in the same order in
| `#!cpp --` | `#!cpp iter--` | `#!cpp --iter` |
| `(` `)` | `#!cpp f( 1, 2, 3)` | `#!cpp f( 1, 2, 3)` |
| `[` `]` | `#!cpp vec[123]` | `#!cpp vec[123]` |
| `$` | `val$` | _reflection — no Cpp1 equivalent yet_ |

> Because `++` and `--` always have in-place update semantics, we never need to remember "use prefix `++`/`--` unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling `++`/`--`.
| `...` (half-open range operator) | `#!cpp v.begin()...v.end()` | `#!cpp std::ranges::subrange(v.begin(), v.end())` |
| `..=` (closed range operator) | `#!cpp 1..=10` | `#!cpp std::views::iota(1, 11)` |
| `$` (capture operator) | `val$` | _reflection — no Cpp1 equivalent yet_ |

Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~` are used as binary operators they must be preceded by whitespace. For example:

| Unary postfix operators that<br>are also binary operators | Cpp2 example | Cpp1 equivalent |
|---|---|---|
| `#!cpp *` | `#!cpp pobj* * 42` | `#!cpp (*pobj)*42` |
| `#!cpp &` | `#!cpp obj& & mask` <p> (note: allowed in unsafe code only) | `#!cpp &obj & mask` |
> Note: The `...` pack expansion syntax is also supported. The above `...` and `..=` are the Cpp2 range operators, which overlap in syntax.

For more details, see [Design note: Postfix unary operators vs binary operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-unary-operators-vs-binary-operators).
> Note: Because `++` and `--` always have in-place update semantics, we never need to remember "use prefix `++`/`--` unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling `++`/`--`.


### <a id="binary-operators"></a> Binary operators
Expand Down
39 changes: 38 additions & 1 deletion docs/cpp2/expressions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

# Common expressions

## <a id="ufcs"></a> Calling functions: `f(x)` syntax, and `x.f()` UFCS syntax
## <a id="ufcs"></a> Calling functions: `f(x)` syntax, `x.f()` UFCS syntax, and `x..f()` members-only syntax

A function argument list is a [list](common.md#lists) of arguments enclosed by `(` `)` parentheses.

Expand All @@ -11,6 +11,8 @@ A function call like `x.f()` is a unified function call syntax (aka UFCS) call.

An operator notation call like `#!cpp a + b` will call an overloaded operator function if one is available, as usual in C++.

A function call like `x..f()` will consider only member functions.

For example:

``` cpp title="Function calls" hl_lines="3 7 11 16 19 20"
Expand Down Expand Up @@ -221,6 +223,41 @@ test(42);
For more examples, see also the examples in the previous two sections on `is` and `as`, many of which use `inspect`.


## <a id="ranges"></a> `...` and `..=` — range operators

`...` and `..=` designate a range of things. In addition to using `...` for variadic parameters, variadic pack expansion, and fold expressions as in Cpp1, Cpp2 also supports using `begin...end` for a half-open range (that does not include `end`) and `first..=last` for a closed range (that does include `last`).

For example:

``` cpp title="Using ... and ..= for ranges" hl_lines="4,11"
test: (v: std::vector<std::string>) =
{
// Print strings from "Nonesuch" (if present) onward
i1 := v.std::ranges::find("Nonesuch");
for i1 ... v.end() do (e) {
std::cout << " (e*)$\n";
}

if v.ssize() > 2 {
// Print indexes 1 and 2 of v
for 1 ..= 2 do (e) {
std::cout << " (e)$ (v[e])$\n";
}
}
}

main: () = {
vec: std::vector<std::string> = ("Beholder", "Grue", "Nonesuch", "Wumpus");
test( vec );
}
// Prints:
// Nonesuch
// Wumpus
// 1 Grue
// 2 Nonesuch
```


## <a id="captures"></a> `$` — captures, including interpolations

Suffix `$` is pronounced **"paste the value of"** and captures the value of an expression at the point when the expression where the capture is written is evaluated. Depending on the complexity of the capture expression `expr$` and where it is used, parentheses `(expr)$` may be required for precedence or to show the boundaries of the expression.
Expand Down
86 changes: 79 additions & 7 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -2046,12 +2046,15 @@ constexpr auto unsafe_narrow( X&& x ) noexcept -> decltype(auto)
//
// Does not perform any dynamic memory allocation - each string_view
// is directly bound to the string provided by the host environment
//
// Note: These string_views happen to be null-terminated. We ought
// to also have a std::zstring_view to express that...
//
//-----------------------------------------------------------------------
//
struct args_t
struct args
{
args_t(int c, char** v) : argc{c}, argv{v} {}
args(int c, char** v) : argc{c}, argv{v} {}

class iterator {
public:
Expand Down Expand Up @@ -2084,24 +2087,93 @@ struct args_t
auto end() const -> iterator { return iterator{ argc, argv, argc }; }
auto cbegin() const -> iterator { return begin(); }
auto cend() const -> iterator { return end(); }
auto size() const -> std::size_t { return cpp2::unsafe_narrow<std::size_t>(argc); }
auto size() const -> std::size_t { return cpp2::unsafe_narrow<std::size_t>(ssize()); }
auto ssize() const -> int { return argc; }

auto operator[](int i) const {
if (0 <= i && i < argc) { return std::string_view{ argv[i] }; }
else { return std::string_view{}; }
if (0 <= i && i < ssize()) { return std::string_view{ argv[i] }; }
else { return std::string_view{}; }
}

mutable int argc = 0; // mutable for compatibility with frameworks that take 'int& argc'
char** argv = nullptr;
};

inline auto make_args(int argc, char** argv) -> args_t
inline auto make_args(int argc, char** argv) -> args
{
return args_t{argc, argv};
return args{argc, argv};
}


//-----------------------------------------------------------------------
//
// range: a range of [begin, end) or [first, last]
//
//-----------------------------------------------------------------------
//
template<typename T>
struct range
{
range(
T const& f,
T const& l,
bool include_last = false
)
: first{ f }
, last{ l }
{
if (include_last) {
++last;
}
}

class iterator {
public:
iterator(T const& f, T const& l, T start) : first{ f }, last{ l }, curr{ start } {}

auto operator*() const {
if (curr != last) { return curr; }
else { return T{}; }
}

auto operator+(int i) -> iterator {
if (i > 0) { return { first, last, std::min(curr + i, last) }; }
else { return { first, last, std::max(curr + i, 0) }; }
}
auto operator-(int i) -> iterator { return operator+(-i); }
auto operator++() -> iterator& { if (curr != last ) { ++curr; } return *this; }
auto operator--() -> iterator& { if (curr != first) { --curr; } return *this; }
auto operator++(int) -> iterator { auto old = *this; ++*this; return old; }
auto operator--(int) -> iterator { auto old = *this; ++*this; return old; }

auto operator<=>(iterator const&) const = default;

private:
T first;
T last;
T curr;
};

auto begin() const -> iterator { return iterator{ first, last, first }; }
auto end() const -> iterator { return iterator{ first, last, last }; }
auto cbegin() const -> iterator { return begin(); }
auto cend() const -> iterator { return end(); }
auto size() const -> std::size_t { return cpp2::unsafe_narrow<std::size_t>(ssize()); }
auto ssize() const -> int { return last - first; }

auto operator[](int i) const {
if (0 <= i && i < ssize()) { return first + i; }
else { return T{}; }
}

T first;
T last;
};

template<class T, class U>
range(T, U, bool = false) -> range<std::common_type_t<T, U>>;


//-----------------------------------------------------------------------
//
// alien_memory: memory typed as T but that is outside C++ and that the
Expand Down
24 changes: 24 additions & 0 deletions regression-tests/pure2-range-operators.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
main: () = {

v: std::vector =
( "Aardvark", "Baboon", "Cat", "Dolphin", "Elephant", "Flicker", "Grue", "Wumpus" );

std::cout << "We have some alpabetical animals:\n";
for v.begin()...v.end() do (e) {
std::cout << " (e*)$\n";
}

std::cout << "\nAnd from indexes 1..=5 they are:\n";
for 1..=5 do (e) {
std::cout << " (e)$ (v[e])$\n";
}

all_about: std::list =
( "Hokey", "Pokey" );

std::cout << "\nMake sure non-random-access iterators work:\n";
for all_about.begin()...all_about.end() do (e) {
std::cout << " (e*)$\n";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
We have some alpabetical animals:
Aardvark
Baboon
Cat
Dolphin
Elephant
Flicker
Grue
Wumpus

And from indexes 1..=5 they are:
1 Baboon
2 Cat
3 Dolphin
4 Elephant
5 Flicker

Make sure non-random-access iterators work:
Hokey
Pokey
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
We have some alpabetical animals:
Aardvark
Baboon
Cat
Dolphin
Elephant
Flicker
Grue
Wumpus

And from indexes 1..=5 they are:
1 Baboon
2 Cat
3 Dolphin
4 Elephant
5 Flicker

Make sure non-random-access iterators work:
Hokey
Pokey
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
We have some alpabetical animals:
Aardvark
Baboon
Cat
Dolphin
Elephant
Flicker
Grue
Wumpus

And from indexes 1..=5 they are:
1 Baboon
2 Cat
3 Dolphin
4 Elephant
5 Flicker

Make sure non-random-access iterators work:
Hokey
Pokey
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
We have some alpabetical animals:
Aardvark
Baboon
Cat
Dolphin
Elephant
Flicker
Grue
Wumpus

And from indexes 1..=5 they are:
1 Baboon
2 Cat
3 Dolphin
4 Elephant
5 Flicker

Make sure non-random-access iterators work:
Hokey
Pokey
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pure2-range-operators.cpp
45 changes: 45 additions & 0 deletions regression-tests/test-results/pure2-range-operators.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

#define CPP2_IMPORT_STD Yes

//=== Cpp2 type declarations ====================================================


#include "cpp2util.h"

#line 1 "pure2-range-operators.cpp2"


//=== Cpp2 type definitions and function declarations ===========================

#line 1 "pure2-range-operators.cpp2"
auto main() -> int;

//=== Cpp2 function definitions =================================================

#line 1 "pure2-range-operators.cpp2"
auto main() -> int{

#line 3 "pure2-range-operators.cpp2"
std::vector v {
"Aardvark", "Baboon", "Cat", "Dolphin", "Elephant", "Flicker", "Grue", "Wumpus"};

std::cout << "We have some alpabetical animals:\n";
for ( auto const& e : cpp2::range(CPP2_UFCS(begin)(v),CPP2_UFCS(end)(v)) ) {
std::cout << " " + cpp2::to_string(*cpp2::impl::assert_not_null(e)) + "\n";
}

std::cout << "\nAnd from indexes 1..=5 they are:\n";
for ( auto const& e : cpp2::range(1,5,true) ) {
std::cout << " " + cpp2::to_string(e) + " " + cpp2::to_string(CPP2_ASSERT_IN_BOUNDS(v, e)) + "\n";
}

std::list all_about {
"Hokey", "Pokey"};

std::cout << "\nMake sure non-random-access iterators work:\n";
for ( auto const& e : cpp2::range(CPP2_UFCS(begin)(all_about),CPP2_UFCS(end)(cpp2::move(all_about))) ) {
std::cout << " " + cpp2::to_string(*cpp2::impl::assert_not_null(e)) + "\n";
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pure2-range-operators.cpp2... ok (all Cpp2, passes safety checks)

Loading
Loading