diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 4f035e0fc2e..377557be623 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -889,10 +889,11 @@ 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" { + 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); + // The first argument to the `print` oracle is a bool, indicating a newline to be inserted at the end of the input + // The second argument is expected to always be an ident + self.append_printable_type_info(&hir_arguments[1], &mut arguments); } } } diff --git a/docs/docs/standard_library/logging.md b/docs/docs/standard_library/logging.md index 7e2fd9b9aff..16daf922e15 100644 --- a/docs/docs/standard_library/logging.md +++ b/docs/docs/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` are generic functions which can work on integers, fields, strings, and even structs or expressions. Note however, that slices are currently unsupported. 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}"; @@ -59,4 +58,20 @@ 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}"); + + std::println(15); // prints 0x0f, implicit Field + std::println(-1 as u8); // prints 255 + std::println(-1 as i8); // prints -1 +``` + +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 line ``` diff --git a/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/03_strings.md b/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/03_strings.md index c42f34ec3ad..b4c75942bb8 100644 --- a/docs/versioned_docs/version-v0.19.4/language_concepts/data_types/03_strings.md +++ b/docs/versioned_docs/version-v0.19.4/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,32 @@ 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" + +let var = -1 as u8; +std::println(f"var {var}") // prints "var 255" + +let var : i8 = -1; +std::println(f"var {var}") // prints "var -1" + +// prints "Hello +//world" +std::println(f"Hello +world"); + +std::println(f"hey \tyou"); // prints "hey \tyou" +``` + +A type can be specified to print numbers either as hex via `Field`, unsigned via `u*` types and signed via `i*` types. + +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. diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 8d878eecbb3..fecdf77a4ec 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -20,12 +20,16 @@ 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 -#[oracle(println)] -unconstrained fn println_oracle(_input: T) {} +// Thus, the only argument to the `println` oracle is expected to always be an ident +#[oracle(print)] +unconstrained fn print_oracle(_with_newline: bool, _input: T) {} + +unconstrained pub fn print(input: T) { + print_oracle(false, input); +} unconstrained pub fn println(input: T) { - println_oracle(input); + print_oracle(true, input); } #[foreign(recursive_aggregation)] diff --git a/test_programs/execution_success/strings/src/main.nr b/test_programs/execution_success/strings/src/main.nr index cb2218837f3..c35f59dd8c5 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,6 @@ 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. // 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/src/ops/foreign_calls.rs b/tooling/nargo/src/ops/foreign_calls.rs index 1ca270a5bf7..bc1e19cdcf4 100644 --- a/tooling/nargo/src/ops/foreign_calls.rs +++ b/tooling/nargo/src/ops/foreign_calls.rs @@ -14,7 +14,7 @@ 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, CreateMock, SetMockParams, SetMockReturns, @@ -31,7 +31,7 @@ impl std::fmt::Display for ForeignCall { impl ForeignCall { pub(crate) fn name(&self) -> &'static str { match self { - ForeignCall::Println => "println", + ForeignCall::Print => "print", ForeignCall::CreateMock => "create_mock", ForeignCall::SetMockParams => "set_mock_params", ForeignCall::SetMockReturns => "set_mock_returns", @@ -42,7 +42,7 @@ impl ForeignCall { pub(crate) fn lookup(op_name: &str) -> Option { match op_name { - "println" => Some(ForeignCall::Println), + "print" => Some(ForeignCall::Print), "create_mock" => Some(ForeignCall::CreateMock), "set_mock_params" => Some(ForeignCall::SetMockParams), "set_mock_returns" => Some(ForeignCall::SetMockReturns), @@ -92,7 +92,7 @@ pub struct DefaultForeignCallExecutor { last_mock_id: usize, /// The registered mocks mocked_responses: Vec, - /// Whether to print [`ForeignCall::Println`] output. + /// Whether to print [`ForeignCall::Print`] output. show_output: bool, } @@ -120,9 +120,14 @@ 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}"); + fn execute_print(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> { + let skip_newline = foreign_call_inputs[0].unwrap_value().is_zero(); + let display_values: PrintableValueDisplay = foreign_call_inputs + .split_first() + .ok_or(ForeignCallError::MissingForeignCallInputs)? + .1 + .try_into()?; + print!("{display_values}{}", if skip_newline { "" } else { "\n" }); Ok(()) } } @@ -134,9 +139,9 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { ) -> Result { let foreign_call_name = foreign_call.function.as_str(); match ForeignCall::lookup(foreign_call_name) { - Some(ForeignCall::Println) => { + Some(ForeignCall::Print) => { if self.show_output { - Self::execute_println(&foreign_call.inputs)?; + Self::execute_print(&foreign_call.inputs)?; } Ok(ForeignCallResult { values: vec![] }) } 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"); }