Skip to content
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

feat: Implement print method #3576

Closed
Closed
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
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/monomorphization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@

The string type is a fixed length value defined with `str<N>`.

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).

Check warning on line 18 in docs/versioned_docs/version-v0.19.3/language_concepts/data_types/03_strings.md

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (println)

```rust
use dep::std;

fn main(message : pub str<11>, hex_as_string : str<4>) {
std::println(message);

Check warning on line 24 in docs/versioned_docs/version-v0.19.3/language_concepts/data_types/03_strings.md

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (println)
assert(message == "hello world");
assert(hex_as_string == "0x41");
}
Expand All @@ -42,7 +41,7 @@
}
```

## Escape characters
## Escape Characters

You can use escape characters for your strings:

Expand All @@ -58,6 +57,26 @@
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"

Check warning on line 72 in docs/versioned_docs/version-v0.19.3/language_concepts/data_types/03_strings.md

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (println)

// prints "Hello
//world"
std::println(f"Hello

Check warning on line 76 in docs/versioned_docs/version-v0.19.3/language_concepts/data_types/03_strings.md

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (println)
world");

std::println(f"hey \tyou"); // prints "hey \tyou"

Check warning on line 79 in docs/versioned_docs/version-v0.19.3/language_concepts/data_types/03_strings.md

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (println)
```

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.
27 changes: 20 additions & 7 deletions docs/versioned_docs/version-v0.19.3/standard_library/logging.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
---
title: Logging
description:
Learn how to use the println statement for debugging in Noir with this tutorial. Understand the

Check warning on line 4 in docs/versioned_docs/version-v0.19.3/standard_library/logging.md

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (println)
basics of logging in Noir and how to implement it in your code.
keywords:
[
noir logging,
println statement,

Check warning on line 9 in docs/versioned_docs/version-v0.19.3/standard_library/logging.md

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (println)
print statement,
debugging in noir,
noir std library,
logging tutorial,
Expand All @@ -17,14 +18,13 @@
]
---

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.

Check warning on line 21 in docs/versioned_docs/version-v0.19.3/standard_library/logging.md

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (println)

Check warning on line 21 in docs/versioned_docs/version-v0.19.3/standard_library/logging.md

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (println)

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;
Expand All @@ -40,10 +40,9 @@
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}";
Expand All @@ -60,3 +59,17 @@
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.
16 changes: 12 additions & 4 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ 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
#[oracle(println)]
unconstrained fn println_oracle<T>(_input: T) {}
// Thus, the only argument to the `print` oracle is expected to always be an ident

#[oracle(print)]
unconstrained fn print_oracle<T>(_input: T, _with_newline: bool) {}

unconstrained pub fn print<T>(input: T) {
print_oracle(input, false);
}

unconstrained pub fn println<T>(input: T) {
println_oracle(input);
print_oracle(input, true);
}

#[foreign(recursive_aggregation)]
Expand All @@ -36,10 +42,12 @@ pub fn verify_proof<N>(
_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<T>(_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.
Expand Down
14 changes: 12 additions & 2 deletions test_programs/execution_success/strings/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand All @@ -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.
Expand Down
51 changes: 44 additions & 7 deletions tooling/nargo/src/ops/foreign_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -14,7 +15,9 @@ 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,
CreateMock,
SetMockParams,
SetMockReturns,
Expand All @@ -31,7 +34,9 @@ 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",
ForeignCall::CreateMock => "create_mock",
ForeignCall::SetMockParams => "set_mock_params",
ForeignCall::SetMockReturns => "set_mock_returns",
Expand All @@ -42,7 +47,9 @@ impl ForeignCall {

pub(crate) fn lookup(op_name: &str) -> Option<ForeignCall> {
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),
Expand Down Expand Up @@ -120,9 +127,15 @@ impl DefaultForeignCallExecutor {
decode_string_value(&fields)
}

fn execute_println(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> {
fn execute_print(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> {
let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?;
println!("{display_values}");

if foreign_call_inputs[1].unwrap_value().is_zero() {
print!("{display_values}");
} else {
println!("{display_values}");
}

Ok(())
}
}
Expand All @@ -134,12 +147,36 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor {
) -> Result<ForeignCallResult, ForeignCallError> {
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![] })
}
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());
Expand Down
1 change: 1 addition & 0 deletions tooling/nargo_fmt/tests/expected/print.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::std;

fn main() {
std::print("Hello world");
std::println("Hello world");
}
3 changes: 2 additions & 1 deletion tooling/nargo_fmt/tests/expected/print2.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::std;

fn main( ) {
fn main() {
std::print("Hello world");
std::println("Hello world");
}
5 changes: 4 additions & 1 deletion tooling/nargo_fmt/tests/input/print.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use dep::std;

fn main() { std::println("Hello world"); }
fn main() {
std::print("Hello world");
std::println("Hello world");
}
5 changes: 3 additions & 2 deletions tooling/nargo_fmt/tests/input/print2.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::std;

fn main( ) {
std::println("Hello world");
fn main() {
std::print("Hello world");
std::println("Hello world");
}
Loading