diff --git a/concepts/felts/links.json b/concepts/felts/links.json index 5c4e4487..5aec8020 100644 --- a/concepts/felts/links.json +++ b/concepts/felts/links.json @@ -3,6 +3,10 @@ "url": "https://book.cairo-lang.org/ch02-02-data-types.html#felt-type", "description": "Felt type in the Cairo book" }, + { + "url": "https://docs.starknet.io/architecture-and-concepts/smart-contracts/serialization-of-cairo-types/#data_types_of_252_bits_or_less", + "description": "Explanation for how certain data types get serialized into felt252" + }, { "url": "https://starknet-by-example.voyager.online/getting-started/cairo_cheatsheet/felt.html", "description": "Starknet by Example section on felts" diff --git a/concepts/strings/links.json b/concepts/strings/links.json index 846abe8a..9469e798 100644 --- a/concepts/strings/links.json +++ b/concepts/strings/links.json @@ -4,15 +4,11 @@ "description": "String types in the Cairo book" }, { - "url": "https://starknet-by-example.voyager.online/getting-started/basics/bytearrays-strings.html", - "description": "Starknet by Example section on Strings and ByteArrays" + "url": "https://docs.swmansion.com/scarb/corelib/core-byte_array-ByteArrayTrait.html", + "description": "Operations defined on ByteArray" }, { "url": "https://docs.starknet.io/architecture-and-concepts/smart-contracts/serialization-of-cairo-types/#serialization_of_byte_arrays", "description": "Starknet docs explaining how ByteArray is implemented and how it's serialized" - }, - { - "url": "https://community.starknet.io/t/cairo-v2-4-0-is-out/109275", - "description": "Cairo v2.4.0 is out" } ] diff --git a/config.json b/config.json index a2cb77f5..fb85bdc9 100644 --- a/config.json +++ b/config.json @@ -73,18 +73,6 @@ ], "status": "wip" }, - { - "slug": "welcome-to-tech-palace", - "name": "Welcome To Tech Palace!", - "uuid": "f8108950-9819-4540-8a3a-880d0778806c", - "concepts": [ - "strings" - ], - "prerequisites": [ - "functions" - ], - "status": "wip" - }, { "slug": "magician-in-training", "name": "Magician in Training", @@ -102,11 +90,23 @@ "name": "RPN Calculator", "uuid": "536d9f09-5910-4a26-93fd-2242667b0b87", "concepts": [ - "control-flow" + "control-flow", + "enums" ], "prerequisites": [ - "arrays", - "enums" + "arrays" + ], + "status": "wip" + }, + { + "slug": "welcome-to-tech-palace", + "name": "Welcome To Tech Palace!", + "uuid": "f8108950-9819-4540-8a3a-880d0778806c", + "concepts": [ + "strings" + ], + "prerequisites": [ + "control-flow" ], "status": "wip" }, diff --git a/exercises/concept/welcome-to-tech-palace/.docs/hints.md b/exercises/concept/welcome-to-tech-palace/.docs/hints.md index e69de29b..20e23ded 100644 --- a/exercises/concept/welcome-to-tech-palace/.docs/hints.md +++ b/exercises/concept/welcome-to-tech-palace/.docs/hints.md @@ -0,0 +1,24 @@ +# Hints + +## General + +- The [ByteArray][bytearray] type contains a few useful inbuilt methods, so you'll generally have to implement your own utility functions for manipulating ByteArrays. +- `ByteArray` actually represents an array of bytes and not "chars" as in many other languages. +- Working with both short and long strings at the same time can be challenging, so some helper functions are already implemented which you can use for this exercise. + +## 1. Create the welcome message + +- Strings can be concatenated using the `+` operator. +- Check if you can use any of the helper functions. + +## 2. Add a fancy border + +- You can append `'*'` characters in a loop. +- A newline is a special escape character. + +## 3. Clean up old marketing messages + +- Find the start and end indices of the clean message. +- You will probably need to copy the clean message characters into a new ByteArray one by one. + +[bytearray]: https://docs.swmansion.com/scarb/corelib/core-byte_array-ByteArrayTrait.html diff --git a/exercises/concept/welcome-to-tech-palace/.docs/instructions.md b/exercises/concept/welcome-to-tech-palace/.docs/instructions.md index e69de29b..a0c9da0e 100644 --- a/exercises/concept/welcome-to-tech-palace/.docs/instructions.md +++ b/exercises/concept/welcome-to-tech-palace/.docs/instructions.md @@ -0,0 +1,61 @@ +# Instructions + +There is an appliance store called "Tech Palace" nearby. +The owner of the store recently installed a big display to use for marketing messages and to show a special greeting when customers scan their loyalty cards at the entrance. +The display consists of lots of small LED lights and can show multiple lines of text. + +The store owner needs your help with the code that is used to generate the text for the new display. + +## 1. Create the welcome message + + +For most customers who scan their loyalty cards, the store owner wants to see `Welcome to the Tech Palace, ` followed by the name of the customer in capital letters on the display. + +Implement the function `welcome_message` that accepts the name of the customer as a short string (`felt252`) argument and returns the desired message as a `ByteArray`. + +```rust +welcome_message("Judy") +// => Welcome to the Tech Palace, JUDY +``` + +## 2. Add a fancy border + +For loyal customers that buy a lot at the store, the owner wants the welcome display to be more fancy by adding a line of stars before and after the welcome message. +They are not sure yet how many stars should be in the lines so they want that to be configurable. + +Write a function `add_border` that accepts a welcome message (a `ByteArray`) and the number of stars per line (type `u32`) as arguments. +It should return a `ByteArray` that consists of 3 lines, a line with the desired number of stars, then the welcome message as it was passed in, then another line of stars. + +```rust +add_border("Welcome!", 10) +``` + +Should return the following: + +```bash +********** +Welcome! +********** +``` + +## 3. Clean up old marketing messages + +Before installing this new display, the store had a similar display that could only show non-configurable, static lines. +The owner would like to reuse some of the old marketing messages on the new display. +However, the data already includes a star border and some unfortunate whitespaces. +Your task is to clean up the messages so they can be re-used. + +Implement a function `clean_up_message` that accepts the old marketing message as a `ByteArray`. +The function should first remove all stars from the text and afterwards remove the leading and trailing whitespaces from the remaining text. +The function should then return the cleaned up message. + +```rust +let message: ByteArray = " +************************** +* BUY NOW, SAVE 10% * +************************** +"; + +clean_up_message(message) +// => BUY NOW, SAVE 10% +``` diff --git a/exercises/concept/welcome-to-tech-palace/.docs/introduction.md b/exercises/concept/welcome-to-tech-palace/.docs/introduction.md index e69de29b..7bbddbaa 100644 --- a/exercises/concept/welcome-to-tech-palace/.docs/introduction.md +++ b/exercises/concept/welcome-to-tech-palace/.docs/introduction.md @@ -0,0 +1,76 @@ +# Introduction + +Cairo doesn't have a native type for strings but provides two ways to handle them: short strings using simple quotes and `ByteArray` using double quotes. + +A short string is an ASCII string where each character is encoded on one byte. + +The type Cairo uses for short strings by default is `felt252`, making their maximum length only 31 characters. + +```rust +// below are different ways to write the same value +let name: felt252 = 'Jane'; // short string format +let name: felt252 = 0x4a616e65; // byte format +let name: felt252 = 1247899237; // decimal format +``` + +Technically, any integer type smaller than `felt252` can be represented using the short string format. + +```rust +// all of the below are equal u8 values, they are just written using different formats! +let lowercase_a_in_ascii: u8 = 'a'; // short string format +let lowercase_a_in_ascii: u8 = 0x61; // byte format +let lowercase_a_in_ascii: u8 = 97; // decimal format +``` + +For strings longer than 31 characters Cairo provides `ByteArray`, which has an unlimited length. + +```rust +let long_string: ByteArray = "this is a string which has more than 31 characters"; +``` + +ByteArrays can be concatenated via the `+` operator: + +```rust +"Jane" + " " + "Austen" +// => "Jane Austen" +``` + +Some special characters need to be escaped with a leading backslash, such as `\t` for a tab and `\n` for a new line in strings. + +```rust +"How is the weather today?\nIt's sunny" +// => +// How is the weather today? +// It's sunny +``` + +You can even access individual bytes within a ByteArray using its index. + +Be careful though, the type of the returned byte is `u8` and not `ByteArray`! + +```rust +let hello: ByteArray = "Hello World!"; +let exclamation_mark: u8 = hello[11]; +// => 33 +``` + +The core library provides many useful methods and operators to work on ByteArrays. + +For more information about ByteArray methods, check out the [ByteArrayTrait documentation][docs]. + +Here are some examples: + +```rust +let name: felt252 = 'Jane'; +let mut greeting: ByteArray = "Welcome "; + +// append_word appends a single word of a given number of bytes to the end of the ByteArray +greeting.append_word(name, 4); +// => "Welcome Jane" + +// rev returns a ByteArray with the all characters reversed +let rev_greeting = greeting.rev(); +// => "enaJ emocleW" +``` + +[docs]: https://docs.swmansion.com/scarb/corelib/core-byte_array-ByteArrayTrait.html diff --git a/exercises/concept/welcome-to-tech-palace/.meta/config.json b/exercises/concept/welcome-to-tech-palace/.meta/config.json index 24531679..b14ef890 100644 --- a/exercises/concept/welcome-to-tech-palace/.meta/config.json +++ b/exercises/concept/welcome-to-tech-palace/.meta/config.json @@ -1,6 +1,6 @@ { "authors": [ - "" + "0xNeshi" ], "files": { "solution": [ @@ -19,5 +19,5 @@ "forked_from": [ "go/welcome-to-tech-palace" ], - "blurb": "" + "blurb": "Learn how to represent strings by generating text for a store's new display." } diff --git a/exercises/concept/welcome-to-tech-palace/.meta/design.md b/exercises/concept/welcome-to-tech-palace/.meta/design.md index e69de29b..e7ca7d19 100644 --- a/exercises/concept/welcome-to-tech-palace/.meta/design.md +++ b/exercises/concept/welcome-to-tech-palace/.meta/design.md @@ -0,0 +1,26 @@ +# Design + +## Goal + +The goal of this exercise is to teach the student the basics of strings and the `strings` package in Go. + +## Learning objectives + +- Know how to define short strings +- Know how to define Byte Array strings +- Know how to concatenate different string types +- Know how to use escape characters + +## Out of scope + +- String formatting + +## Concepts + +The Concepts this exercise unlocks are: + +- `strings` + +## Prerequisites + +- `control-flow` diff --git a/exercises/concept/welcome-to-tech-palace/.meta/exemplar.cairo b/exercises/concept/welcome-to-tech-palace/.meta/exemplar.cairo index e69de29b..1e93a41b 100644 --- a/exercises/concept/welcome-to-tech-palace/.meta/exemplar.cairo +++ b/exercises/concept/welcome-to-tech-palace/.meta/exemplar.cairo @@ -0,0 +1,76 @@ +// Returns a welcome message for the customer. +pub fn welcome_message(customer: felt252) -> ByteArray { + "Welcome to the Tech Palace, " + to_uppercase(customer.into()) +} + +// Adds a border to a welcome message. +pub fn add_border(welcome_msg: ByteArray, num_stars_per_line: u32) -> ByteArray { + let mut border: ByteArray = ""; + for _ in 0..num_stars_per_line { + border.append_byte('*'); + }; + border.clone() + "\n" + welcome_msg + "\n" + border +} + +// Cleans up an old marketing message. +pub fn clean_up_message(old_msg: ByteArray) -> ByteArray { + let mut start = 0; + while is_whitespace(old_msg[start]) || old_msg[start] == '*' { + start += 1; + }; + let mut end = old_msg.len(); + while is_whitespace(old_msg[end - 1]) || old_msg[end - 1] == '*' { + end -= 1; + }; + let mut clean_msg = ""; + for i in start..end { + clean_msg.append_byte(old_msg[i]); + }; + clean_msg +} + +/////////////// +/// Helpers /// +/////////////// + +// Distance between a lowercase and uppercase representations +// of the same character in the ASCII table +const ASCII_CASE_OFFSET: u8 = 32; +const BYTE_SIZE: u256 = 256; // 2^8, number of possible values in a byte +const BYTE_MASK: u256 = 0xff; // Mask to extract the last byte (8 bits) + +fn to_uppercase(input: u256) -> ByteArray { + let mut remaining_bytes = input; + let mut uppercase_chars: ByteArray = ""; + while remaining_bytes != 0 { + uppercase_chars.append_byte(char_to_uppercase(get_last_byte(remaining_bytes))); + remaining_bytes = remove_last_byte(remaining_bytes); + }; + uppercase_chars.rev() +} + +fn get_last_byte(chars: u256) -> u8 { + (chars & BYTE_MASK.into()).try_into().unwrap() +} + +fn remove_last_byte(chars: u256) -> u256 { + chars / BYTE_SIZE.into() +} + +fn char_to_uppercase(c: u8) -> u8 { + if is_lowercase(c) { + c - ASCII_CASE_OFFSET + } else { + c + } +} + +fn is_lowercase(c: u8) -> bool { + // This comparison is valid because the type of the value in the short + // string format gets automatically inferred by the compiler + c >= 'a' && c <= 'z' +} + +fn is_whitespace(chr: u8) -> bool { + chr == ' ' || chr == '\t' || chr == '\n' || chr == '\r' +} diff --git a/exercises/concept/welcome-to-tech-palace/Scarb.toml b/exercises/concept/welcome-to-tech-palace/Scarb.toml index e69de29b..bf1da8fa 100644 --- a/exercises/concept/welcome-to-tech-palace/Scarb.toml +++ b/exercises/concept/welcome-to-tech-palace/Scarb.toml @@ -0,0 +1,7 @@ +[package] +name = "welcome_to_tech_palace" +version = "0.1.0" +edition = "2024_07" + +[dev-dependencies] +cairo_test = "2.9.2" diff --git a/exercises/concept/welcome-to-tech-palace/src/lib.cairo b/exercises/concept/welcome-to-tech-palace/src/lib.cairo index e69de29b..486cdec7 100644 --- a/exercises/concept/welcome-to-tech-palace/src/lib.cairo +++ b/exercises/concept/welcome-to-tech-palace/src/lib.cairo @@ -0,0 +1,60 @@ +// Returns a welcome message for the customer. +pub fn welcome_message(customer: felt252) -> ByteArray { + panic!("implement `welcome_message`") +} + +// Adds a border to a welcome message. +pub fn add_border(welcome_msg: ByteArray, num_stars_per_line: u32) -> ByteArray { + panic!("implement `add_border`") +} + +// Cleans up an old marketing message. +pub fn clean_up_message(old_msg: ByteArray) -> ByteArray { + panic!("implement `clean_up_message`") +} + +/////////////// +/// Helpers /// +/////////////// + +// Distance between a lowercase and uppercase representations +// of the same character in the ASCII table +const ASCII_CASE_OFFSET: u8 = 32; +const BYTE_SIZE: u256 = 256; // 2^8, number of possible values in a byte +const BYTE_MASK: u256 = 0xff; // Mask to extract the last byte (8 bits) + +fn to_uppercase(input: u256) -> ByteArray { + let mut remaining_bytes = input; + let mut uppercase_chars: ByteArray = ""; + while remaining_bytes != 0 { + uppercase_chars.append_byte(char_to_uppercase(get_last_byte(remaining_bytes))); + remaining_bytes = remove_last_byte(remaining_bytes); + }; + uppercase_chars.rev() +} + +fn get_last_byte(chars: u256) -> u8 { + (chars & BYTE_MASK.into()).try_into().unwrap() +} + +fn remove_last_byte(chars: u256) -> u256 { + chars / BYTE_SIZE.into() +} + +fn char_to_uppercase(c: u8) -> u8 { + if is_lowercase(c) { + c - ASCII_CASE_OFFSET + } else { + c + } +} + +fn is_lowercase(c: u8) -> bool { + // This comparison is valid because the type of the value in the short + // string format gets automatically inferred by the compiler + c >= 'a' && c <= 'z' +} + +fn is_whitespace(chr: u8) -> bool { + chr == ' ' || chr == '\t' || chr == '\n' || chr == '\r' +} diff --git a/exercises/concept/welcome-to-tech-palace/tests/welcome_to_tech_palace.cairo b/exercises/concept/welcome-to-tech-palace/tests/welcome_to_tech_palace.cairo index e69de29b..13b7f095 100644 --- a/exercises/concept/welcome-to-tech-palace/tests/welcome_to_tech_palace.cairo +++ b/exercises/concept/welcome-to-tech-palace/tests/welcome_to_tech_palace.cairo @@ -0,0 +1,84 @@ +use welcome_to_tech_palace::*; + +#[test] +fn welcome_message_for_customer_with_first_letter_capitalized() { + let customer = 'Judy'; + let actual = welcome_message(customer); + let expected = "Welcome to the Tech Palace, JUDY"; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn welcome_message_for_customer_with_only_lowercase_letters() { + let customer = 'lars'; + let actual = welcome_message(customer); + let expected = "Welcome to the Tech Palace, LARS"; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn welcome_message_for_customer_with_dash_in_name() { + let customer = 'Peter-James'; + let actual = welcome_message(customer); + let expected = "Welcome to the Tech Palace, PETER-JAMES"; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn welcome_message_for_customer_with_only_uppercase_letters() { + let customer = 'MJ'; + let actual = welcome_message(customer); + let expected = "Welcome to the Tech Palace, MJ"; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn add_border_with_10_stars_per_line() { + let welcome_msg = "Welcome!"; + let num_stars_per_line = 10; + let actual = add_border(welcome_msg, num_stars_per_line); + let expected = "**********\nWelcome!\n**********"; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn add_border_with_2_stars_per_line() { + let welcome_msg = "Hi"; + let num_stars_per_line = 2; + let actual = add_border(welcome_msg, num_stars_per_line); + let expected = "**\nHi\n**"; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn clean_up_message_with_many_stars_and_leading_and_trailing_whitespace() { + let old_msg = + "**************************\n* BUY NOW, SAVE 10% *\n**************************"; + let actual = clean_up_message(old_msg); + let expected = "BUY NOW, SAVE 10%"; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn clean_up_message_without_leading_or_trailing_whitespace() { + let old_msg = "**********\n*DISCOUNT*\n**********"; + let actual = clean_up_message(old_msg); + let expected = "DISCOUNT"; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn clean_up_message_without_leading_whitespace() { + let old_msg = "*****\n SALE\n*****"; + let actual = clean_up_message(old_msg); + let expected = "SALE"; + assert_eq!(expected, actual); +}