From 01caba201497103feb5bdc6b83995420b3672a42 Mon Sep 17 00:00:00 2001 From: Nenad Date: Fri, 6 Dec 2024 08:09:34 +0100 Subject: [PATCH] add Calculator conundrum concept exercise (#307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * non-working scaffold * Implement exercise + update prerequisites * recreate exercise + add design.md + add panic to exerc * division panics * add hints * add introduction * add instructions * remove ? section * make operation simple ByteArray (no Option) * fix hints * Lint markdown * Apply suggestions from code review Co-authored-by: András B Nagy <20251272+BNAndras@users.noreply.github.com> --------- Co-authored-by: András B Nagy <20251272+BNAndras@users.noreply.github.com> --- concepts/error-handling/about.md | 18 +++++ concepts/error-handling/links.json | 4 ++ config.json | 9 +-- .../calculator-conundrum/.docs/hints.md | 25 +++++++ .../.docs/instructions.md | 46 +++++++++++++ .../.docs/introduction.md | 65 +++++++++++++++++++ .../.meta/config.json | 8 +-- .../calculator-conundrum/.meta/design.md | 27 ++++++++ .../calculator-conundrum/.meta/exemplar.cairo | 17 +++++ .../concept/calculator-conundrum/Scarb.toml | 7 ++ .../calculator-conundrum/src/lib.cairo | 6 ++ .../tests/calculator_conundrum.cairo | 62 ++++++++++++++++++ exercises/concept/the-farm/.docs/hints.md | 0 .../concept/the-farm/.docs/instructions.md | 0 .../concept/the-farm/.docs/introduction.md | 0 exercises/concept/the-farm/.meta/design.md | 0 .../concept/the-farm/.meta/exemplar.cairo | 0 exercises/concept/the-farm/Scarb.toml | 0 exercises/concept/the-farm/src/lib.cairo | 0 .../concept/the-farm/tests/the_farm.cairo | 0 single-sentence-per-line-rule.js | 2 + 21 files changed, 288 insertions(+), 8 deletions(-) create mode 100644 exercises/concept/calculator-conundrum/.docs/hints.md create mode 100644 exercises/concept/calculator-conundrum/.docs/instructions.md create mode 100644 exercises/concept/calculator-conundrum/.docs/introduction.md rename exercises/concept/{the-farm => calculator-conundrum}/.meta/config.json (58%) create mode 100644 exercises/concept/calculator-conundrum/.meta/design.md create mode 100644 exercises/concept/calculator-conundrum/.meta/exemplar.cairo create mode 100644 exercises/concept/calculator-conundrum/Scarb.toml create mode 100644 exercises/concept/calculator-conundrum/src/lib.cairo create mode 100644 exercises/concept/calculator-conundrum/tests/calculator_conundrum.cairo delete mode 100644 exercises/concept/the-farm/.docs/hints.md delete mode 100644 exercises/concept/the-farm/.docs/instructions.md delete mode 100644 exercises/concept/the-farm/.docs/introduction.md delete mode 100644 exercises/concept/the-farm/.meta/design.md delete mode 100644 exercises/concept/the-farm/.meta/exemplar.cairo delete mode 100644 exercises/concept/the-farm/Scarb.toml delete mode 100644 exercises/concept/the-farm/src/lib.cairo delete mode 100644 exercises/concept/the-farm/tests/the_farm.cairo diff --git a/concepts/error-handling/about.md b/concepts/error-handling/about.md index 5a617597..88193945 100644 --- a/concepts/error-handling/about.md +++ b/concepts/error-handling/about.md @@ -36,6 +36,24 @@ There is also a special macro, called `panic!`, that allows a `ByteArray` to be panic!("The error for the panic! Error message is not limited to 31 characters anymore"); ``` +### The `assert!` Macro + +The `assert!` macro is a useful tool for enforcing specific conditions in your code. +If the condition in `assert!` evaluates to `false`, the program will panic with a `ByteArray` error message. +This is often used to verify assumptions during development and ensure values meet certain criteria. + +For example: + +```rust +fn main() { + let x = 5; + assert!(x > 0, "x must be greater than zero"); +} +``` + +If `x` is not greater than zero, the program will panic with the message `"x must be greater than zero"`. +`assert!` is helpful for checking invariants and preconditions without manually writing error-handling code. + ### `nopanic` Notation Cairo `nopanic` notation indicates that a function will never panic. diff --git a/concepts/error-handling/links.json b/concepts/error-handling/links.json index f2a97872..ff03a996 100644 --- a/concepts/error-handling/links.json +++ b/concepts/error-handling/links.json @@ -2,5 +2,9 @@ { "url": "https://book.cairo-lang.org/ch09-00-error-handling.html", "description": "Error handling in the Cairo book" + }, + { + "url": "https://book.cairo-lang.org/ch11-05-macros.html?highlight=assert#assert-and-assert_xx-macros", + "description": "assert! macro" } ] diff --git a/config.json b/config.json index 071775bd..bea37047 100644 --- a/config.json +++ b/config.json @@ -236,14 +236,15 @@ "status": "wip" }, { - "slug": "the-farm", - "name": "The Farm", - "uuid": "e167e30c-84b1-44da-b21c-70bc46688f20", + "slug": "calculator-conundrum", + "name": "Calculator Conundrum", + "uuid": "989df4b1-6d89-468d-96e0-47013e3cf99b", "concepts": [ "error-handling" ], "prerequisites": [ - "structs" + "traits", + "option" ], "status": "wip" }, diff --git a/exercises/concept/calculator-conundrum/.docs/hints.md b/exercises/concept/calculator-conundrum/.docs/hints.md new file mode 100644 index 00000000..81cefb87 --- /dev/null +++ b/exercises/concept/calculator-conundrum/.docs/hints.md @@ -0,0 +1,25 @@ +# Hints + +## General + +- [Unrecoverable Errors][unrecoverable]: invoke a panic with error message. +- [`panic!` Macro][panic-excl-macro]: invoke a panic with `ByteArray` error message. +- [`assert!` Macro][assert]: invoke a panic if a condition evaluates to `false`. +- [Result Enum][result]: how to use the `Result` enum. + +## 2. Handle illegal operations + +- You need to [return an error][result] here. + +## 3. Handle no operation provided + +- You need to [panic][panic-excl-macro] here with a `ByteArray` message to handle empty strings for operations. + +## 4. Handle errors when dividing by zero + +- You need to panic here to handle division by zero. + +[unrecoverable]: https://book.cairo-lang.org/ch09-01-unrecoverable-errors-with-panic.html#unrecoverable-errors-with-panic +[panic-excl-macro]: https://book.cairo-lang.org/ch09-01-unrecoverable-errors-with-panic.html#panic-macro +[assert]: https://book.cairo-lang.org/ch11-05-macros.html?highlight=assert#assert-and-assert_xx-macros +[result]: https://book.cairo-lang.org/ch09-02-recoverable-errors.html#the-result-enum diff --git a/exercises/concept/calculator-conundrum/.docs/instructions.md b/exercises/concept/calculator-conundrum/.docs/instructions.md new file mode 100644 index 00000000..f363228f --- /dev/null +++ b/exercises/concept/calculator-conundrum/.docs/instructions.md @@ -0,0 +1,46 @@ +# Instructions + +In this exercise, you will be building error handling for a simple integer calculator. +The calculator should support addition, multiplication, and division operations, returning the result as a formatted string. +You will also implement error handling to address illegal operations and division by zero. + +The goal is to have a working calculator that returns a string in the following format: + +```rust +SimpleCalculator::calculate(16, 51, "+"); // => returns "16 + 51 = 67" + +SimpleCalculator::calculate(32, 6, "*"); // => returns "32 * 6 = 192" + +SimpleCalculator::calculate(512, 4, "/"); // => returns "512 / 4 = 128" +``` + +## 1. Implement the calculator operations + +The main function for this task will be `SimpleCalculator::calculate`, which takes three arguments: two integers and a `ByteArray` representing the operation. +Implement the following operations: + +- **Addition** with the `+` symbol +- **Multiplication** with the `*` symbol +- **Division** with the `/` symbol + +## 2. Handle illegal operations + +If the operation symbol is anything other than `+`, `*`, or `/`, the calculator should either panic or return an error: + +- If the operation is an empty string, panic with a `ByteArray` error message `"Operation cannot be an empty string"`. +- For any other invalid operation, return `Result::Err("Operation is out of range")`. + +```rust +SimpleCalculator::calculate(100, 10, "-"); // => returns Result::Err("Operation is out of range") + +SimpleCalculator::calculate(8, 2, ""); // => panics with "Operation cannot be an empty string" +``` + +## 3. Handle errors when dividing by zero + +When attempting to divide by `0`, the calculator should panic with an error message indicating that division by zero is not allowed. +The returned result should be a `felt252` value of `'Division by zero is not allowed'`. + +```rust +SimpleCalculator::calculate(512, 0, "/"); // => panics with 'Division by zero is not allowed' +``` diff --git a/exercises/concept/calculator-conundrum/.docs/introduction.md b/exercises/concept/calculator-conundrum/.docs/introduction.md new file mode 100644 index 00000000..3756476d --- /dev/null +++ b/exercises/concept/calculator-conundrum/.docs/introduction.md @@ -0,0 +1,65 @@ +# Introduction + +In programming, it's essential to handle errors gracefully to ensure that unexpected situations do not cause a program to crash or behave unpredictably. +Cairo provides two main mechanisms for error handling: + +1. **Unrecoverable errors** with `panic`, which immediately stop the program. +2. **Recoverable errors** with `Result`, which allow the program to handle and respond to errors. + +## Unrecoverable Errors with `panic` + +Sometimes, a program encounters an error so severe that it cannot proceed. +Cairo uses the `panic` function to immediately stop execution in such cases. +This is helpful when an error, like attempting to access an out-of-bounds index, makes it impossible for the program to continue in a sensible way. +The `panic` function accepts a `ByteArray` message that describes the reason for the error. + +For example, in Cairo: + +```rust +fn main() { + let data = array![1, 2]; + panic("An unrecoverable error has occurred!"); +} +``` + +This example demonstrates a forced panic, which immediately stops the program with a message. + +### The `assert!` Macro + +The `assert!` macro is a useful tool for enforcing specific conditions in your code. +If the condition in `assert!` evaluates to `false`, the program will panic with a `ByteArray` error message. +This is often used to verify assumptions during development and ensure values meet certain criteria. + +For example: + +```rust +fn main() { + let x = 5; + assert!(x > 0, "x must be greater than zero"); +} +``` + +If `x` is not greater than zero, the program will panic with the message `"x must be greater than zero"`. `assert!` is helpful for checking invariants and preconditions without manually writing error-handling code. + +## Recoverable Errors with `Result` + +Not all errors need to stop the program. +Some can be handled gracefully so the program can continue. +Cairo's `Result` enum represents these recoverable errors, and it has two variants: + +- `Result::Ok` indicates success. +- `Result::Err` represents an error. + +Using `Result`, a function can return either a success value or an error, allowing the calling function to decide what to do next. + +```rust +fn divide(a: u32, b: u32) -> Result { + if b == 0 { + Result::Err("Error: Division by zero") + } else { + Result::Ok(a / b) + } +} +``` + +In this example, if `b` is zero, an error is returned; otherwise, the result of the division is returned. diff --git a/exercises/concept/the-farm/.meta/config.json b/exercises/concept/calculator-conundrum/.meta/config.json similarity index 58% rename from exercises/concept/the-farm/.meta/config.json rename to exercises/concept/calculator-conundrum/.meta/config.json index 2f288353..b116677b 100644 --- a/exercises/concept/the-farm/.meta/config.json +++ b/exercises/concept/calculator-conundrum/.meta/config.json @@ -1,13 +1,13 @@ { "authors": [ - "" + "0xNeshi" ], "files": { "solution": [ "src/lib.cairo" ], "test": [ - "tests/the_farm.cairo" + "tests/calculator_conundrum.cairo" ], "exemplar": [ ".meta/exemplar.cairo" @@ -17,7 +17,7 @@ ] }, "forked_from": [ - "go/the-farm" + "csharp/calculator-conundrum" ], - "blurb": "" + "blurb": "Learn about error handling by working on a simple calculator." } diff --git a/exercises/concept/calculator-conundrum/.meta/design.md b/exercises/concept/calculator-conundrum/.meta/design.md new file mode 100644 index 00000000..0c451b01 --- /dev/null +++ b/exercises/concept/calculator-conundrum/.meta/design.md @@ -0,0 +1,27 @@ +# Design + +## Goal + +Introduce the student to error handling in Cairo. + +## Learning objectives + +- know how create recoverable errors. +- know how create unrecoverable errors. + +## Out of scope + +## Concepts + +- error-handling + +## Prerequisites + +- traits +- option + +## Resources to refer to + +- [Cairo Book - Error Handling][error-handling] + +[error-handling]: https://book.cairo-lang.org/ch09-00-error-handling.html diff --git a/exercises/concept/calculator-conundrum/.meta/exemplar.cairo b/exercises/concept/calculator-conundrum/.meta/exemplar.cairo new file mode 100644 index 00000000..19f3cdff --- /dev/null +++ b/exercises/concept/calculator-conundrum/.meta/exemplar.cairo @@ -0,0 +1,17 @@ +#[generate_trait] +pub impl SimpleCalculatorImpl of SimpleCalculatorTrait { + fn calculate(a: i32, b: i32, operation: ByteArray) -> Result { + assert!(operation != "", "Operation cannot be an empty string"); + + if operation == "+" { + Result::Ok(format!("{} + {} = {}", a, b, a + b)) + } else if operation == "*" { + Result::Ok(format!("{} * {} = {}", a, b, a * b)) + } else if operation == "/" { + assert(b != 0, 'Division by zero is not allowed'); + Result::Ok(format!("{} / {} = {}", a, b, a / b)) + } else { + Result::Err("Operation is out of range") + } + } +} diff --git a/exercises/concept/calculator-conundrum/Scarb.toml b/exercises/concept/calculator-conundrum/Scarb.toml new file mode 100644 index 00000000..ec9c1d1d --- /dev/null +++ b/exercises/concept/calculator-conundrum/Scarb.toml @@ -0,0 +1,7 @@ +[package] +name = "calculator_conundrum" +version = "0.1.0" +edition = "2024_07" + +[dev-dependencies] +cairo_test = "2.8.2" diff --git a/exercises/concept/calculator-conundrum/src/lib.cairo b/exercises/concept/calculator-conundrum/src/lib.cairo new file mode 100644 index 00000000..d87f391a --- /dev/null +++ b/exercises/concept/calculator-conundrum/src/lib.cairo @@ -0,0 +1,6 @@ +#[generate_trait] +pub impl SimpleCalculatorImpl of SimpleCalculatorTrait { + fn calculate(a: i32, b: i32, operation: ByteArray) -> Result { + panic!("implement `calculate`") + } +} diff --git a/exercises/concept/calculator-conundrum/tests/calculator_conundrum.cairo b/exercises/concept/calculator-conundrum/tests/calculator_conundrum.cairo new file mode 100644 index 00000000..6c0cb948 --- /dev/null +++ b/exercises/concept/calculator-conundrum/tests/calculator_conundrum.cairo @@ -0,0 +1,62 @@ +use calculator_conundrum::SimpleCalculatorTrait as SimpleCalculator; + +#[test] +fn addition_with_small_operands() { + assert_eq!(SimpleCalculator::calculate(22, 25, "+").unwrap(), "22 + 25 = 47"); +} + +#[test] +#[ignore] +fn addition_with_large_operands() { + assert_eq!( + SimpleCalculator::calculate(378_961, 399_635, "+").unwrap(), "378961 + 399635 = 778596" + ); +} + +#[test] +#[ignore] +fn multiplication_with_small_operands() { + assert_eq!(SimpleCalculator::calculate(3, 21, "*").unwrap(), "3 * 21 = 63"); +} + +#[test] +#[ignore] +fn multiplication_with_large_operands() { + assert_eq!( + SimpleCalculator::calculate(72_441, 2_048, "*").unwrap(), "72441 * 2048 = 148359168" + ); +} + +#[test] +#[ignore] +fn division_with_small_operands() { + assert_eq!(SimpleCalculator::calculate(72, 9, "/").unwrap(), "72 / 9 = 8"); +} + +#[test] +#[ignore] +fn division_with_large_operands() { + assert_eq!( + SimpleCalculator::calculate(1_338_800, 83_675, "/").unwrap(), "1338800 / 83675 = 16" + ); +} + +#[test] +#[ignore] +fn calculate_returns_result_err_for_non_valid_operations() { + assert_eq!(SimpleCalculator::calculate(1, 2, "**").unwrap_err(), "Operation is out of range"); +} + +#[test] +#[ignore] +#[should_panic(expected: ("Operation cannot be an empty string",))] +fn calculate_returns_result_err_for_empty_string_as_operation() { + let _ = SimpleCalculator::calculate(1, 2, ""); +} + +#[test] +#[ignore] +#[should_panic(expected: ('Division by zero is not allowed',))] +fn calculate_panics_for_division_with_0() { + let _ = SimpleCalculator::calculate(33, 0, "/"); +} diff --git a/exercises/concept/the-farm/.docs/hints.md b/exercises/concept/the-farm/.docs/hints.md deleted file mode 100644 index e69de29b..00000000 diff --git a/exercises/concept/the-farm/.docs/instructions.md b/exercises/concept/the-farm/.docs/instructions.md deleted file mode 100644 index e69de29b..00000000 diff --git a/exercises/concept/the-farm/.docs/introduction.md b/exercises/concept/the-farm/.docs/introduction.md deleted file mode 100644 index e69de29b..00000000 diff --git a/exercises/concept/the-farm/.meta/design.md b/exercises/concept/the-farm/.meta/design.md deleted file mode 100644 index e69de29b..00000000 diff --git a/exercises/concept/the-farm/.meta/exemplar.cairo b/exercises/concept/the-farm/.meta/exemplar.cairo deleted file mode 100644 index e69de29b..00000000 diff --git a/exercises/concept/the-farm/Scarb.toml b/exercises/concept/the-farm/Scarb.toml deleted file mode 100644 index e69de29b..00000000 diff --git a/exercises/concept/the-farm/src/lib.cairo b/exercises/concept/the-farm/src/lib.cairo deleted file mode 100644 index e69de29b..00000000 diff --git a/exercises/concept/the-farm/tests/the_farm.cairo b/exercises/concept/the-farm/tests/the_farm.cairo deleted file mode 100644 index e69de29b..00000000 diff --git a/single-sentence-per-line-rule.js b/single-sentence-per-line-rule.js index 459508f9..ce12d2c2 100644 --- a/single-sentence-per-line-rule.js +++ b/single-sentence-per-line-rule.js @@ -52,6 +52,8 @@ module.exports = { "e.g", "etc", "ex", + "`assert", + "`panic", ]; const lineEndings = params.config.line_endings || [".", "?", "!"]; const sentenceStartRegex =