diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 190005bcc03..de9095aaff2 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -1117,16 +1117,11 @@ where } fn quote() -> impl NoirParser { - token_kind(TokenKind::Quote).validate(|token, span, emit| { - let tokens = match token { + token_kind(TokenKind::Quote).map(|token| { + ExpressionKind::Quote(match token { Token::Quote(tokens) => tokens, _ => unreachable!("token_kind(Quote) should guarantee parsing only a quote token"), - }; - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("quoted expressions"), - span, - )); - ExpressionKind::Quote(tokens) + }) }) } diff --git a/docs/docs/noir/concepts/data_types/slices.mdx b/docs/docs/noir/concepts/data_types/slices.mdx index d619117b799..95da2030843 100644 --- a/docs/docs/noir/concepts/data_types/slices.mdx +++ b/docs/docs/noir/concepts/data_types/slices.mdx @@ -197,7 +197,7 @@ fn main() { Applies a function to each element of the slice, returning a new slice containing the mapped elements. ```rust -fn map(self, f: fn(T) -> U) -> [U] +fn map(self, f: fn[Env](T) -> U) -> [U] ``` example @@ -213,7 +213,7 @@ Applies a function to each element of the slice, returning the final accumulated parameter is the initial value. ```rust -fn fold(self, mut accumulator: U, f: fn(U, T) -> U) -> U +fn fold(self, mut accumulator: U, f: fn[Env](U, T) -> U) -> U ``` This is a left fold, so the given function will be applied to the accumulator and first element of @@ -247,7 +247,7 @@ fn main() { Same as fold, but uses the first element as the starting element. ```rust -fn reduce(self, f: fn(T, T) -> T) -> T +fn reduce(self, f: fn[Env](T, T) -> T) -> T ``` example: @@ -260,12 +260,72 @@ fn main() { } ``` +### filter + +Returns a new slice containing only elements for which the given predicate returns true. + +```rust +fn filter(self, f: fn[Env](T) -> bool) -> Self +``` + +example: + +```rust +fn main() { + let slice = &[1, 2, 3, 4, 5]; + let odds = slice.filter(|x| x % 2 == 1); + assert_eq(odds, &[1, 3, 5]); +} +``` + +### join + +Flatten each element in the slice into one value, separated by `separator`. + +Note that although slices implement `Append`, `join` cannot be used on slice +elements since nested slices are prohibited. + +```rust +fn join(self, separator: T) -> T where T: Append +``` + +example: + +```rust +struct Accumulator { + total: Field, +} + +// "Append" two accumulators by adding them +impl Append for Accumulator { + fn empty() -> Self { + Self { total: 0 } + } + + fn append(self, other: Self) -> Self { + Self { total: self.total + other.total } + } +} + +fn main() { + let slice = &[1, 2, 3, 4, 5].map(|total| Accumulator { total }); + + let result = slice.join(Accumulator::empty()); + assert_eq(result, Accumulator { total: 15 }); + + // We can use a non-empty separator to insert additional elements to sum: + let separator = Accumulator { total: 10 }; + let result = slice.join(separator); + assert_eq(result, Accumulator { total: 55 }); +} +``` + ### all Returns true if all the elements satisfy the given predicate ```rust -fn all(self, predicate: fn(T) -> bool) -> bool +fn all(self, predicate: fn[Env](T) -> bool) -> bool ``` example: @@ -283,7 +343,7 @@ fn main() { Returns true if any of the elements satisfy the given predicate ```rust -fn any(self, predicate: fn(T) -> bool) -> bool +fn any(self, predicate: fn[Env](T) -> bool) -> bool ``` example: diff --git a/docs/docs/noir/standard_library/traits.md b/docs/docs/noir/standard_library/traits.md index 96a7b8e2f22..e6f6f80ff03 100644 --- a/docs/docs/noir/standard_library/traits.md +++ b/docs/docs/noir/standard_library/traits.md @@ -51,6 +51,7 @@ For primitive integer types, the return value of `default` is `0`. Container types such as arrays are filled with default values of their element type, except slices whose length is unknown and thus defaulted to zero. +--- ## `std::convert` @@ -85,6 +86,7 @@ For this reason, implementing `From` on a type will automatically generate a mat `Into` is most useful when passing function arguments where the types don't quite match up with what the function expects. In this case, the compiler has enough type information to perform the necessary conversion by just appending `.into()` onto the arguments in question. +--- ## `std::cmp` @@ -178,6 +180,8 @@ impl Ord for (A, B, C, D, E) where A: Ord, B: Ord, C: Ord, D: Ord, E: Ord { .. } ``` +--- + ## `std::ops` ### `std::ops::Add`, `std::ops::Sub`, `std::ops::Mul`, and `std::ops::Div` @@ -301,3 +305,29 @@ impl Shl for u16 { fn shl(self, other: u16) -> u16 { self << other } } impl Shl for u32 { fn shl(self, other: u32) -> u32 { self << other } } impl Shl for u64 { fn shl(self, other: u64) -> u64 { self << other } } ``` + +--- + +## `std::append` + +### `std::append::Append` + +`Append` can abstract over types that can be appended to - usually container types: + +#include_code append-trait noir_stdlib/src/append.nr rust + +`Append` requires two methods: + +- `empty`: Constructs an empty value of `Self`. +- `append`: Append two values together, returning the result. + +Additionally, it is expected that for any implementation: + +- `T::empty().append(x) == x` +- `x.append(T::empty()) == x` + +Implementations: +```rust +impl Append for [T] +impl Append for Quoted +``` diff --git a/noir_stdlib/src/append.nr b/noir_stdlib/src/append.nr new file mode 100644 index 00000000000..4577ae199b8 --- /dev/null +++ b/noir_stdlib/src/append.nr @@ -0,0 +1,35 @@ +// Appends two values together, returning the result. +// +// An alternate name for this trait is `Monoid` if that is familiar. +// If not, it can be ignored. +// +// It is expected that for any implementation: +// - `T::empty().append(x) == x` +// - `x.append(T::empty()) == x` +// docs:start:append-trait +trait Append { + fn empty() -> Self; + fn append(self, other: Self) -> Self; +} +// docs:end:append-trait + +impl Append for [T] { + fn empty() -> Self { + &[] + } + + fn append(self, other: Self) -> Self { + // Slices have an existing append function which this will resolve to. + self.append(other) + } +} + +impl Append for Quoted { + fn empty() -> Self { + quote {} + } + + fn append(self, other: Self) -> Self { + quote { $self $other } + } +} diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 65da7e6e9ab..ac53941e752 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -27,6 +27,7 @@ mod uint128; mod bigint; mod runtime; mod meta; +mod append; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident diff --git a/noir_stdlib/src/slice.nr b/noir_stdlib/src/slice.nr index 1a40abcf704..8d3f395f080 100644 --- a/noir_stdlib/src/slice.nr +++ b/noir_stdlib/src/slice.nr @@ -1,3 +1,5 @@ +use crate::append::Append; + impl [T] { #[builtin(array_len)] pub fn len(self) -> u32 {} @@ -85,6 +87,33 @@ impl [T] { accumulator } + // Returns a new slice containing only elements for which the given predicate + // returns true. + pub fn filter(self, predicate: fn[Env](T) -> bool) -> Self { + let mut ret = &[]; + for elem in self { + if predicate(elem) { + ret = ret.push_back(elem); + } + } + ret + } + + // Flatten each element in the slice into one value, separated by `separator`. + pub fn join(self, separator: T) -> T where T: Append { + let mut ret = T::empty(); + + if self.len() != 0 { + ret = self[0]; + + for i in 1..self.len() { + ret = ret.append(separator).append(self[i]); + } + } + + ret + } + // Returns true if all elements in the slice satisfy the predicate pub fn all(self, predicate: fn[Env](T) -> bool) -> bool { let mut ret = true; diff --git a/test_programs/compile_success_empty/slice_join/Nargo.toml b/test_programs/compile_success_empty/slice_join/Nargo.toml new file mode 100644 index 00000000000..44be002efb4 --- /dev/null +++ b/test_programs/compile_success_empty/slice_join/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "slice_join" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/slice_join/src/main.nr b/test_programs/compile_success_empty/slice_join/src/main.nr new file mode 100644 index 00000000000..217000694b0 --- /dev/null +++ b/test_programs/compile_success_empty/slice_join/src/main.nr @@ -0,0 +1,16 @@ +use std::append::Append; + +fn main() { + let slice = &[1, 2, 3, 4, 5]; + + let odds = slice.filter(|x| x % 2 == 1); + assert_eq(odds, &[1, 3, 5]); + + let odds_and_evens = append_three(odds, &[100], &[2, 4]); + assert_eq(odds_and_evens, &[1, 3, 5, 100, 2, 4]); +} + +fn append_three(one: T, two: T, three: T) -> T where T: Append { + // The `T::empty()`s here should do nothing + T::empty().append(one).append(two).append(three).append(T::empty()) +}