From 25db6164c8b61acc637803839f08b35f6a10db85 Mon Sep 17 00:00:00 2001 From: grasshopper47 Date: Sat, 25 Nov 2023 14:53:05 +0100 Subject: [PATCH 1/6] Extend ForeignCalls with print method --- .../src/monomorphization/mod.rs | 3 +- noir_stdlib/src/lib.nr | 12 ++++- tooling/nargo/src/ops/foreign_calls.rs | 46 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 57e4e6cdeb0..d3fb0d1ee12 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -883,7 +883,8 @@ impl<'interner> Monomorphizer<'interner> { if let ast::Expression::Ident(ident) = original_func.as_ref() { if let Definition::Oracle(name) = &ident.definition { - if name.as_str() == "println" { + let name_str = name.as_str(); + if (name_str == "println") | (name_str == "print") { // 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 self.append_printable_type_info(&hir_arguments[0], &mut arguments); diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 8d878eecbb3..f271a2927ce 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -19,8 +19,9 @@ mod compat; mod option; mod string; mod test; + // 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 +// Thus, the only argument to the `println` oracle is expected to always be an ident #[oracle(println)] unconstrained fn println_oracle(_input: T) {} @@ -28,6 +29,13 @@ unconstrained pub fn println(input: T) { println_oracle(input); } +#[oracle(print)] +unconstrained fn print_oracle(_input: T) {} + +unconstrained pub fn print(input: T) { + print_oracle(input); +} + #[foreign(recursive_aggregation)] pub fn verify_proof( _verification_key: [Field], @@ -36,10 +44,12 @@ pub fn verify_proof( _key_hash: Field, _input_aggregation_object: [Field; N] ) -> [Field; N] {} + // Asserts that the given value is known at compile-time. // Useful for debugging for-loop bounds. #[builtin(assert_constant)] pub fn assert_constant(_x: T) {} + // from_field and as_field are private since they are not valid for every type. // `as` should be the default for users to cast between primitive types, and in the future // traits can be used to work with generic types. diff --git a/tooling/nargo/src/ops/foreign_calls.rs b/tooling/nargo/src/ops/foreign_calls.rs index 1ca270a5bf7..9014e839276 100644 --- a/tooling/nargo/src/ops/foreign_calls.rs +++ b/tooling/nargo/src/ops/foreign_calls.rs @@ -2,6 +2,7 @@ use acvm::{ acir::brillig::{ForeignCallParam, ForeignCallResult, Value}, pwg::ForeignCallWaitInfo, }; +use iter_extended::vecmap; use noirc_printable_type::{decode_string_value, ForeignCallError, PrintableValueDisplay}; pub trait ForeignCallExecutor { @@ -15,6 +16,9 @@ pub trait ForeignCallExecutor { /// After resolution of a foreign call, nargo will restart execution of the ACVM pub(crate) enum ForeignCall { Println, + Print, + Sequence, + ReverseSequence, CreateMock, SetMockParams, SetMockReturns, @@ -32,6 +36,9 @@ impl ForeignCall { pub(crate) fn name(&self) -> &'static str { match self { ForeignCall::Println => "println", + ForeignCall::Print => "print", + ForeignCall::Sequence => "get_number_sequence", + ForeignCall::ReverseSequence => "get_reverse_number_sequence", ForeignCall::CreateMock => "create_mock", ForeignCall::SetMockParams => "set_mock_params", ForeignCall::SetMockReturns => "set_mock_returns", @@ -43,6 +50,9 @@ impl ForeignCall { pub(crate) fn lookup(op_name: &str) -> Option { match op_name { "println" => Some(ForeignCall::Println), + "print" => Some(ForeignCall::Print), + "get_number_sequence" => Some(ForeignCall::Sequence), + "get_reverse_number_sequence" => Some(ForeignCall::ReverseSequence), "create_mock" => Some(ForeignCall::CreateMock), "set_mock_params" => Some(ForeignCall::SetMockParams), "set_mock_returns" => Some(ForeignCall::SetMockReturns), @@ -125,6 +135,12 @@ impl DefaultForeignCallExecutor { println!("{display_values}"); Ok(()) } + + fn execute_print(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> { + let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?; + print!("{display_values}"); + Ok(()) + } } impl ForeignCallExecutor for DefaultForeignCallExecutor { @@ -140,6 +156,36 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { } Ok(ForeignCallResult { values: vec![] }) } + Some(ForeignCall::Print) => { + if self.show_output { + Self::execute_print(&foreign_call.inputs)?; + } + Ok(ForeignCallResult { values: vec![] }) + } + Some(ForeignCall::Sequence) => { + let sequence_length: u128 = + foreign_call.inputs[0].unwrap_value().to_field().to_u128(); + let sequence = vecmap(0..sequence_length, Value::from); + + Ok(ForeignCallResult { + values: vec![ + ForeignCallParam::Single(sequence_length.into()), + ForeignCallParam::Array(sequence), + ], + }) + } + Some(ForeignCall::ReverseSequence) => { + let sequence_length: u128 = + foreign_call.inputs[0].unwrap_value().to_field().to_u128(); + let sequence = vecmap((0..sequence_length).rev(), Value::from); + + Ok(ForeignCallResult { + values: vec![ + ForeignCallParam::Single(sequence_length.into()), + ForeignCallParam::Array(sequence), + ], + }) + } Some(ForeignCall::CreateMock) => { let mock_oracle_name = Self::parse_string(&foreign_call.inputs[0]); assert!(ForeignCall::lookup(&mock_oracle_name).is_none()); From 317446253b7960d5fb7e68c70e3fecc0979daea2 Mon Sep 17 00:00:00 2001 From: grasshopper47 Date: Mon, 27 Nov 2023 20:10:12 +0100 Subject: [PATCH 2/6] Add a print statement alongisde println in tests --- .../execution_success/strings/src/main.nr | 14 ++++++++++++-- tooling/nargo_fmt/tests/expected/print.nr | 1 + tooling/nargo_fmt/tests/expected/print2.nr | 3 ++- tooling/nargo_fmt/tests/input/print.nr | 5 ++++- tooling/nargo_fmt/tests/input/print2.nr | 5 +++-- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/test_programs/execution_success/strings/src/main.nr b/test_programs/execution_success/strings/src/main.nr index cb2218837f3..0ba13b13724 100644 --- a/test_programs/execution_success/strings/src/main.nr +++ b/test_programs/execution_success/strings/src/main.nr @@ -10,20 +10,26 @@ fn main(message: pub str<11>, y: Field, hex_as_string: str<4>, hex_as_field: Fie let x = 10; let z = x * 5; std::println(10); + std::print(10); std::println(z); // x * 5 in println not yet supported + std::print(z); std::println(x); + std::print(x); let array = [1, 2, 3, 5, 8]; assert(y == 5); // Change to y != 5 to see how the later print statements are not called std::println(array); + std::print(array); bad_message = "hell\0\"world"; std::println(bad_message); + std::print(bad_message); assert(message != bad_message); let hash = std::hash::pedersen_commitment([x]); std::println(hash); + std::print(hash); assert(hex_as_string == "0x41"); // assert(hex_as_string != 0x41); This will fail with a type mismatch between str[4] and Field @@ -36,6 +42,10 @@ fn test_prints_strings() { std::println(message); std::println("goodbye world"); + + std::print(message); + std::print("\n"); + std::print("goodbye world\n"); } #[test] @@ -52,8 +62,8 @@ fn test_prints_array() { } fn failed_constraint(hex_as_field: Field) { - // TODO(#2116): Note that `println` will not work if a failed constraint can be - // evaluated at compile time. + // TODO(#2116): Note that `println` will not work if a failed constraint can be + // evaluated at compile time. // When this method is called from a test method or with constant values // a `Failed constraint` compile error will be caught before this `println` // is executed as the input will be a constant. diff --git a/tooling/nargo_fmt/tests/expected/print.nr b/tooling/nargo_fmt/tests/expected/print.nr index e169f565455..3bce0941da2 100644 --- a/tooling/nargo_fmt/tests/expected/print.nr +++ b/tooling/nargo_fmt/tests/expected/print.nr @@ -1,5 +1,6 @@ use dep::std; fn main() { + std::print("Hello world"); std::println("Hello world"); } diff --git a/tooling/nargo_fmt/tests/expected/print2.nr b/tooling/nargo_fmt/tests/expected/print2.nr index 80284444af8..3bce0941da2 100644 --- a/tooling/nargo_fmt/tests/expected/print2.nr +++ b/tooling/nargo_fmt/tests/expected/print2.nr @@ -1,5 +1,6 @@ use dep::std; -fn main( ) { +fn main() { + std::print("Hello world"); std::println("Hello world"); } diff --git a/tooling/nargo_fmt/tests/input/print.nr b/tooling/nargo_fmt/tests/input/print.nr index 8afa562dada..3bce0941da2 100644 --- a/tooling/nargo_fmt/tests/input/print.nr +++ b/tooling/nargo_fmt/tests/input/print.nr @@ -1,3 +1,6 @@ use dep::std; -fn main() { std::println("Hello world"); } +fn main() { + std::print("Hello world"); + std::println("Hello world"); +} diff --git a/tooling/nargo_fmt/tests/input/print2.nr b/tooling/nargo_fmt/tests/input/print2.nr index 07ef9dd0386..3bce0941da2 100644 --- a/tooling/nargo_fmt/tests/input/print2.nr +++ b/tooling/nargo_fmt/tests/input/print2.nr @@ -1,5 +1,6 @@ use dep::std; -fn main( ) { -std::println("Hello world"); +fn main() { + std::print("Hello world"); + std::println("Hello world"); } From ec1acb41c040ea964c8c6b2df2c5b2db02d1bd8d Mon Sep 17 00:00:00 2001 From: grasshopper47 Date: Mon, 27 Nov 2023 20:11:38 +0100 Subject: [PATCH 3/6] Refactor to mention print method --- .../standard_library/logging.md | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/versioned_docs/version-v0.19.3/standard_library/logging.md b/docs/versioned_docs/version-v0.19.3/standard_library/logging.md index 7e2fd9b9aff..d85b6cc7e50 100644 --- a/docs/versioned_docs/version-v0.19.3/standard_library/logging.md +++ b/docs/versioned_docs/version-v0.19.3/standard_library/logging.md @@ -7,6 +7,7 @@ keywords: [ noir logging, println statement, + print statement, debugging in noir, noir std library, logging tutorial, @@ -17,14 +18,13 @@ keywords: ] --- -The standard library provides a familiar `println` statement you can use. Despite being a limited -implementation of rust's `println!` macro, this construct can be useful for debugging. +The standard library provides two familiar statements you can use: `println` and `print`. Despite being a limited implementation of rust's `println!` and `print!` macros, these constructs can be useful for debugging. -You can print the output of println statements in your Noir code by using the `nargo execute` command or the `--show-output` flag when using `nargo test` (provided there are println statements in your tests). +You can print the output of both statements in your Noir code by using the `nargo execute` command or the `--show-output` flag when using `nargo test` (provided there are print statements in your tests). -It is recommended to use `nargo execute` if you want to debug failing constrains with `println` statements. This is due to every input in a test being a constant rather than a witness, so we issue an error during compilation while we only print during execution (which comes after compilation). `println` will not work for failed constraints caught at compile time. +It is recommended to use `nargo execute` if you want to debug failing constrains with `println` or `print` statements. This is due to every input in a test being a constant rather than a witness, so we issue an error during compilation while we only print during execution (which comes after compilation). Neither `println`, nor `print` are callable for failed constraints caught at compile time. -The `println` statement is unconstrained, so it works for outputting integers, fields, strings, and even structs or expressions. For example: +Both `print` and `println` statements have the `unconstrained` attribute, enabling them to output integers, fields, strings, and even structs or expressions. For example: ```rust use dep::std; @@ -40,10 +40,9 @@ fn main(age : Field, height : Field) { std::println(age + height); std::println("Hello world!"); } - ``` -You can print multiple different types in the same statement and string as well as a new "fmtstr" type. A `fmtstr` can be specified in the same way as a normal string it just should be prepended with an "f" character: +You can print different types in the same statement (including strings) with a type called `fmtstr`. It can be specified in the same way as a normal string, just prepended with an "f" character: ```rust let fmt_str = f"i: {i}, j: {j}"; @@ -60,3 +59,17 @@ You can print multiple different types in the same statement and string as well let foo = fooStruct { my_struct: s, foo: 15 }; std::println(f"s: {s}, foo: {foo}"); ``` + +Examples shown above are interchangeable between the two `print` statements: + +```rust +let person = Person { age : age, height : height }; + +std::println(person); +std::print(person); + +std::println("Hello world!"); // Prints with a newline at the end of the input +std::print("Hello world!"); // Prints the input and keeps cursor on the same +``` + +`println` is favored over `print`, the latter being present for narrower use cases, such as printing collection elements on the same line. From 0fb434060dcc4f114e4e5648bbb7377bc1c76170 Mon Sep 17 00:00:00 2001 From: grasshopper47 Date: Mon, 27 Nov 2023 20:11:44 +0100 Subject: [PATCH 4/6] Mention formatted strings --- .../data_types/03_strings.md | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/versioned_docs/version-v0.19.3/language_concepts/data_types/03_strings.md b/docs/versioned_docs/version-v0.19.3/language_concepts/data_types/03_strings.md index c42f34ec3ad..e4c744babb6 100644 --- a/docs/versioned_docs/version-v0.19.3/language_concepts/data_types/03_strings.md +++ b/docs/versioned_docs/version-v0.19.3/language_concepts/data_types/03_strings.md @@ -15,8 +15,7 @@ keywords: The string type is a fixed length value defined with `str`. -You can use strings in `assert()` functions or print them with -`std::println()`. See more about [Logging](../../standard_library/logging). +You can use strings in `assert()` functions or print them with `std::println()`. See more about [Logging](../../standard_library/logging). ```rust use dep::std; @@ -42,7 +41,7 @@ fn main() { } ``` -## Escape characters +## Escape Characters You can use escape characters for your strings: @@ -58,6 +57,26 @@ You can use escape characters for your strings: Example: ```rust -let s = "Hello \"world" // prints "Hello "world" +let s = "Hello \"world"; // prints "Hello "world" let s = "hey \tyou"; // prints "hey you" ``` + +## Formatted Strings + +You can prepend a string with the singular `f` token to create a formatted string. This is useful when logging, as it allows injection of local variables: + +```rust + +let var = 15; + +std::println(f"var {var}") // prints "var 0x0F" + +// prints "Hello +//world" +std::println(f"Hello +world"); + +std::println(f"hey \tyou"); // prints "hey \tyou" +``` + +Note that escaped characters in formatted strings `fmtstr` will be outputted as defined, i.e. "\n" will be printed `\n`, not as a new line. You can add a newline or other whitespace by creating a multiline string as in the example above. From 10131395bcd7662d144f608851fd17992c24704c Mon Sep 17 00:00:00 2001 From: grasshopper47 Date: Wed, 29 Nov 2023 13:24:19 +0100 Subject: [PATCH 5/6] Replace println with print --- .../noirc_frontend/src/monomorphization/mod.rs | 3 +-- noir_stdlib/src/lib.nr | 13 ++++++------- tooling/nargo/src/ops/foreign_calls.rs | 15 --------------- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index d3fb0d1ee12..6c7f07b68be 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -883,8 +883,7 @@ impl<'interner> Monomorphizer<'interner> { if let ast::Expression::Ident(ident) = original_func.as_ref() { if let Definition::Oracle(name) = &ident.definition { - let name_str = name.as_str(); - if (name_str == "println") | (name_str == "print") { + if name.as_str() == "print" { // 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 self.append_printable_type_info(&hir_arguments[0], &mut arguments); diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index f271a2927ce..be2fe844052 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -21,13 +21,7 @@ mod string; mod test; // 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 -#[oracle(println)] -unconstrained fn println_oracle(_input: T) {} - -unconstrained pub fn println(input: T) { - println_oracle(input); -} +// Thus, the only argument to the `print` oracle is expected to always be an ident #[oracle(print)] unconstrained fn print_oracle(_input: T) {} @@ -36,6 +30,11 @@ unconstrained pub fn print(input: T) { print_oracle(input); } +unconstrained pub fn println(input: T) { + print(f"{input} +"); // skip a 2nd oracle call; use multi-line format to insert a newline +} + #[foreign(recursive_aggregation)] pub fn verify_proof( _verification_key: [Field], diff --git a/tooling/nargo/src/ops/foreign_calls.rs b/tooling/nargo/src/ops/foreign_calls.rs index 9014e839276..7af31afc2c3 100644 --- a/tooling/nargo/src/ops/foreign_calls.rs +++ b/tooling/nargo/src/ops/foreign_calls.rs @@ -15,7 +15,6 @@ pub trait ForeignCallExecutor { /// This enumeration represents the Brillig foreign calls that are natively supported by nargo. /// After resolution of a foreign call, nargo will restart execution of the ACVM pub(crate) enum ForeignCall { - Println, Print, Sequence, ReverseSequence, @@ -35,7 +34,6 @@ impl std::fmt::Display for ForeignCall { impl ForeignCall { pub(crate) fn name(&self) -> &'static str { match self { - ForeignCall::Println => "println", ForeignCall::Print => "print", ForeignCall::Sequence => "get_number_sequence", ForeignCall::ReverseSequence => "get_reverse_number_sequence", @@ -49,7 +47,6 @@ impl ForeignCall { pub(crate) fn lookup(op_name: &str) -> Option { match op_name { - "println" => Some(ForeignCall::Println), "print" => Some(ForeignCall::Print), "get_number_sequence" => Some(ForeignCall::Sequence), "get_reverse_number_sequence" => Some(ForeignCall::ReverseSequence), @@ -130,12 +127,6 @@ impl DefaultForeignCallExecutor { decode_string_value(&fields) } - fn execute_println(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> { - let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?; - println!("{display_values}"); - Ok(()) - } - fn execute_print(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> { let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?; print!("{display_values}"); @@ -150,12 +141,6 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { ) -> Result { let foreign_call_name = foreign_call.function.as_str(); match ForeignCall::lookup(foreign_call_name) { - Some(ForeignCall::Println) => { - if self.show_output { - Self::execute_println(&foreign_call.inputs)?; - } - Ok(ForeignCallResult { values: vec![] }) - } Some(ForeignCall::Print) => { if self.show_output { Self::execute_print(&foreign_call.inputs)?; From 129fbedbbf25792752dbbdc5ad64f1eb5fc436bb Mon Sep 17 00:00:00 2001 From: grasshopper47 Date: Wed, 29 Nov 2023 16:15:09 +0100 Subject: [PATCH 6/6] Add newline param to print oracle --- noir_stdlib/src/lib.nr | 7 +++---- tooling/nargo/src/ops/foreign_calls.rs | 8 +++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index be2fe844052..6e9f7133ece 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -24,15 +24,14 @@ mod test; // Thus, the only argument to the `print` oracle is expected to always be an ident #[oracle(print)] -unconstrained fn print_oracle(_input: T) {} +unconstrained fn print_oracle(_input: T, _with_newline: bool) {} unconstrained pub fn print(input: T) { - print_oracle(input); + print_oracle(input, false); } unconstrained pub fn println(input: T) { - print(f"{input} -"); // skip a 2nd oracle call; use multi-line format to insert a newline + print_oracle(input, true); } #[foreign(recursive_aggregation)] diff --git a/tooling/nargo/src/ops/foreign_calls.rs b/tooling/nargo/src/ops/foreign_calls.rs index 7af31afc2c3..4ee0850ea24 100644 --- a/tooling/nargo/src/ops/foreign_calls.rs +++ b/tooling/nargo/src/ops/foreign_calls.rs @@ -129,7 +129,13 @@ impl DefaultForeignCallExecutor { fn execute_print(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> { let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?; - print!("{display_values}"); + + if foreign_call_inputs[1].unwrap_value().is_zero() { + print!("{display_values}"); + } else { + println!("{display_values}"); + } + Ok(()) } }