From 9c0c0e17cb4a9189095f43db561bb25ecc833550 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 5 Jul 2024 13:03:20 -0500 Subject: [PATCH 1/5] Add more slice methods --- compiler/noirc_frontend/src/parser/parser.rs | 11 +-- docs/docs/noir/concepts/data_types/slices.mdx | 70 +++++++++++++++++-- noir_stdlib/src/append.nr | 33 +++++++++ noir_stdlib/src/lib.nr | 1 + noir_stdlib/src/slice.nr | 29 ++++++++ .../slice_join/Nargo.toml | 7 ++ .../slice_join/src/main.nr | 16 +++++ 7 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 noir_stdlib/src/append.nr create mode 100644 test_programs/compile_success_empty/slice_join/Nargo.toml create mode 100644 test_programs/compile_success_empty/slice_join/src/main.nr diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index afeee889ede..e008d4a6b28 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/noir_stdlib/src/append.nr b/noir_stdlib/src/append.nr new file mode 100644 index 00000000000..6ffee6ad8f3 --- /dev/null +++ b/noir_stdlib/src/append.nr @@ -0,0 +1,33 @@ +// 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` +trait Append { + fn empty() -> Self; + fn append(self, other: Self) -> Self; +} + +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..bbb6924a53c 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..4315544ccee --- /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 last .append(T::empty()) should do nothing + one.append(two).append(three).append(T::empty()) +} From bdc8dff52299cf6d490ab413fc9fdaac265849bd Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 5 Jul 2024 13:11:05 -0500 Subject: [PATCH 2/5] Add append docs --- docs/docs/noir/standard_library/append.md | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 docs/docs/noir/standard_library/append.md diff --git a/docs/docs/noir/standard_library/append.md b/docs/docs/noir/standard_library/append.md new file mode 100644 index 00000000000..9740f30b9f4 --- /dev/null +++ b/docs/docs/noir/standard_library/append.md @@ -0,0 +1,38 @@ +--- +title: Append Trait +description: + The Append Trait abstracts over types that can be appended to +keywords: [append, trait] +--- + +`Append` can abstract over types that can be appended to - usually container types: + +```rs +trait Append { + fn empty() -> Self; + + fn append(self, other: Self) -> Self; +} +``` + +`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 + +```rs +impl Append for [T] +``` + +```rs +impl Append for Quoted +``` From 4ab353d8643d4c0e258cc86ae26640d0258a333e Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 5 Jul 2024 14:53:46 -0500 Subject: [PATCH 3/5] Move Append docs to traits.md --- docs/docs/noir/standard_library/append.md | 38 ----------------------- docs/docs/noir/standard_library/traits.md | 30 ++++++++++++++++++ noir_stdlib/src/append.nr | 2 ++ 3 files changed, 32 insertions(+), 38 deletions(-) delete mode 100644 docs/docs/noir/standard_library/append.md diff --git a/docs/docs/noir/standard_library/append.md b/docs/docs/noir/standard_library/append.md deleted file mode 100644 index 9740f30b9f4..00000000000 --- a/docs/docs/noir/standard_library/append.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Append Trait -description: - The Append Trait abstracts over types that can be appended to -keywords: [append, trait] ---- - -`Append` can abstract over types that can be appended to - usually container types: - -```rs -trait Append { - fn empty() -> Self; - - fn append(self, other: Self) -> Self; -} -``` - -`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 - -```rs -impl Append for [T] -``` - -```rs -impl Append for Quoted -``` 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 index 6ffee6ad8f3..4577ae199b8 100644 --- a/noir_stdlib/src/append.nr +++ b/noir_stdlib/src/append.nr @@ -6,10 +6,12 @@ // 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 { From 2cab600dff46a32cb4ff8b6a59b6aa18b2221a40 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 5 Jul 2024 14:55:38 -0500 Subject: [PATCH 4/5] Add T::empty to beginning --- test_programs/compile_success_empty/slice_join/src/main.nr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_programs/compile_success_empty/slice_join/src/main.nr b/test_programs/compile_success_empty/slice_join/src/main.nr index 4315544ccee..217000694b0 100644 --- a/test_programs/compile_success_empty/slice_join/src/main.nr +++ b/test_programs/compile_success_empty/slice_join/src/main.nr @@ -11,6 +11,6 @@ fn main() { } fn append_three(one: T, two: T, three: T) -> T where T: Append { - // The last .append(T::empty()) should do nothing - one.append(two).append(three).append(T::empty()) + // The `T::empty()`s here should do nothing + T::empty().append(one).append(two).append(three).append(T::empty()) } From c8bed04c57e7eadf489355f4f2116192ed0a1f18 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 5 Jul 2024 15:04:40 -0500 Subject: [PATCH 5/5] Format stdlib --- noir_stdlib/src/slice.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noir_stdlib/src/slice.nr b/noir_stdlib/src/slice.nr index bbb6924a53c..8d3f395f080 100644 --- a/noir_stdlib/src/slice.nr +++ b/noir_stdlib/src/slice.nr @@ -106,7 +106,7 @@ impl [T] { if self.len() != 0 { ret = self[0]; - for i in 1 .. self.len() { + for i in 1..self.len() { ret = ret.append(separator).append(self[i]); } }