├── .github └── workflows │ ├── install-mdbook │ └── action.yml │ ├── main.yml │ ├── mdbook-test.yml │ ├── publish.yml │ └── setup-rust-cache │ └── action.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── book ├── .gitignore ├── README.md ├── book.toml ├── css │ └── language-picker.css ├── po ├── src │ ├── 01_README.md │ ├── 02_README.md │ ├── 03_README.md │ ├── 04_README.md │ ├── 05_README.md │ ├── 06_README.md │ ├── 07_README.md │ ├── 08_README.md │ ├── 09_README.md │ ├── 10_README.md │ ├── 11_README.md │ ├── 12_README.md │ ├── 13_README.md │ ├── 99_README.md │ ├── README.md │ └── SUMMARY.md └── theme │ ├── book.js │ └── index.hbs ├── exercises ├── 01_my_first_macro │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 02_numbers │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 03_literal_variables │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 04_expression_variables │ ├── README.md │ ├── examples │ │ └── broken_macro.rs │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 05_more_complex_example │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 06_repetition │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 07_more_repetition │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 08_nested_repetition │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 09_ambiguity_and_ordering │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 10_macros_calling_macros │ ├── README.md │ ├── archive │ │ ├── main.rs │ │ └── solutions │ │ │ ├── main.rs │ │ │ └── solution.diff │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 11_macro_recursion │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── advanced.rs │ │ ├── main.rs │ │ └── solution.diff ├── 12_hygienic_macros │ ├── README.md │ ├── main.rs │ └── solutions │ │ ├── main.rs │ │ └── solution.diff ├── 13_scoping_importing_and_exporting │ └── README.md └── 99_extra_reading │ └── README.md ├── po ├── macrokata.pot ├── zh.mo └── zh.po ├── scripts └── serve_book.sh └── src ├── check.rs ├── goal.rs ├── main.rs ├── test.rs └── update_diff.rs /.github/workflows/install-mdbook/action.yml: -------------------------------------------------------------------------------- 1 | ame: Install mdbook and dependencies 2 | 3 | description: Install the mdbook with the dependencies we need. 4 | 5 | runs: 6 | using: composite 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: nightly 14 | - uses: actions/cache@v3 15 | with: 16 | path: | 17 | ~/.cargo/bin/ 18 | ~/.cargo/registry/index/ 19 | ~/.cargo/registry/cache/ 20 | ~/.cargo/git/db/ 21 | target/ 22 | key: mdbook-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 23 | # The --locked flag is important for reproducible builds. It also 24 | # avoids breakage due to skews between mdbook and mdbook-svgbob. 25 | - name: Install mdbook 26 | run: which mdbook || cargo install mdbook --locked --version 0.4.27 27 | shell: bash 28 | 29 | - name: Install mdbook-keeper 30 | run: which mdbook-keeper || cargo install mdbook-keeper --locked --version 0.2.4 31 | shell: bash 32 | 33 | - name: Install mdbook-cmdrun 34 | run: which mdbook-cmdrun || cargo install mdbook-cmdrun --locked --version 0.5.0 35 | shell: bash 36 | 37 | - name: Install cargo-expand 38 | run: which cargo-expand || cargo install cargo-expand 39 | shell: bash 40 | 41 | - name: Install i18n-helpers 42 | run: which mdbook-gettext || cargo install mdbook-i18n-helpers 43 | shell: bash 44 | 45 | - name: Build macrokata 46 | run: cargo build --bin macrokata 47 | shell: bash 48 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | - run: cargo install cargo-all-features 18 | - run: cargo test-all-features --bin '*_soln' 19 | - run: cargo test-all-features --bin 'macrokata' 20 | - run: cargo test-all-features --release --bin '*_soln' 21 | - run: cargo test-all-features --release --bin 'macrokata' 22 | 23 | clippy: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: stable 30 | - run: rustup component add clippy 31 | - run: cargo clippy --bin '*_soln' -- -D warnings 32 | - run: cargo clippy --bin 'macrokata' -- -D warnings 33 | 34 | rustfmt: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v2 38 | - uses: actions-rs/toolchain@v1 39 | with: 40 | toolchain: stable 41 | - run: rustup component add rustfmt 42 | - run: cargo fmt --check 43 | 44 | macrokata-checks: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v2 48 | - uses: actions-rs/toolchain@v1 49 | with: 50 | toolchain: stable 51 | - run: cargo run --bin macrokata -- check-all 52 | -------------------------------------------------------------------------------- /.github/workflows/mdbook-test.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | i18n-helpers: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Rust cache 18 | uses: ./.github/workflows/setup-rust-cache 19 | 20 | - name: Install Gettext 21 | run: sudo apt install gettext 22 | 23 | - name: Install mdbook 24 | uses: ./.github/workflows/install-mdbook 25 | 26 | - name: Generate po/messages.pot 27 | run: mdbook build -d po 28 | working-directory: ./book 29 | env: 30 | MDBOOK_OUTPUT: '{"xgettext": {"pot-file": "messages.pot"}}' 31 | 32 | - name: Test messages.pot 33 | working-directory: ./book 34 | run: msgfmt --statistics -o /dev/null po/messages.pot 35 | 36 | - name: Expand includes without translation 37 | working-directory: ./book 38 | run: mdbook build -d expanded 39 | env: 40 | MDBOOK_OUTPUT: '{"markdown": {}}' 41 | 42 | - name: Expand includes with no-op translation 43 | working-directory: ./book 44 | run: mdbook build -d no-op 45 | env: 46 | MDBOOK_OUTPUT: '{"markdown": {}}' 47 | MDBOOK_PREPROCESSOR__GETTEXT__PO_FILE: po/messages.pot 48 | 49 | - name: Compare no translation to no-op translation 50 | working-directory: ./book 51 | run: diff --color=always --unified --recursive expanded no-op 52 | 53 | find-translations: 54 | runs-on: ubuntu-latest 55 | outputs: 56 | languages: ${{ steps.find-translations.outputs.languages }} 57 | steps: 58 | - name: Checkout 59 | uses: actions/checkout@v3 60 | 61 | - name: Find translations 62 | working-directory: ./book 63 | id: find-translations 64 | shell: python 65 | run: | 66 | import os, json, pathlib 67 | languages = [p.stem for p in pathlib.Path("po").iterdir() if p.suffix == ".po"] 68 | github_output = open(os.environ["GITHUB_OUTPUT"], "a") 69 | github_output.write("languages=") 70 | json.dump(sorted(languages), github_output) 71 | translations: 72 | runs-on: ubuntu-latest 73 | needs: 74 | - find-translations 75 | defaults: 76 | run: 77 | working-directory: ./book/ 78 | strategy: 79 | matrix: 80 | language: ${{ fromJSON(needs.find-translations.outputs.languages) }} 81 | env: 82 | MDBOOK_BOOK__LANGUAGE: ${{ matrix.language }} 83 | steps: 84 | - name: Checkout 85 | uses: actions/checkout@v3 86 | 87 | - name: Setup Rust cache 88 | uses: ./.github/workflows/setup-rust-cache 89 | 90 | - name: Install Gettext 91 | run: sudo apt install gettext 92 | 93 | - name: Install mdbook 94 | uses: ./.github/workflows/install-mdbook 95 | 96 | - name: Test ${{ matrix.language }} translation 97 | run: msgfmt --statistics -o /dev/null po/${{ matrix.language }}.po 98 | 99 | - name: Build course with ${{ matrix.language }} translation 100 | run: mdbook build 101 | 102 | - uses: actions/upload-artifact@v3 103 | with: 104 | name: macrokata-${{ matrix.language }} 105 | path: book/book/ 106 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | # Allow one concurrent deployment 15 | concurrency: 16 | group: pages 17 | cancel-in-progress: true 18 | 19 | env: 20 | CARGO_TERM_COLOR: always 21 | # Update the language picker in index.hbs to link new languages. 22 | LANGUAGES: zh 23 | 24 | jobs: 25 | publish: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | defaults: 31 | run: 32 | working-directory: ./book/ 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v3 36 | 37 | - name: Setup Rust cache 38 | uses: ./.github/workflows/setup-rust-cache 39 | 40 | - name: Install mdbook 41 | uses: ./.github/workflows/install-mdbook 42 | 43 | - name: Build course in English 44 | run: mdbook build -d book 45 | 46 | - name: Build all translations 47 | run: | 48 | for po_lang in ${{ env.LANGUAGES }}; do 49 | echo "::group::Building $po_lang translation" 50 | MDBOOK_BOOK__LANGUAGE=$po_lang \ 51 | MDBOOK_OUTPUT__HTML__SITE_URL=/macrokata/$po_lang/ \ 52 | mdbook build -d book/$po_lang 53 | echo "::endgroup::" 54 | done 55 | - name: Setup Pages 56 | uses: actions/configure-pages@v2 57 | 58 | - name: Upload artifact 59 | uses: actions/upload-pages-artifact@v1 60 | with: 61 | path: book/book 62 | 63 | - name: Deploy to GitHub Pages 64 | id: deployment 65 | uses: actions/deploy-pages@v1 66 | -------------------------------------------------------------------------------- /.github/workflows/setup-rust-cache/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Rust cache 2 | 3 | description: Configure the rust-cache workflow. 4 | 5 | runs: 6 | using: composite 7 | steps: 8 | - name: Setup Rust cache 9 | uses: Swatinem/rust-cache@v2 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Macrokata 2 | 3 | Thank you for your interest in helping with MacroKata. 4 | 5 | These instructions are probably incomplete (feel free to PR to add to them)! 6 | 7 | ## What are `solution.diff` files? 8 | 9 | Since we have to maintain two separate files (the starter code, and the solution code), 10 | it's very easy to make a change in one that affects the other. 11 | 12 | Therefore, `solution.diff` files describe what the correct diff is between a starter 13 | code and solution code. If you intentionally change this, use the `macrokata update-diff` 14 | command to update the file. Part of any PR will be checking that this diff is sane. 15 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107" 10 | dependencies = [ 11 | "cfg-if", 12 | "getrandom", 13 | "once_cell", 14 | "version_check", 15 | ] 16 | 17 | [[package]] 18 | name = "atty" 19 | version = "0.2.14" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 22 | dependencies = [ 23 | "hermit-abi", 24 | "libc", 25 | "winapi", 26 | ] 27 | 28 | [[package]] 29 | name = "bitflags" 30 | version = "1.3.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 33 | 34 | [[package]] 35 | name = "cfg-if" 36 | version = "1.0.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 39 | 40 | [[package]] 41 | name = "clap" 42 | version = "4.0.26" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" 45 | dependencies = [ 46 | "atty", 47 | "bitflags", 48 | "clap_derive", 49 | "clap_lex", 50 | "once_cell", 51 | "strsim", 52 | "termcolor", 53 | ] 54 | 55 | [[package]] 56 | name = "clap_derive" 57 | version = "4.0.21" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" 60 | dependencies = [ 61 | "heck", 62 | "proc-macro-error", 63 | "proc-macro2", 64 | "quote", 65 | "syn", 66 | ] 67 | 68 | [[package]] 69 | name = "clap_lex" 70 | version = "0.3.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" 73 | dependencies = [ 74 | "os_str_bytes", 75 | ] 76 | 77 | [[package]] 78 | name = "getrandom" 79 | version = "0.2.8" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 82 | dependencies = [ 83 | "cfg-if", 84 | "libc", 85 | "wasi", 86 | ] 87 | 88 | [[package]] 89 | name = "glob" 90 | version = "0.3.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 93 | 94 | [[package]] 95 | name = "hashbrown" 96 | version = "0.12.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 99 | 100 | [[package]] 101 | name = "heck" 102 | version = "0.4.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 105 | 106 | [[package]] 107 | name = "hermit-abi" 108 | version = "0.1.19" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 111 | dependencies = [ 112 | "libc", 113 | ] 114 | 115 | [[package]] 116 | name = "imara-diff" 117 | version = "0.1.5" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8" 120 | dependencies = [ 121 | "ahash", 122 | "hashbrown", 123 | ] 124 | 125 | [[package]] 126 | name = "libc" 127 | version = "0.2.137" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 130 | 131 | [[package]] 132 | name = "macrokata" 133 | version = "0.3.1" 134 | dependencies = [ 135 | "atty", 136 | "clap", 137 | "glob", 138 | "imara-diff", 139 | ] 140 | 141 | [[package]] 142 | name = "once_cell" 143 | version = "1.16.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 146 | 147 | [[package]] 148 | name = "os_str_bytes" 149 | version = "6.4.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" 152 | 153 | [[package]] 154 | name = "proc-macro-error" 155 | version = "1.0.4" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 158 | dependencies = [ 159 | "proc-macro-error-attr", 160 | "proc-macro2", 161 | "quote", 162 | "syn", 163 | "version_check", 164 | ] 165 | 166 | [[package]] 167 | name = "proc-macro-error-attr" 168 | version = "1.0.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 171 | dependencies = [ 172 | "proc-macro2", 173 | "quote", 174 | "version_check", 175 | ] 176 | 177 | [[package]] 178 | name = "proc-macro2" 179 | version = "1.0.47" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 182 | dependencies = [ 183 | "unicode-ident", 184 | ] 185 | 186 | [[package]] 187 | name = "quote" 188 | version = "1.0.21" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 191 | dependencies = [ 192 | "proc-macro2", 193 | ] 194 | 195 | [[package]] 196 | name = "strsim" 197 | version = "0.10.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 200 | 201 | [[package]] 202 | name = "syn" 203 | version = "1.0.103" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 206 | dependencies = [ 207 | "proc-macro2", 208 | "quote", 209 | "unicode-ident", 210 | ] 211 | 212 | [[package]] 213 | name = "termcolor" 214 | version = "1.1.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 217 | dependencies = [ 218 | "winapi-util", 219 | ] 220 | 221 | [[package]] 222 | name = "unicode-ident" 223 | version = "1.0.5" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 226 | 227 | [[package]] 228 | name = "version_check" 229 | version = "0.9.4" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 232 | 233 | [[package]] 234 | name = "wasi" 235 | version = "0.11.0+wasi-snapshot-preview1" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 238 | 239 | [[package]] 240 | name = "winapi" 241 | version = "0.3.9" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 244 | dependencies = [ 245 | "winapi-i686-pc-windows-gnu", 246 | "winapi-x86_64-pc-windows-gnu", 247 | ] 248 | 249 | [[package]] 250 | name = "winapi-i686-pc-windows-gnu" 251 | version = "0.4.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 254 | 255 | [[package]] 256 | name = "winapi-util" 257 | version = "0.1.5" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 260 | dependencies = [ 261 | "winapi", 262 | ] 263 | 264 | [[package]] 265 | name = "winapi-x86_64-pc-windows-gnu" 266 | version = "0.4.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 269 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macrokata" 3 | version = "0.3.1" 4 | edition = "2021" 5 | default-run = "macrokata" 6 | 7 | [[bin]] 8 | name = "macrokata" 9 | path = "src/main.rs" 10 | 11 | [[bin]] 12 | name = "01_my_first_macro" 13 | path = "exercises/01_my_first_macro/main.rs" 14 | 15 | [[bin]] 16 | name = "01_my_first_macro_soln" 17 | path = "exercises/01_my_first_macro/solutions/main.rs" 18 | 19 | [[bin]] 20 | name = "02_numbers" 21 | path = "exercises/02_numbers/main.rs" 22 | 23 | [[bin]] 24 | name = "02_numbers_soln" 25 | path = "exercises/02_numbers/solutions/main.rs" 26 | 27 | [[bin]] 28 | name = "03_literal_variables" 29 | path = "exercises/03_literal_variables/main.rs" 30 | 31 | [[bin]] 32 | name = "03_literal_variables_soln" 33 | path = "exercises/03_literal_variables/solutions/main.rs" 34 | 35 | [[bin]] 36 | name = "04_expression_variables" 37 | path = "exercises/04_expression_variables/main.rs" 38 | 39 | [[bin]] 40 | name = "04_expression_variables_soln" 41 | path = "exercises/04_expression_variables/solutions/main.rs" 42 | 43 | [[bin]] 44 | name = "05_more_complex_example" 45 | path = "exercises/05_more_complex_example/main.rs" 46 | 47 | [[bin]] 48 | name = "05_more_complex_example_soln" 49 | path = "exercises/05_more_complex_example/solutions/main.rs" 50 | 51 | [[bin]] 52 | name = "06_repetition" 53 | path = "exercises/06_repetition/main.rs" 54 | 55 | [[bin]] 56 | name = "06_repetition_soln" 57 | path = "exercises/06_repetition/solutions/main.rs" 58 | 59 | [[bin]] 60 | name = "07_more_repetition" 61 | path = "exercises/07_more_repetition/main.rs" 62 | 63 | [[bin]] 64 | name = "07_more_repetition_soln" 65 | path = "exercises/07_more_repetition/solutions/main.rs" 66 | 67 | [[bin]] 68 | name = "08_nested_repetition" 69 | path = "exercises/08_nested_repetition/main.rs" 70 | 71 | [[bin]] 72 | name = "08_nested_repetition_soln" 73 | path = "exercises/08_nested_repetition/solutions/main.rs" 74 | 75 | [[bin]] 76 | name = "09_ambiguity_and_ordering" 77 | path = "exercises/09_ambiguity_and_ordering/main.rs" 78 | 79 | [[bin]] 80 | name = "09_ambiguity_and_ordering_soln" 81 | path = "exercises/09_ambiguity_and_ordering/solutions/main.rs" 82 | 83 | [[bin]] 84 | name = "10_macros_calling_macros" 85 | path = "exercises/10_macros_calling_macros/main.rs" 86 | 87 | [[bin]] 88 | name = "10_macros_calling_macros_soln" 89 | path = "exercises/10_macros_calling_macros/solutions/main.rs" 90 | 91 | [[bin]] 92 | name = "11_macro_recursion" 93 | path = "exercises/11_macro_recursion/main.rs" 94 | 95 | [[bin]] 96 | name = "11_macro_recursion_soln" 97 | path = "exercises/11_macro_recursion/solutions/main.rs" 98 | 99 | [[bin]] 100 | name = "12_hygienic_macros" 101 | path = "exercises/12_hygienic_macros/main.rs" 102 | 103 | [[bin]] 104 | name = "12_hygienic_macros_soln" 105 | path = "exercises/12_hygienic_macros/solutions/main.rs" 106 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 107 | 108 | [dependencies] 109 | atty = "0.2.14" 110 | clap = { version = "4.0.26", features = ["derive"] } 111 | imara-diff = "0.1.5" 112 | glob = "0.3.0" 113 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2022 Tom Kunc [tom tfpk dev]. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MacroKata 2 | 3 | Welcome to MacroKata, a set of exercises which you can use to learn how to write 4 | macros in Rust. When completing each task, there are three goals: 5 | 6 | - Get your code to compile without warnings or errors. 7 | - Get your code to "work correctly" (i.e. produce the same output) 8 | - Importantly, *generate the same code* as what the sample solution does. 9 | 10 | You should complete the kata in order, as they increase in 11 | difficulty, and depend on previous kata. 12 | 13 | This set of exercises is written for people who have already spent some time 14 | programming in Rust. Before completing this, work through a Rust tutorial 15 | and build some small programs yourself. 16 | 17 | ## Getting Started 18 | 19 | Clone this repository: 20 | 21 | ``` sh 22 | $ git clone https://www.github.com/tfpk/macrokata/ 23 | ``` 24 | 25 | You will also need to install the Rust "nightly" toolchain, so that we can show 26 | expanded macros: 27 | 28 | ``` sh 29 | $ rustup toolchain install nightly 30 | ``` 31 | 32 | Next, install `cargo-expand`: 33 | 34 | ``` sh 35 | $ cargo install cargo-expand 36 | ``` 37 | 38 | Build the main binary provided with this repo: 39 | 40 | ``` sh 41 | $ cargo build --bin macrokata 42 | ``` 43 | 44 | You can find the first kata (`my_first_macro`) inside `exercises/01_my_first_macro`. 45 | Read the [first chapter of the book](https://tfpk.github.io/macrokata/01_README.html) 46 | and get started by editing the `main.rs` file. 47 | 48 | To compare your expanded code to the "goal", use the `test` subcommand: 49 | 50 | ``` sh 51 | $ cargo run -- test 01_my_first_macro 52 | ``` 53 | 54 | You can run your own code as follows: 55 | 56 | ``` sh 57 | $ cargo run --bin 01_my_first_macro 58 | ``` 59 | 60 | ## How To Learn About Procedural Macros 61 | 62 | I was originally planning to expand `macrokata` into discussing procedural 63 | macros as well. As I was researching that, I found dtolnay's superlative [Proc 64 | Macro Workshop](https://github.com/dtolnay/proc-macro-workshop). 65 | [Jon Gjengset's video on proc-macros](https://www.youtube.com/watch?v=geovSK3wMB8) 66 | is also a phenomenal resource (despite its length). 67 | 68 | I've put my attempt to write something like that on hold because I think the 69 | above is better in every way. Do file an issue if there's something that we 70 | could do here to complement that workshop though. 71 | -------------------------------------------------------------------------------- /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | doctest_cache 3 | -------------------------------------------------------------------------------- /book/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Tom Kunc "] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "MacroKata" 7 | 8 | [build] 9 | extra-watch-dirs = ["../po"] 10 | 11 | [preprocessor.keeper] 12 | 13 | [preprocessor.cmdrun] 14 | 15 | [preprocessor.gettext] 16 | 17 | [output.html] 18 | no-section-label = true 19 | site-url = "/macrokata/" 20 | git-repository-url = "https://github.com/tfpk/macrokata/" 21 | additional-css = ["css/language-picker.css"] 22 | -------------------------------------------------------------------------------- /book/css/language-picker.css: -------------------------------------------------------------------------------- 1 | #language-list { 2 | left: auto; 3 | right: 10px; 4 | } 5 | 6 | #language-list a { 7 | color: inherit; 8 | } 9 | -------------------------------------------------------------------------------- /book/po: -------------------------------------------------------------------------------- 1 | ../po -------------------------------------------------------------------------------- /book/src/01_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/01_my_first_macro/README.md -------------------------------------------------------------------------------- /book/src/02_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/02_numbers/README.md -------------------------------------------------------------------------------- /book/src/03_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/03_literal_variables/README.md -------------------------------------------------------------------------------- /book/src/04_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/04_expression_variables/README.md -------------------------------------------------------------------------------- /book/src/05_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/05_more_complex_example/README.md -------------------------------------------------------------------------------- /book/src/06_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/06_repetition/README.md -------------------------------------------------------------------------------- /book/src/07_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/07_more_repetition/README.md -------------------------------------------------------------------------------- /book/src/08_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/08_nested_repetition/README.md -------------------------------------------------------------------------------- /book/src/09_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/09_ambiguity_and_ordering/README.md -------------------------------------------------------------------------------- /book/src/10_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/10_macros_calling_macros/README.md -------------------------------------------------------------------------------- /book/src/11_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/11_macro_recursion/README.md -------------------------------------------------------------------------------- /book/src/12_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/12_hygienic_macros/README.md -------------------------------------------------------------------------------- /book/src/13_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/13_scoping_importing_and_exporting/README.md -------------------------------------------------------------------------------- /book/src/99_README.md: -------------------------------------------------------------------------------- 1 | ../../exercises/99_extra_reading/README.md -------------------------------------------------------------------------------- /book/src/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [00: Introduction](./README.md) 4 | - [01: My First Macro](./01_README.md) 5 | - [02: Numbers](./02_README.md) 6 | - [03: Literal Meta-Variables](./03_README.md) 7 | - [04: Expression Meta-Variables](./04_README.md) 8 | - [05: More Complex Example](./05_README.md) 9 | - [06: Repetition](./06_README.md) 10 | - [07: More Repetition](./07_README.md) 11 | - [08: Nested Repetition](./08_README.md) 12 | - [09: Ambiguity and Ordering](./09_README.md) 13 | - [10: Macros Calling Macros](./10_README.md) 14 | - [11: Macro Recursion](./11_README.md) 15 | - [12: Macro Hygiene](./12_README.md) 16 | - [13: Scoping, Importing and Exporting](./13_README.md) 17 | - [14: Extra Reading](./99_README.md) 18 | -------------------------------------------------------------------------------- /book/theme/book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Fix back button cache problem 4 | window.onunload = function () { }; 5 | 6 | function isPlaygroundModified(playground) { 7 | let code_block = playground.querySelector("code"); 8 | if (window.ace && code_block.classList.contains("editable")) { 9 | let editor = window.ace.edit(code_block); 10 | return editor.getValue() != editor.originalCode; 11 | } else { 12 | return false; 13 | } 14 | } 15 | 16 | // Global variable, shared between modules 17 | function playground_text(playground, hidden = true) { 18 | let code_block = playground.querySelector("code"); 19 | 20 | if (window.ace && code_block.classList.contains("editable")) { 21 | let editor = window.ace.edit(code_block); 22 | return editor.getValue(); 23 | } else if (hidden) { 24 | return code_block.textContent; 25 | } else { 26 | return code_block.innerText; 27 | } 28 | } 29 | 30 | (function codeSnippets() { 31 | function fetch_with_timeout(url, options, timeout = 15000) { 32 | return Promise.race([ 33 | fetch(url, options), 34 | new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) 35 | ]); 36 | } 37 | 38 | var playgrounds = Array.from(document.querySelectorAll(".playground")); 39 | if (playgrounds.length > 0) { 40 | fetch_with_timeout("https://play.rust-lang.org/meta/crates", { 41 | headers: { 42 | 'Content-Type': "application/json", 43 | }, 44 | method: 'POST', 45 | mode: 'cors', 46 | }) 47 | .then(response => response.json()) 48 | .then(response => { 49 | // get list of crates available in the rust playground 50 | let playground_crates = response.crates.map(item => item["id"]); 51 | playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); 52 | }); 53 | } 54 | 55 | function handle_crate_list_update(playground_block, playground_crates) { 56 | // update the play buttons after receiving the response 57 | update_play_button(playground_block, playground_crates); 58 | 59 | // and install on change listener to dynamically update ACE editors 60 | if (window.ace) { 61 | let code_block = playground_block.querySelector("code"); 62 | if (code_block.classList.contains("editable")) { 63 | let editor = window.ace.edit(code_block); 64 | editor.addEventListener("change", function (e) { 65 | update_play_button(playground_block, playground_crates); 66 | }); 67 | // add Ctrl-Enter command to execute rust code 68 | editor.commands.addCommand({ 69 | name: "run", 70 | bindKey: { 71 | win: "Ctrl-Enter", 72 | mac: "Ctrl-Enter" 73 | }, 74 | exec: _editor => run_rust_code(playground_block) 75 | }); 76 | } 77 | } 78 | } 79 | 80 | // updates the visibility of play button based on `no_run` class and 81 | // used crates vs ones available on http://play.rust-lang.org 82 | function update_play_button(pre_block, playground_crates) { 83 | var play_button = pre_block.querySelector(".play-button"); 84 | 85 | // skip if code is `no_run` 86 | if (pre_block.querySelector('code').classList.contains("no_run")) { 87 | play_button.classList.add("hidden"); 88 | return; 89 | } 90 | 91 | // get list of `extern crate`'s from snippet 92 | var txt = playground_text(pre_block); 93 | var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; 94 | var snippet_crates = []; 95 | var item; 96 | while (item = re.exec(txt)) { 97 | snippet_crates.push(item[1]); 98 | } 99 | 100 | // check if all used crates are available on play.rust-lang.org 101 | var all_available = snippet_crates.every(function (elem) { 102 | return playground_crates.indexOf(elem) > -1; 103 | }); 104 | 105 | if (all_available) { 106 | play_button.classList.remove("hidden"); 107 | } else { 108 | play_button.classList.add("hidden"); 109 | } 110 | } 111 | 112 | function run_rust_code(code_block) { 113 | var result_block = code_block.querySelector(".result"); 114 | if (!result_block) { 115 | result_block = document.createElement('code'); 116 | result_block.className = 'result hljs language-bash'; 117 | 118 | code_block.append(result_block); 119 | } 120 | 121 | let text = playground_text(code_block); 122 | let classes = code_block.querySelector('code').classList; 123 | let edition = "2015"; 124 | if(classes.contains("edition2018")) { 125 | edition = "2018"; 126 | } else if(classes.contains("edition2021")) { 127 | edition = "2021"; 128 | } 129 | var params = { 130 | version: "stable", 131 | optimize: "0", 132 | code: text, 133 | edition: edition 134 | }; 135 | 136 | if (text.indexOf("#![feature") !== -1) { 137 | params.version = "nightly"; 138 | } 139 | 140 | result_block.innerText = "Running..."; 141 | 142 | const playgroundModified = isPlaygroundModified(code_block); 143 | const startTime = window.performance.now(); 144 | fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { 145 | headers: { 146 | 'Content-Type': "application/json", 147 | }, 148 | method: 'POST', 149 | mode: 'cors', 150 | body: JSON.stringify(params) 151 | }) 152 | .then(response => response.json()) 153 | .then(response => { 154 | const endTime = window.performance.now(); 155 | gtag("event", "playground", { 156 | "modified": playgroundModified, 157 | "error": (response.error == null) ? null : 'compilation_error', 158 | "latency": (endTime - startTime) / 1000, 159 | }); 160 | 161 | if (response.result.trim() === '') { 162 | result_block.innerText = "No output"; 163 | result_block.classList.add("result-no-output"); 164 | } else { 165 | result_block.innerText = response.result; 166 | result_block.classList.remove("result-no-output"); 167 | } 168 | }) 169 | .catch(error => { 170 | const endTime = window.performance.now(); 171 | gtag("event", "playground", { 172 | "modified": playgroundModified, 173 | "error": error.message, 174 | "latency": (endTime - startTime) / 1000, 175 | }); 176 | result_block.innerText = "Playground Communication: " + error.message 177 | }); 178 | } 179 | 180 | // Syntax highlighting Configuration 181 | hljs.configure({ 182 | tabReplace: ' ', // 4 spaces 183 | languages: [], // Languages used for auto-detection 184 | }); 185 | 186 | let code_nodes = Array 187 | .from(document.querySelectorAll('code')) 188 | // Don't highlight `inline code` blocks in headers. 189 | .filter(function (node) {return !node.parentElement.classList.contains("header"); }); 190 | 191 | if (window.ace) { 192 | // language-rust class needs to be removed for editable 193 | // blocks or highlightjs will capture events 194 | code_nodes 195 | .filter(function (node) {return node.classList.contains("editable"); }) 196 | .forEach(function (block) { block.classList.remove('language-rust'); }); 197 | 198 | code_nodes 199 | .filter(function (node) {return !node.classList.contains("editable"); }) 200 | .forEach(function (block) { hljs.highlightBlock(block); }); 201 | } else { 202 | code_nodes.forEach(function (block) { hljs.highlightBlock(block); }); 203 | } 204 | 205 | // Adding the hljs class gives code blocks the color css 206 | // even if highlighting doesn't apply 207 | code_nodes.forEach(function (block) { block.classList.add('hljs'); }); 208 | 209 | Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) { 210 | 211 | var lines = Array.from(block.querySelectorAll('.boring')); 212 | // If no lines were hidden, return 213 | if (!lines.length) { return; } 214 | block.classList.add("hide-boring"); 215 | 216 | var buttons = document.createElement('div'); 217 | buttons.className = 'buttons'; 218 | buttons.innerHTML = ""; 219 | 220 | // add expand button 221 | var pre_block = block.parentNode; 222 | pre_block.insertBefore(buttons, pre_block.firstChild); 223 | 224 | pre_block.querySelector('.buttons').addEventListener('click', function (e) { 225 | if (e.target.classList.contains('fa-eye')) { 226 | e.target.classList.remove('fa-eye'); 227 | e.target.classList.add('fa-eye-slash'); 228 | e.target.title = 'Hide lines'; 229 | e.target.setAttribute('aria-label', e.target.title); 230 | 231 | block.classList.remove('hide-boring'); 232 | } else if (e.target.classList.contains('fa-eye-slash')) { 233 | e.target.classList.remove('fa-eye-slash'); 234 | e.target.classList.add('fa-eye'); 235 | e.target.title = 'Show hidden lines'; 236 | e.target.setAttribute('aria-label', e.target.title); 237 | 238 | block.classList.add('hide-boring'); 239 | } 240 | }); 241 | }); 242 | 243 | if (window.playground_copyable) { 244 | Array.from(document.querySelectorAll('pre code')).forEach(function (block) { 245 | var pre_block = block.parentNode; 246 | if (!pre_block.classList.contains('playground')) { 247 | var buttons = pre_block.querySelector(".buttons"); 248 | if (!buttons) { 249 | buttons = document.createElement('div'); 250 | buttons.className = 'buttons'; 251 | pre_block.insertBefore(buttons, pre_block.firstChild); 252 | } 253 | 254 | var clipButton = document.createElement('button'); 255 | clipButton.className = 'fa fa-copy clip-button'; 256 | clipButton.title = 'Copy to clipboard'; 257 | clipButton.setAttribute('aria-label', clipButton.title); 258 | clipButton.innerHTML = ''; 259 | 260 | buttons.insertBefore(clipButton, buttons.firstChild); 261 | } 262 | }); 263 | } 264 | 265 | // Process playground code blocks 266 | Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) { 267 | // Add play button 268 | var buttons = pre_block.querySelector(".buttons"); 269 | if (!buttons) { 270 | buttons = document.createElement('div'); 271 | buttons.className = 'buttons'; 272 | pre_block.insertBefore(buttons, pre_block.firstChild); 273 | } 274 | 275 | var runCodeButton = document.createElement('button'); 276 | runCodeButton.className = 'fa fa-play play-button'; 277 | runCodeButton.hidden = true; 278 | runCodeButton.title = 'Run this code'; 279 | runCodeButton.setAttribute('aria-label', runCodeButton.title); 280 | 281 | buttons.insertBefore(runCodeButton, buttons.firstChild); 282 | runCodeButton.addEventListener('click', function (e) { 283 | run_rust_code(pre_block); 284 | }); 285 | 286 | if (window.playground_copyable) { 287 | var copyCodeClipboardButton = document.createElement('button'); 288 | copyCodeClipboardButton.className = 'fa fa-copy clip-button'; 289 | copyCodeClipboardButton.innerHTML = ''; 290 | copyCodeClipboardButton.title = 'Copy to clipboard'; 291 | copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); 292 | 293 | buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); 294 | } 295 | 296 | let code_block = pre_block.querySelector("code"); 297 | if (window.ace && code_block.classList.contains("editable")) { 298 | var undoChangesButton = document.createElement('button'); 299 | undoChangesButton.className = 'fa fa-history reset-button'; 300 | undoChangesButton.title = 'Undo changes'; 301 | undoChangesButton.setAttribute('aria-label', undoChangesButton.title); 302 | 303 | buttons.insertBefore(undoChangesButton, buttons.firstChild); 304 | 305 | undoChangesButton.addEventListener('click', function () { 306 | let editor = window.ace.edit(code_block); 307 | editor.setValue(editor.originalCode); 308 | editor.clearSelection(); 309 | }); 310 | } 311 | }); 312 | })(); 313 | 314 | (function themes() { 315 | var html = document.querySelector('html'); 316 | var themeToggleButton = document.getElementById('theme-toggle'); 317 | var themePopup = document.getElementById('theme-list'); 318 | var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); 319 | var stylesheets = { 320 | ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), 321 | tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), 322 | highlight: document.querySelector("[href$='highlight.css']"), 323 | }; 324 | 325 | function showThemes() { 326 | themePopup.style.display = 'block'; 327 | themeToggleButton.setAttribute('aria-expanded', true); 328 | themePopup.querySelector("button#" + get_theme()).focus(); 329 | } 330 | 331 | function updateThemeSelected() { 332 | themePopup.querySelectorAll('.theme-selected').forEach(function (el) { 333 | el.classList.remove('theme-selected'); 334 | }); 335 | themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected'); 336 | } 337 | 338 | function hideThemes() { 339 | themePopup.style.display = 'none'; 340 | themeToggleButton.setAttribute('aria-expanded', false); 341 | themeToggleButton.focus(); 342 | } 343 | 344 | function get_theme() { 345 | var theme; 346 | try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { } 347 | if (theme === null || theme === undefined) { 348 | return default_theme; 349 | } else { 350 | return theme; 351 | } 352 | } 353 | 354 | function set_theme(theme, store = true) { 355 | let ace_theme; 356 | 357 | if (theme == 'coal' || theme == 'navy') { 358 | stylesheets.ayuHighlight.disabled = true; 359 | stylesheets.tomorrowNight.disabled = false; 360 | stylesheets.highlight.disabled = true; 361 | 362 | ace_theme = "ace/theme/tomorrow_night"; 363 | } else if (theme == 'ayu') { 364 | stylesheets.ayuHighlight.disabled = false; 365 | stylesheets.tomorrowNight.disabled = true; 366 | stylesheets.highlight.disabled = true; 367 | ace_theme = "ace/theme/tomorrow_night"; 368 | } else { 369 | stylesheets.ayuHighlight.disabled = true; 370 | stylesheets.tomorrowNight.disabled = true; 371 | stylesheets.highlight.disabled = false; 372 | ace_theme = "ace/theme/dawn"; 373 | } 374 | 375 | setTimeout(function () { 376 | themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor; 377 | }, 1); 378 | 379 | if (window.ace && window.editors) { 380 | window.editors.forEach(function (editor) { 381 | editor.setTheme(ace_theme); 382 | }); 383 | } 384 | 385 | var previousTheme = get_theme(); 386 | 387 | if (store) { 388 | try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } 389 | } 390 | 391 | html.classList.remove(previousTheme); 392 | html.classList.add(theme); 393 | updateThemeSelected(); 394 | } 395 | 396 | // Set theme 397 | var theme = get_theme(); 398 | 399 | set_theme(theme, false); 400 | 401 | themeToggleButton.addEventListener('click', function () { 402 | if (themePopup.style.display === 'block') { 403 | hideThemes(); 404 | } else { 405 | showThemes(); 406 | } 407 | }); 408 | 409 | themePopup.addEventListener('click', function (e) { 410 | var theme; 411 | if (e.target.className === "theme") { 412 | theme = e.target.id; 413 | } else if (e.target.parentElement.className === "theme") { 414 | theme = e.target.parentElement.id; 415 | } else { 416 | return; 417 | } 418 | set_theme(theme); 419 | }); 420 | 421 | themePopup.addEventListener('focusout', function(e) { 422 | // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) 423 | if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { 424 | hideThemes(); 425 | } 426 | }); 427 | 428 | // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628 429 | document.addEventListener('click', function(e) { 430 | if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { 431 | hideThemes(); 432 | } 433 | }); 434 | 435 | document.addEventListener('keydown', function (e) { 436 | if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } 437 | if (!themePopup.contains(e.target)) { return; } 438 | 439 | switch (e.key) { 440 | case 'Escape': 441 | e.preventDefault(); 442 | hideThemes(); 443 | break; 444 | case 'ArrowUp': 445 | e.preventDefault(); 446 | var li = document.activeElement.parentElement; 447 | if (li && li.previousElementSibling) { 448 | li.previousElementSibling.querySelector('button').focus(); 449 | } 450 | break; 451 | case 'ArrowDown': 452 | e.preventDefault(); 453 | var li = document.activeElement.parentElement; 454 | if (li && li.nextElementSibling) { 455 | li.nextElementSibling.querySelector('button').focus(); 456 | } 457 | break; 458 | case 'Home': 459 | e.preventDefault(); 460 | themePopup.querySelector('li:first-child button').focus(); 461 | break; 462 | case 'End': 463 | e.preventDefault(); 464 | themePopup.querySelector('li:last-child button').focus(); 465 | break; 466 | } 467 | }); 468 | })(); 469 | 470 | (function sidebar() { 471 | var html = document.querySelector("html"); 472 | var sidebar = document.getElementById("sidebar"); 473 | var sidebarLinks = document.querySelectorAll('#sidebar a'); 474 | var sidebarToggleButton = document.getElementById("sidebar-toggle"); 475 | var sidebarResizeHandle = document.getElementById("sidebar-resize-handle"); 476 | var firstContact = null; 477 | 478 | function showSidebar() { 479 | html.classList.remove('sidebar-hidden') 480 | html.classList.add('sidebar-visible'); 481 | Array.from(sidebarLinks).forEach(function (link) { 482 | link.setAttribute('tabIndex', 0); 483 | }); 484 | sidebarToggleButton.setAttribute('aria-expanded', true); 485 | sidebar.setAttribute('aria-hidden', false); 486 | try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } 487 | } 488 | 489 | 490 | var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle'); 491 | 492 | function toggleSection(ev) { 493 | ev.currentTarget.parentElement.classList.toggle('expanded'); 494 | } 495 | 496 | Array.from(sidebarAnchorToggles).forEach(function (el) { 497 | el.addEventListener('click', toggleSection); 498 | }); 499 | 500 | function hideSidebar() { 501 | html.classList.remove('sidebar-visible') 502 | html.classList.add('sidebar-hidden'); 503 | Array.from(sidebarLinks).forEach(function (link) { 504 | link.setAttribute('tabIndex', -1); 505 | }); 506 | sidebarToggleButton.setAttribute('aria-expanded', false); 507 | sidebar.setAttribute('aria-hidden', true); 508 | try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } 509 | } 510 | 511 | // Toggle sidebar 512 | sidebarToggleButton.addEventListener('click', function sidebarToggle() { 513 | if (html.classList.contains("sidebar-hidden")) { 514 | var current_width = parseInt( 515 | document.documentElement.style.getPropertyValue('--sidebar-width'), 10); 516 | if (current_width < 150) { 517 | document.documentElement.style.setProperty('--sidebar-width', '150px'); 518 | } 519 | showSidebar(); 520 | } else if (html.classList.contains("sidebar-visible")) { 521 | hideSidebar(); 522 | } else { 523 | if (getComputedStyle(sidebar)['transform'] === 'none') { 524 | hideSidebar(); 525 | } else { 526 | showSidebar(); 527 | } 528 | } 529 | }); 530 | 531 | sidebarResizeHandle.addEventListener('mousedown', initResize, false); 532 | 533 | function initResize(e) { 534 | window.addEventListener('mousemove', resize, false); 535 | window.addEventListener('mouseup', stopResize, false); 536 | html.classList.add('sidebar-resizing'); 537 | } 538 | function resize(e) { 539 | var pos = (e.clientX - sidebar.offsetLeft); 540 | if (pos < 20) { 541 | hideSidebar(); 542 | } else { 543 | if (html.classList.contains("sidebar-hidden")) { 544 | showSidebar(); 545 | } 546 | pos = Math.min(pos, window.innerWidth - 100); 547 | document.documentElement.style.setProperty('--sidebar-width', pos + 'px'); 548 | } 549 | } 550 | //on mouseup remove windows functions mousemove & mouseup 551 | function stopResize(e) { 552 | html.classList.remove('sidebar-resizing'); 553 | window.removeEventListener('mousemove', resize, false); 554 | window.removeEventListener('mouseup', stopResize, false); 555 | } 556 | 557 | document.addEventListener('touchstart', function (e) { 558 | firstContact = { 559 | x: e.touches[0].clientX, 560 | time: Date.now() 561 | }; 562 | }, { passive: true }); 563 | 564 | document.addEventListener('touchmove', function (e) { 565 | if (!firstContact) 566 | return; 567 | 568 | var curX = e.touches[0].clientX; 569 | var xDiff = curX - firstContact.x, 570 | tDiff = Date.now() - firstContact.time; 571 | 572 | if (tDiff < 250 && Math.abs(xDiff) >= 150) { 573 | if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) 574 | showSidebar(); 575 | else if (xDiff < 0 && curX < 300) 576 | hideSidebar(); 577 | 578 | firstContact = null; 579 | } 580 | }, { passive: true }); 581 | 582 | // Scroll sidebar to current active section 583 | var activeSection = document.getElementById("sidebar").querySelector(".active"); 584 | if (activeSection) { 585 | // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView 586 | activeSection.scrollIntoView({ block: 'center' }); 587 | } 588 | })(); 589 | 590 | (function chapterNavigation() { 591 | document.addEventListener('keydown', function (e) { 592 | if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } 593 | if (window.search && window.search.hasFocus()) { return; } 594 | 595 | switch (e.key) { 596 | case 'ArrowRight': 597 | e.preventDefault(); 598 | var nextButton = document.querySelector('.nav-chapters.next'); 599 | if (nextButton) { 600 | window.location.href = nextButton.href; 601 | } 602 | break; 603 | case 'ArrowLeft': 604 | e.preventDefault(); 605 | var previousButton = document.querySelector('.nav-chapters.previous'); 606 | if (previousButton) { 607 | window.location.href = previousButton.href; 608 | } 609 | break; 610 | } 611 | }); 612 | })(); 613 | 614 | (function clipboard() { 615 | var clipButtons = document.querySelectorAll('.clip-button'); 616 | 617 | function hideTooltip(elem) { 618 | elem.firstChild.innerText = ""; 619 | elem.className = 'fa fa-copy clip-button'; 620 | } 621 | 622 | function showTooltip(elem, msg) { 623 | elem.firstChild.innerText = msg; 624 | elem.className = 'fa fa-copy tooltipped'; 625 | } 626 | 627 | var clipboardSnippets = new ClipboardJS('.clip-button', { 628 | text: function (trigger) { 629 | hideTooltip(trigger); 630 | let playground = trigger.closest("pre"); 631 | return playground_text(playground, false); 632 | } 633 | }); 634 | 635 | Array.from(clipButtons).forEach(function (clipButton) { 636 | clipButton.addEventListener('mouseout', function (e) { 637 | hideTooltip(e.currentTarget); 638 | }); 639 | }); 640 | 641 | clipboardSnippets.on('success', function (e) { 642 | e.clearSelection(); 643 | showTooltip(e.trigger, "Copied!"); 644 | }); 645 | 646 | clipboardSnippets.on('error', function (e) { 647 | showTooltip(e.trigger, "Clipboard error!"); 648 | }); 649 | })(); 650 | 651 | (function scrollToTop () { 652 | var menuTitle = document.querySelector('.menu-title'); 653 | 654 | menuTitle.addEventListener('click', function () { 655 | document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); 656 | }); 657 | })(); 658 | 659 | (function controllMenu() { 660 | var menu = document.getElementById('menu-bar'); 661 | 662 | (function controllPosition() { 663 | var scrollTop = document.scrollingElement.scrollTop; 664 | var prevScrollTop = scrollTop; 665 | var minMenuY = -menu.clientHeight - 50; 666 | // When the script loads, the page can be at any scroll (e.g. if you reforesh it). 667 | menu.style.top = scrollTop + 'px'; 668 | // Same as parseInt(menu.style.top.slice(0, -2), but faster 669 | var topCache = menu.style.top.slice(0, -2); 670 | menu.classList.remove('sticky'); 671 | var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster 672 | document.addEventListener('scroll', function () { 673 | scrollTop = Math.max(document.scrollingElement.scrollTop, 0); 674 | // `null` means that it doesn't need to be updated 675 | var nextSticky = null; 676 | var nextTop = null; 677 | var scrollDown = scrollTop > prevScrollTop; 678 | var menuPosAbsoluteY = topCache - scrollTop; 679 | if (scrollDown) { 680 | nextSticky = false; 681 | if (menuPosAbsoluteY > 0) { 682 | nextTop = prevScrollTop; 683 | } 684 | } else { 685 | if (menuPosAbsoluteY > 0) { 686 | nextSticky = true; 687 | } else if (menuPosAbsoluteY < minMenuY) { 688 | nextTop = prevScrollTop + minMenuY; 689 | } 690 | } 691 | if (nextSticky === true && stickyCache === false) { 692 | menu.classList.add('sticky'); 693 | stickyCache = true; 694 | } else if (nextSticky === false && stickyCache === true) { 695 | menu.classList.remove('sticky'); 696 | stickyCache = false; 697 | } 698 | if (nextTop !== null) { 699 | menu.style.top = nextTop + 'px'; 700 | topCache = nextTop; 701 | } 702 | prevScrollTop = scrollTop; 703 | }, { passive: true }); 704 | })(); 705 | (function controllBorder() { 706 | menu.classList.remove('bordered'); 707 | document.addEventListener('scroll', function () { 708 | if (menu.offsetTop === 0) { 709 | menu.classList.remove('bordered'); 710 | } else { 711 | menu.classList.add('bordered'); 712 | } 713 | }, { passive: true }); 714 | })(); 715 | })(); 716 | -------------------------------------------------------------------------------- /book/theme/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | {{#if is_print }} 8 | 9 | {{/if}} 10 | {{#if base_url}} 11 | 12 | {{/if}} 13 | 14 | 15 | {{> head}} 16 | 17 | 18 | 19 | 20 | 21 | {{#if favicon_svg}} 22 | 23 | {{/if}} 24 | {{#if favicon_png}} 25 | 26 | {{/if}} 27 | 28 | 29 | 30 | {{#if print_enable}} 31 | 32 | {{/if}} 33 | 34 | 35 | 36 | {{#if copy_fonts}} 37 | 38 | {{/if}} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {{#each additional_css}} 47 | 48 | {{/each}} 49 | 50 | {{#if mathjax_support}} 51 | 52 | 53 | {{/if}} 54 | 55 | 56 | 57 | 61 | 62 | 63 | 77 | 78 | 79 | 89 | 90 | 91 | 101 | 102 | 108 | 109 |
110 | 111 |
112 | {{> header}} 113 | 114 | 207 | 208 | {{#if search_enabled}} 209 | 219 | {{/if}} 220 | 221 | 222 | 229 | 230 |
231 |
232 | {{{ content }}} 233 |
234 | 235 | 251 |
252 |
253 | 254 | 267 | 268 |
269 | 270 | {{#if live_reload_endpoint}} 271 | 272 | 287 | {{/if}} 288 | 289 | {{#if google_analytics}} 290 | 291 | 306 | {{/if}} 307 | 308 | {{#if playground_line_numbers}} 309 | 312 | {{/if}} 313 | 314 | {{#if playground_copyable}} 315 | 318 | {{/if}} 319 | 320 | {{#if playground_js}} 321 | 322 | 323 | 324 | 325 | 326 | {{/if}} 327 | 328 | {{#if search_js}} 329 | 330 | 331 | 332 | {{/if}} 333 | 334 | 335 | 336 | 337 | 338 | 339 | {{#each additional_js}} 340 | 341 | {{/each}} 342 | 343 | {{#if is_print}} 344 | {{#if mathjax_support}} 345 | 352 | {{else}} 353 | 358 | {{/if}} 359 | {{/if}} 360 | 361 | 362 | 363 | -------------------------------------------------------------------------------- /exercises/01_my_first_macro/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 1: My First Macro 2 | 3 | Welcome to this introduction to Rust's Macro system. 4 | To complete each exercise (including this one), you should: 5 | 6 | * [ ] Read this file to understand the theory being tested, and what 7 | task you will be asked to complete. 8 | * [ ] Try and complete the `main.rs` file. 9 | * [ ] Test to see if your macro creates the same code we have, using 10 | `cargo run -- test 01_my_first_macro`. 11 | * [ ] Run your code, using `cargo run --bin 01_my_first_macro`, to see what it does. 12 | 13 | 14 | ## What are Macros? 15 | 16 | Rust's macros are a way of using code to generate code before compilation. 17 | Because the generation happens before the compiler does anything, you are given 18 | much more flexibility in what you can write. 19 | 20 | This allows you to break many of the syntax rules Rust imposes on you. For 21 | example, Rust does not allow "variadic" functions: functions with variable 22 | numbers of arguments. This makes a `println` function impossible -- it would 23 | have to take any number of arguments (`println("hello")` and `println("{}", 24 | 123)`, for example). 25 | 26 | Rust gets around this rule by using a `println!` macro. Before `println!` is 27 | compiled, Rust rewrites the macro into a function which takes a single array of 28 | arguments. That way, even though it looks to you like there are multiple 29 | arguments, once it's compiled there's always just one array. 30 | 31 | Macros can range from simple (e.g. reducing duplicated code) to complex (e.g. 32 | implementing HTML parsing inside of Rust). This guide aims to build you up from 33 | the simple to the complex. 34 | 35 | As mentioned, you've already used macros: `println!` for example, is a macro. 36 | `vec![]` is as well. Macros always have a name. To run a macro, call its name 37 | with a bang (`!`) afterwards, and then brackets (any of `()`, `[]` or `{}`) 38 | containing arguments. 39 | 40 | In other words, to run the macro `my_macro`, you'd say `my_macro!()` or 41 | `my_macro![]` or `my_macro!{}`. 42 | 43 | ## Macro Rules vs. Procedural Macros 44 | 45 | Rust has two macros systems, but this guide will only focus on one. 46 | `macro_rules!` are a special language to describe how to transform 47 | code into valid Rust code: this is the system we will focus on. 48 | Procedural macros (proc-macros) are a method of writing a Rust function 49 | which transforms an input piece of Rust code into an output piece. 50 | 51 | Proc Macros are useful, but complex, and not the subject of this guide. 52 | [You can read more about them here.](https://doc.rust-lang.org/reference/procedural-macros.html) 53 | 54 | ## How do I create one? 55 | 56 | The simplest form of macro looks like this: 57 | 58 | ```rust 59 | macro_rules! my_macro { 60 | () => { 61 | 3 62 | } 63 | } 64 | 65 | # fn main() { 66 | let _value = my_macro!(); 67 | # } 68 | ``` 69 | 70 | The `macro_rules!` instructs the compiler that there is a new macro you are 71 | defining. It is followed by the name of the macro, `my_macro`. The next line 72 | specifies a "rule". Inside the normal brackets is a "matcher" -- some text 73 | (formally, we refer to the text as "tokens") -- which Rust will use to decide 74 | which rule to execute. Inside the curly brackets is a "transcriber", which is 75 | what Rust will replace `my_macro!()` with. 76 | 77 | So, `my_macro!()` will be replaced by `3`. 78 | 79 | 80 | ## Exercise 1: My First Macro 81 | 82 | Your task is to write a macro named `show_output!()` which calls the 83 | `show_output()` function. 84 | 85 | You may not edit the `main` function, but it should eventually look like the 86 | following: 87 | 88 | 89 | 90 | ```rust,ignore 91 | 92 | ``` 93 | -------------------------------------------------------------------------------- /exercises/01_my_first_macro/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | // This function should be called by the `show_output!()` macro 3 | fn show_output() { 4 | println!("I should appear as the output.") 5 | } 6 | ////////// DO NOT CHANGE ABOVE HERE ///////// 7 | 8 | // TODO: create `show_output!()` macro. 9 | 10 | ////////// DO NOT CHANGE BELOW HERE ///////// 11 | 12 | fn main() { 13 | show_output!() 14 | } 15 | -------------------------------------------------------------------------------- /exercises/01_my_first_macro/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | // This function should be called by the `show_output!()` macro 3 | fn show_output() { 4 | println!("I should appear as the output.") 5 | } 6 | ////////// DO NOT CHANGE ABOVE HERE ///////// 7 | 8 | macro_rules! show_output { 9 | () => { 10 | show_output() 11 | }; 12 | } 13 | 14 | ////////// DO NOT CHANGE BELOW HERE ///////// 15 | 16 | fn main() { 17 | show_output!() 18 | } 19 | -------------------------------------------------------------------------------- /exercises/01_my_first_macro/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -5,7 +5,11 @@ 2 | } 3 | ////////// DO NOT CHANGE ABOVE HERE ///////// 4 | 5 | -// TODO: create `show_output!()` macro. 6 | +macro_rules! show_output { 7 | + () => { 8 | + show_output() 9 | + }; 10 | +} 11 | 12 | ////////// DO NOT CHANGE BELOW HERE ///////// 13 | 14 | -------------------------------------------------------------------------------- /exercises/02_numbers/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 2: Numbers 2 | 3 | As a reminder, to complete this exercise: 4 | 5 | * [ ] Read this file to understand the theory being tested, and what 6 | task you will be asked to complete. 7 | * [ ] Try and complete the `main.rs` file. 8 | * [ ] Test to see if your macro creates the same code we have; using 9 | `cargo run -- test 02_numbers`. 10 | * [ ] Run your code, using `cargo run --bin 02_numbers`, to see what it does. 11 | 12 | ## Macros With Arguments 13 | 14 | Macros would be pretty useless if you couldn't modify their behaviour based on 15 | input from the programmer. To this end, let's see how we can vary what our macro 16 | does. 17 | 18 | The simplest way of doing this is to have our macro behave differently if 19 | different tokens are placed in-between the matcher. As a reminder, the matcher 20 | is the bit in each rule before the `=>`. 21 | 22 | Below we see a macro which will replace itself with `true` if the letter `t` is 23 | inside the brackets; and `f` otherwise. 24 | 25 | 26 | ``` rust 27 | macro_rules! torf { 28 | (t) => { 29 | true 30 | }; 31 | (f) => { 32 | false 33 | }; 34 | } 35 | # fn main() { 36 | let _true = torf!(t); 37 | let _false = torf!(f); 38 | # } 39 | ``` 40 | 41 | You'll note the syntax has changed slightly: we've gone from having one of the 42 | `() => {}` blocks (which is called a rule) to having two. Macros try to find 43 | the first rule that matches, and replaces the macro with the contents of the 44 | transcriber block. 45 | 46 | Macros are very similar to a `match` statement because they find the first match 47 | and take action based on that; but it's important to note that you're not matching 48 | on *variables*, you're matching on tokens. 49 | 50 | ## But what is a "token" 51 | 52 | Up until now, we've spoken about "tokens" without explaining what we mean, 53 | further than a handwavy "it's text". 54 | 55 | When Rust code is compiled, one of the first steps of parsing is turning bytes 56 | of text into a "token tree", which is a data-structure representing the 57 | text-fragments of a line of code (so `(3 + (4 + 5))` becomes a token tree containing 58 | `3`, `+` and another token tree containing `4`, `+` and `5`). 59 | 60 | This means that macro matchers aren't restricted to matching exact text, and that 61 | they preserve brackets when matching things. 62 | 63 | As you've seen above, macros let you capture all the tokens inside their 64 | brackets, and then modify the code the write back out based on those tokens. 65 | This ability to react to different pieces of code without them having been fully 66 | compiled lets us create powerful extensions to the Rust language, using your own 67 | syntax. 68 | 69 | Further advanced reading about what tokens are can be found [here.](https://doc.rust-lang.org/reference/tokens.html) 70 | 71 | ## Exercise 2: Numbers 72 | 73 | Your task is to create a macro called `num` which replaces the words `one`, `two` and `three` 74 | with the relevant numbers. 75 | 76 | You may not edit the `main` function, but it should eventually look like the 77 | following: 78 | 79 | 80 | 81 | ```rust,ignore 82 | 83 | ``` 84 | -------------------------------------------------------------------------------- /exercises/02_numbers/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_result(num: i32) { 3 | println!("The result is {num}"); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | // TODO: create `num!()` macro. 8 | 9 | ////////// DO NOT CHANGE BELOW HERE ///////// 10 | 11 | fn main() { 12 | print_result(num!(one) + num!(two) + num!(three)); 13 | } 14 | -------------------------------------------------------------------------------- /exercises/02_numbers/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_result(num: i32) { 3 | println!("The result is {num}"); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | macro_rules! num { 8 | (one) => { 9 | 1 10 | }; 11 | (two) => { 12 | 2 13 | }; 14 | (three) => { 15 | 3 16 | }; 17 | } 18 | 19 | ////////// DO NOT CHANGE BELOW HERE ///////// 20 | 21 | fn main() { 22 | print_result(num!(one) + num!(two) + num!(three)); 23 | } 24 | -------------------------------------------------------------------------------- /exercises/02_numbers/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -1,10 +1,20 @@ 2 | ////////// DO NOT CHANGE BELOW HERE ///////// 3 | fn print_result(num: i32) { 4 | println!("The result is {num}"); 5 | } 6 | ////////// DO NOT CHANGE ABOVE HERE ///////// 7 | 8 | -// TODO: create `num!()` macro. 9 | +macro_rules! num { 10 | + (one) => { 11 | + 1 12 | + }; 13 | + (two) => { 14 | + 2 15 | + }; 16 | + (three) => { 17 | + 3 18 | + }; 19 | +} 20 | 21 | ////////// DO NOT CHANGE BELOW HERE ///////// 22 | 23 | -------------------------------------------------------------------------------- /exercises/03_literal_variables/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 3: Literal Metavariables 2 | 3 | In the last exercise, we saw how we could change the behaviour of 4 | a macro based on text inside the brackets. This is great, but it's 5 | basically an if statement on the text inside the brackets: it's 6 | very simplistic. 7 | 8 | Now we will introduce the concept of a "metavariable". Metavariables capture a 9 | particular part of the text inside the macro's brackets, and let you reuse it. 10 | 11 | The syntax for a metavariable is simple. To explain the syntax, see the example 12 | below: 13 | 14 | ```rust,ignore 15 | macro_rules! do_thing { 16 | (print $metavar:literal) => { 17 | println!("{}", $metavar) 18 | }; 19 | } 20 | ``` 21 | 22 | The `$metavar:literal` is saying that you're capturing any `literal` (which is 23 | something like `'a'`, or `3`, or `"hello"`), and naming it `metavar`. Then, 24 | `$metavar` inside the `println!` is saying to "fill in" that space with whatever 25 | `metavar` is. 26 | 27 | For an invocation like 28 | 29 | ```rust 30 | # macro_rules! do_thing { 31 | # (print $metavar:literal) => { 32 | # println!("{}", $metavar) 33 | # }; 34 | # } 35 | # 36 | # fn main() { 37 | do_thing!(print 3); 38 | # } 39 | ``` 40 | 41 | Rust understands that `metavar` means `3`. So, when doing substitution, 42 | it starts by writing 43 | 44 | ```rust,ignore 45 | println!("{}", $metavar); 46 | ``` 47 | 48 | and then substitutes `3` for `$metavar`: 49 | 50 | ``` rust 51 | # fn main() { 52 | println!("{}", 3); 53 | # } 54 | ``` 55 | 56 | ## But what about types? 57 | 58 | You might be wondering why we haven't said anything about the *type* of the 59 | literal. It turns out that the type doesn't matter during macro expansion. Rather 60 | than needing the type, Rust just needs to know what sort of syntax to expect. If 61 | you tried to provide a variable name, and you needed a literal, Rust will throw 62 | an error. If you needed a *string* literal, and you provided a *char* literal, 63 | then Rust will happily expand the code. It'll throw an error later on in the 64 | compilation process, as if you had written the expanded code. 65 | 66 | ## Why do these examples avoid using macros? 67 | 68 | The example above uses the `println!` macro inside the `do_thing` 69 | macro. Rust is totally fine with this! However, `macrokata` tries 70 | to avoid (as much as possible) using macros we didn't define inside 71 | the main function. The reason for this is that, if we did use `println!` 72 | you would see its expansion as well. That could be confusing, since 73 | 74 | ```rust,ignore 75 | print("some text") 76 | ``` 77 | 78 | is much easier to read than 79 | 80 | ```rust,ignore 81 | { 82 | ::std::io::_print( 83 | ::core::fmt::Arguments::new_v1( 84 | &["some text"], 85 | &[], 86 | ), 87 | ); 88 | }; 89 | 90 | ``` 91 | 92 | ## Exercise 3: Literal Meta-Variables 93 | 94 | Your task is to create a macro which can perform two small bits of math: 95 | 96 | - The syntax `math!(3 plus 5)` should expand to `3 + 5`, where `3` and `5` 97 | could be any literal. 98 | - The syntax `math!(square 2)` should expand to `2 * 2`, where `2` could be any 99 | literal. 100 | 101 | You may not edit the `main` function, but it should eventually look like the 102 | following: 103 | 104 | 105 | 106 | ```rust,ignore 107 | 108 | ``` 109 | -------------------------------------------------------------------------------- /exercises/03_literal_variables/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_result(num: i32) { 3 | println!("The result is {num}"); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | // TODO: create `math!()` macro. 8 | 9 | ////////// DO NOT CHANGE BELOW HERE ///////// 10 | 11 | fn main() { 12 | print_result(math!(3 plus 5)); 13 | print_result(math!(square 2)); 14 | } 15 | -------------------------------------------------------------------------------- /exercises/03_literal_variables/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_result(num: i32) { 3 | println!("The result is {num}"); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | macro_rules! math { 8 | ($first:literal plus $second:literal) => { 9 | $first + $second 10 | }; 11 | (square $first:literal) => { 12 | $first * $first 13 | }; 14 | } 15 | 16 | ////////// DO NOT CHANGE BELOW HERE ///////// 17 | 18 | fn main() { 19 | print_result(math!(3 plus 5)); 20 | print_result(math!(square 2)); 21 | } 22 | -------------------------------------------------------------------------------- /exercises/03_literal_variables/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -1,10 +1,17 @@ 2 | ////////// DO NOT CHANGE BELOW HERE ///////// 3 | fn print_result(num: i32) { 4 | println!("The result is {num}"); 5 | } 6 | ////////// DO NOT CHANGE ABOVE HERE ///////// 7 | 8 | -// TODO: create `math!()` macro. 9 | +macro_rules! math { 10 | + ($first:literal plus $second:literal) => { 11 | + $first + $second 12 | + }; 13 | + (square $first:literal) => { 14 | + $first * $first 15 | + }; 16 | +} 17 | 18 | ////////// DO NOT CHANGE BELOW HERE ///////// 19 | 20 | -------------------------------------------------------------------------------- /exercises/04_expression_variables/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 4: Expression Metavariables 2 | 3 | We can now capture fragments of Rust code that are literals, however there are 4 | other fragments of Rust code which can be captured in metavariables. In general, 5 | every metavariable is of the form `$:`. `` is replaced 6 | with the name of the metavariable, but `FRAGSPEC` is more interesting. It means 7 | "Fragment Specifier", and it tells you what sort of fragment of Rust code you 8 | intend to match. We've already seen `literal`, but another common fragment 9 | specifier is `expr`, which allows you to capture any Rust expression (for 10 | example, `(3 * 5)` or `function_call() + CONSTANT`). 11 | 12 | Using this specifier is nearly identical to using the `literal` fragment 13 | specifier: `$x:expr` indicates a metavariable, which is an expression, named 14 | `x`. 15 | 16 | It's also worth mentioning the fragment specifier `stmt`, which is similar to 17 | `expr`, but allows Rust statements too, like `let` statements. 18 | 19 | # Macros and the Precedence of Operators 20 | 21 | Macros *do* affect the order of operations. The expression `3 * math!(4, 22 | plus, 2)` expands to `3 * (4 + 2)`. This is not clearly outlined anywhere 23 | (that I can find), and a previous version of this guide incorrectly stated 24 | the opposite. 25 | 26 | You can check this behaviour by seeing the following: 27 | 28 | ```rust 29 | macro_rules! math { 30 | () => { 3 + 4 } 31 | } 32 | 33 | fn main() { 34 | let math_result = 2 * math!(); 35 | 36 | // 2 * (3 + 4) == 14 37 | assert_eq!(math_result, 14); 38 | 39 | // (2 * 3) + 4 == 10 40 | assert_ne!(math_result, 10); 41 | } 42 | ``` 43 | 44 | # "Follow-set Ambiguity Rules" 45 | 46 | The Rust parser needs to have some way of knowing where a metavariable ends. 47 | If it didn't, expressions like `$first:expr $second:expr` would be confusing to 48 | parse in some circumstances. For example, how would you parse `a * b * c * d`? 49 | Would `first` be `a`, and `second` be `*b * c * d`? Or would `first` be `a * b * c`, 50 | and `second` be `* d`? 51 | 52 | To avoid this problem entirely, Rust has a set of rules called the "follow-set 53 | ambiguity rules". These tell you which tokens are allowed to follow a 54 | metavariable (and which aren't). 55 | 56 | For `literal`s, this rule is simple: anything can follow a literal 57 | metavariable. 58 | 59 | For `expr` (and its friend `stmt`) the rules are much more restrictive: they 60 | can only be followed by `=>` or `,` or `;`. 61 | 62 | This means that building a matcher like 63 | 64 | ``` rust,ignore 65 | macro_rules! broken_macro { 66 | ($a:expr please) => $a 67 | } 68 | 69 | fn main() { 70 | // Fails to compile! 71 | let value = broken_macro!(3 + 5 please); 72 | } 73 | ``` 74 | 75 | will give you this compiler error: 76 | 77 | ``` rust,ignore 78 | error: `$a:expr` is followed by `please`, which is not allowed for `expr` fragments 79 | --> broken_macro.rs:2:14 80 | | 81 | 2 | ($a:expr please) => { $a } 82 | | ^^^^^^ not allowed after `expr` fragments 83 | | 84 | = note: allowed there are: `=>`, `,` or `;` 85 | ``` 86 | 87 | 88 | As we encounter more expression types, we'll make sure to mention their 89 | follow-set rules, but [this page in the Rust 90 | reference](https://doc.rust-lang.org/reference/macros-by-example.html#follow-set-ambiguity-restrictions) 91 | has a comprehensive list of the rules for each fragment specifier type. 92 | 93 | 94 | ## Exercise 4: Expression Variables 95 | 96 | In this task, you will be completing a similar task to the previous one. 97 | Last time, your macro should have worked with any *literal*, but now we would 98 | like a macro which works with any *expression*. 99 | 100 | - The syntax `math!(3, plus, (5 + 6))` should expand to `3 + (5 + 6)`, where 101 | `3` and `(5 + 6)` could be any expression. 102 | - The syntax `math!(square my_expression)` should expand to `my_expression * 103 | my_expression`, where `my_expression` could be any expression. 104 | 105 | You may not edit the `main` function, but it should eventually look like the 106 | following: 107 | 108 | 109 | 110 | ```rust,ignore 111 | 112 | ``` 113 | -------------------------------------------------------------------------------- /exercises/04_expression_variables/examples/broken_macro.rs: -------------------------------------------------------------------------------- 1 | macro_rules! broken_macro { 2 | ($a:expr please) => { $a } 3 | } 4 | 5 | fn main() { 6 | // Fails to compile! 7 | let value = broken_macro!(3 + 5 please); 8 | } 9 | -------------------------------------------------------------------------------- /exercises/04_expression_variables/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_result(num: i32) { 3 | println!("The result is {num}"); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | // TODO: create `math!()` macro. 8 | 9 | ////////// DO NOT CHANGE BELOW HERE ///////// 10 | 11 | fn main() { 12 | let var = 5; 13 | print_result(math!((2 * 3), plus, var)); 14 | print_result(math!(square var)); 15 | } 16 | -------------------------------------------------------------------------------- /exercises/04_expression_variables/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_result(num: i32) { 3 | println!("The result is {num}"); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | macro_rules! math { 8 | ($a:expr, plus, $b:expr) => { 9 | $a + $b 10 | }; 11 | (square $a:expr) => { 12 | $a * $a 13 | }; 14 | } 15 | 16 | ////////// DO NOT CHANGE BELOW HERE ///////// 17 | 18 | fn main() { 19 | let var = 5; 20 | print_result(math!((2 * 3), plus, var)); 21 | print_result(math!(square var)); 22 | } 23 | -------------------------------------------------------------------------------- /exercises/04_expression_variables/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -1,10 +1,17 @@ 2 | ////////// DO NOT CHANGE BELOW HERE ///////// 3 | fn print_result(num: i32) { 4 | println!("The result is {num}"); 5 | } 6 | ////////// DO NOT CHANGE ABOVE HERE ///////// 7 | 8 | -// TODO: create `math!()` macro. 9 | +macro_rules! math { 10 | + ($a:expr, plus, $b:expr) => { 11 | + $a + $b 12 | + }; 13 | + (square $a:expr) => { 14 | + $a * $a 15 | + }; 16 | +} 17 | 18 | ////////// DO NOT CHANGE BELOW HERE ///////// 19 | 20 | -------------------------------------------------------------------------------- /exercises/05_more_complex_example/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 5: A More Complex Example 2 | 3 | In this task, we'll be implementing code to make the following syntax possible: 4 | 5 | ```rust,ignore 6 | # fn main() { 7 | for_2d!(row in 1..5, col in 2..7, { 8 | // code 9 | }); 10 | #} 11 | ``` 12 | 13 | Ignoring extra curly braces, this code should translate to 14 | 15 | ``` rust 16 | # fn main() { 17 | for row in 1..5 { 18 | let row: i32 = row; 19 | for col in 2..7 { 20 | let col: i32 = col; 21 | // code 22 | } 23 | } 24 | # } 25 | ``` 26 | 27 | Note that the names of the variables may change (i.e. they could be `row` and 28 | `col`, or `x` and `y`, or something else). 29 | 30 | To complete this task, there more fragment specifiers you will need to know 31 | about: 32 | 33 | - `ident`: an "identifier", like a variable name. `ident` metavariables 34 | Can be followed by anything. 35 | - `block`: a "block expression" (curly braces, and their contents). 36 | Can be followed by anything. 37 | - `ty`: a type. Can only be followed by `=>`, `,`, `=`, `|`, `;`, 38 | `:`, `>`, `>>`, `[`, `{`, `as`, `where`, or a `block` metavariable. 39 | 40 | As a reminder, you may not edit the `main` function, but it should eventually 41 | look like the following: 42 | 43 | 44 | 45 | ```rust,ignore 46 | 47 | ``` 48 | -------------------------------------------------------------------------------- /exercises/05_more_complex_example/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | // This function should be called by the `show_output!()` macro 3 | #[derive(Debug)] 4 | struct Coordinate { 5 | x: i32, 6 | y: i32, 7 | } 8 | 9 | impl Coordinate { 10 | fn show(&self) { 11 | println!("({}, {})", self.x, self.y); 12 | } 13 | } 14 | 15 | ////////// DO NOT CHANGE ABOVE HERE ///////// 16 | 17 | // TODO: Create `for_2d!` macro here. 18 | 19 | ////////// DO NOT CHANGE BELOW HERE ///////// 20 | 21 | fn main() { 22 | for_2d!(row in 1..5, col in 2..7, { 23 | (Coordinate {x: col, y: row}).show() 24 | }); 25 | 26 | let values = [1, 3, 5]; 27 | 28 | for_2d!(x in values, y in values, { 29 | (Coordinate {x: x.into(), y: y.into()}).show() 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /exercises/05_more_complex_example/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | // This function should be called by the `show_output!()` macro 3 | #[derive(Debug)] 4 | struct Coordinate { 5 | x: i32, 6 | y: i32, 7 | } 8 | 9 | impl Coordinate { 10 | fn show(&self) { 11 | println!("({}, {})", self.x, self.y); 12 | } 13 | } 14 | 15 | ////////// DO NOT CHANGE ABOVE HERE ///////// 16 | 17 | macro_rules! for_2d { 18 | ($x_name:ident <$x_type:ty> in $x_expr:expr, $y_name:ident <$y_type:ty> in $y_expr:expr, $block:block) => { 19 | for $x_name in $x_expr { 20 | let $x_name: $x_type = $x_name; 21 | for $y_name in $y_expr { 22 | let $y_name: $y_type = $y_name; 23 | $block 24 | } 25 | } 26 | }; 27 | } 28 | 29 | ////////// DO NOT CHANGE BELOW HERE ///////// 30 | 31 | fn main() { 32 | for_2d!(row in 1..5, col in 2..7, { 33 | (Coordinate {x: col, y: row}).show() 34 | }); 35 | 36 | let values = [1, 3, 5]; 37 | 38 | for_2d!(x in values, y in values, { 39 | (Coordinate {x: x.into(), y: y.into()}).show() 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /exercises/05_more_complex_example/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -14,7 +14,17 @@ 2 | 3 | ////////// DO NOT CHANGE ABOVE HERE ///////// 4 | 5 | -// TODO: Create `for_2d!` macro here. 6 | +macro_rules! for_2d { 7 | + ($x_name:ident <$x_type:ty> in $x_expr:expr, $y_name:ident <$y_type:ty> in $y_expr:expr, $block:block) => { 8 | + for $x_name in $x_expr { 9 | + let $x_name: $x_type = $x_name; 10 | + for $y_name in $y_expr { 11 | + let $y_name: $y_type = $y_name; 12 | + $block 13 | + } 14 | + } 15 | + }; 16 | +} 17 | 18 | ////////// DO NOT CHANGE BELOW HERE ///////// 19 | 20 | -------------------------------------------------------------------------------- /exercises/06_repetition/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 6: Repetition 2 | 3 | Hopefully, you're now feeling pretty confident with metavariables. One of 4 | the first justifications we gave for macros was their ability to simulate 5 | "variadic" functions (functions which have a variable number of arguments). In 6 | this exercise, we'll have a look at how you can implement them yourself. 7 | 8 | A simple approach might be to write a rule for each number of arguments. For 9 | example, one might write 10 | 11 | ```rust 12 | macro_rules! listing_literals { 13 | (the $e1:literal) => { 14 | { 15 | let mut my_vec = Vec::new(); 16 | my_vec.push($e1); 17 | my_vec 18 | } 19 | }; 20 | (the $e1:literal and the $e2:literal) => { 21 | { 22 | let mut my_vec = Vec::new(); 23 | my_vec.push($e1); 24 | my_vec.push($e2); 25 | my_vec 26 | } 27 | }; 28 | (the $e1:literal and the $e2:literal and the $e3:literal) => { 29 | { 30 | let mut my_vec = Vec::new(); 31 | my_vec.push($e1); 32 | my_vec.push($e2); 33 | my_vec.push($e3); 34 | my_vec 35 | } 36 | } 37 | } 38 | 39 | fn main() { 40 | let vec: Vec<&str> = listing_literals!(the "lion" and the "witch" and the "wardrobe"); 41 | assert_eq!(vec, vec!["lion", "witch", "wardrobe"]); 42 | let vec: Vec = listing_literals!(the 9 and the 5); 43 | assert_eq!(vec, vec![9, 5]); 44 | } 45 | ``` 46 | 47 | This is very clunky, and involves a large amount of repeated code. Imagine doing 48 | this for 10 arguments! What if we could say that we want *a variable number* of 49 | a particular patterns? That would let us say "give me any number of `$e:expr` 50 | tokens, and I'll tell you what to do with them'". 51 | 52 | Macro repetitions let us do just that. They consist of three things: 53 | - A group of tokens that we want to match repeatedly. 54 | - Optionally, a separator token (which tells the parser what to look for between each match). 55 | - Either `+`, `*` or `?`, which says how many times to expect a match. `+` means "at least once". 56 | `*` means "any number of times, including 0 times". `?` means "either 0 times, or 1 time". 57 | 58 | Let's look at an example of a macro repetition, to parse the exact macro 59 | we showed above. 60 | 61 | The matcher we would use for this is `$(the $my_literal:literal)and+`. 62 | To break that down: 63 | 64 | - `$(` means that we're starting a repetition. 65 | - Inside the brackets, `the $my_literal:literal` is the pattern we're matching. We'll match the exact text "the", and then a literal token. 66 | - The `)` means that we're done describing the pattern to match. 67 | - The `and` is optional, but it is the "separator": a token you can use to separate multiple repetitions. Commonly it's `,` to comma-separate things. 68 | - Here, we use `+`, which means the repetition must happen at least once. `*` would have worked just as well if we were okay with an empty `Vec`. 69 | 70 | What's now left is to use the matched values. To do this, the rule would be something like: 71 | 72 | ```rust,ignore 73 | ($(the $my_literal:literal)and+) => { 74 | { 75 | let mut my_vec = Vec::new(); 76 | $(my_vec.push($my_literal));+; 77 | my_vec 78 | } 79 | } 80 | ``` 81 | 82 | The line `$(my_vec.push($my_literal));+;` is nearly identical to the repetition we saw above, but to break it down: 83 | 84 | - `$(` tells us that we're starting a repetition. 85 | - `my_vec.push($my_literal)` is the code that will be transcribed. `$my_literal` will be replaced with each of the literals specified in the matcher. 86 | - The `)` means that we're done describing the code that will be transcribed. 87 | - The `;` means we're separating these lines with semicolons. Note that if you want, this could also be empty (to indicate they should be joined without anything in the middle). 88 | - The `+` ends the repetition. 89 | - The `;` adds a final semicolon after the expansion of everything. 90 | 91 | So this will expand into the same code we saw above! 92 | 93 | It's worth noting that we've used an extra set of curly braces in our transcriber. This is because if you don't 94 | put the code in a block, the code will look like `let whatever = let mut my_vec = Vec::new();`, which doesn't make sense. 95 | 96 | If you put the code in a curly brace, then the right-hand side of the `=` sign will be a block which returns `my_vec`. 97 | 98 | ## Exercise 6: Repetition 99 | 100 | In this task, you will be creating an `if_any!` macro. If any of the first arguments are true, 101 | it should execute the block which is the last argument. 102 | 103 | You may not edit the `main` function, but once you have completed the exercise, your `if_any!` macro should expand to look like the 104 | following: 105 | 106 | 107 | 108 | ```rust,ignore 109 | 110 | ``` 111 | -------------------------------------------------------------------------------- /exercises/06_repetition/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_success() { 3 | println!("Yay, the if statement worked."); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | // TODO: create `if_any!()` macro. 8 | 9 | ////////// DO NOT CHANGE BELOW HERE ///////// 10 | 11 | fn main() { 12 | if_any!(false, 0 == 1, true; { 13 | print_success(); 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /exercises/06_repetition/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_success() { 3 | println!("Yay, the if statement worked."); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | macro_rules! if_any { 8 | ($($e:expr),+; $block:block) => { 9 | if $($e)||+ $block 10 | } 11 | } 12 | 13 | ////////// DO NOT CHANGE BELOW HERE ///////// 14 | 15 | fn main() { 16 | if_any!(false, 0 == 1, true; { 17 | print_success(); 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /exercises/06_repetition/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -1,10 +1,14 @@ 2 | ////////// DO NOT CHANGE BELOW HERE ///////// 3 | fn print_success() { 4 | println!("Yay, the if statement worked."); 5 | } 6 | ////////// DO NOT CHANGE ABOVE HERE ///////// 7 | 8 | -// TODO: create `if_any!()` macro. 9 | +macro_rules! if_any { 10 | + ($($e:expr),+; $block:block) => { 11 | + if $($e)||+ $block 12 | + } 13 | +} 14 | 15 | ////////// DO NOT CHANGE BELOW HERE ///////// 16 | 17 | -------------------------------------------------------------------------------- /exercises/07_more_repetition/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 7: More Repetition 2 | 3 | This exercise is going to also cover writing repetitions, but now involving more than 4 | one metavariable. Don't worry: the syntax is the exact same as what you've seen before. 5 | 6 | Before you start, let's just quickly cover the different ways you can use a metavariable 7 | within a repetition. 8 | 9 | ## Multiple Metavariables in One Repetition 10 | 11 | You can indicate that two metavariables should be used in a single repetition. 12 | 13 | For example, `( $($i:ident is $e:expr),+ )` would match `my_macro!(pi is 3.14, tau is 6.28)`. 14 | You would end up with `$i` having matched `pi` and `tau`; and `$e` having matched `3.14` and 15 | `6.28`. 16 | 17 | Any repetition in the transcriber can use `$i`, or `$e`, or both within the same repetition. 18 | So a transcriber could be `$(let $i = $e;)+`; or `let product = $($e)*+` 19 | 20 | ## One Metavariable Each, For Two Repetitions 21 | 22 | Alternatively, you could specify two different repetitions, each containing their 23 | own metavariable. For example, this program will construct two vecs. 24 | 25 | ```rust 26 | macro_rules! two_vecs { 27 | ($($vec1:expr),+; $($vec2:expr),+) => { 28 | { 29 | let mut vec1 = Vec::new(); 30 | $(vec1.push($vec1);)+ 31 | let mut vec2 = Vec::new(); 32 | $(vec2.push($vec2);)+ 33 | 34 | (vec1, vec2) 35 | } 36 | } 37 | } 38 | 39 | # fn main() { 40 | let vecs = two_vecs!(1, 2, 3; 'a', 'b'); 41 | # } 42 | ``` 43 | 44 | Importantly, with the above example, you have to be careful about using `$vec1` 45 | and `$vec2` in the same repetition within the transcriber. It is a compiler 46 | error to use two metavariables captured a different number of times in the same 47 | repetition. 48 | 49 | To quote [the reference](https://doc.rust-lang.org/reference/macros-by-example.html#transcribing): 50 | 51 | > Each repetition in the transcriber must contain at least one metavariable to 52 | > decide how many times to expand it. If multiple metavariables appear in the 53 | > same repetition, they must be bound to the same number of fragments. For 54 | > instance, `( $( $i:ident ),* ; $( $j:ident ),* ) => (( $( ($i,$j) ),* ))` must 55 | > bind the same number of `$i` fragments as `$j` fragments. This means that invoking 56 | > the macro with `(a, b, c; d, e, f)` is legal and expands to `((a,d), (b,e), (c,f))`, 57 | > but `(a, b, c; d, e)` is illegal because it does not have the same 58 | > number. 59 | 60 | ## Exercise 7: More Repetition 61 | 62 | In this task, you will be creating a `hashmap` macro. It should consist 63 | of comma-separated pairs, of the form `literal => expr,` 64 | This should construct an empty `HashMap` and `insert` the 65 | relevant key-value pairs. 66 | 67 | You may not edit the `main` function, but it should eventually look like the 68 | following: 69 | 70 | 71 | 72 | ```rust,ignore 73 | 74 | ``` 75 | -------------------------------------------------------------------------------- /exercises/07_more_repetition/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | use std::collections::HashMap; 3 | 4 | fn print_hashmap(hashmap: &HashMap<&str, &str>) { 5 | println!("{hashmap:#?}"); 6 | } 7 | ////////// DO NOT CHANGE ABOVE HERE ///////// 8 | 9 | // TODO: create `hashmap!()` macro. 10 | 11 | ////////// DO NOT CHANGE BELOW HERE ///////// 12 | 13 | fn main() { 14 | let value = "my_string"; 15 | let my_hashmap = hashmap!( 16 | "hash" => "map", 17 | "Key" => value, 18 | ); 19 | 20 | print_hashmap(&my_hashmap); 21 | } 22 | -------------------------------------------------------------------------------- /exercises/07_more_repetition/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | use std::collections::HashMap; 3 | 4 | fn print_hashmap(hashmap: &HashMap<&str, &str>) { 5 | println!("{hashmap:#?}"); 6 | } 7 | ////////// DO NOT CHANGE ABOVE HERE ///////// 8 | 9 | macro_rules! hashmap { 10 | ( $($k:literal => $v:expr,)* ) => { 11 | { 12 | let mut hm = HashMap::new(); 13 | $( hm.insert($k, $v); )* 14 | hm 15 | } 16 | } 17 | } 18 | 19 | ////////// DO NOT CHANGE BELOW HERE ///////// 20 | 21 | fn main() { 22 | let value = "my_string"; 23 | let my_hashmap = hashmap!( 24 | "hash" => "map", 25 | "Key" => value, 26 | ); 27 | 28 | print_hashmap(&my_hashmap); 29 | } 30 | -------------------------------------------------------------------------------- /exercises/07_more_repetition/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -6,7 +6,15 @@ 2 | } 3 | ////////// DO NOT CHANGE ABOVE HERE ///////// 4 | 5 | -// TODO: create `hashmap!()` macro. 6 | +macro_rules! hashmap { 7 | + ( $($k:literal => $v:expr,)* ) => { 8 | + { 9 | + let mut hm = HashMap::new(); 10 | + $( hm.insert($k, $v); )* 11 | + hm 12 | + } 13 | + } 14 | +} 15 | 16 | ////////// DO NOT CHANGE BELOW HERE ///////// 17 | 18 | -------------------------------------------------------------------------------- /exercises/08_nested_repetition/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 8: Nested Repetition 2 | 3 | In this exercise, you will need to use nested repetition. That's where you 4 | write a repetition inside another one, for example, `( $( $( $val:expr ),+ );+ )` 5 | would let you specify at least one value, but separate them with either `;` and `,`. 6 | 7 | The only oddity about nested repetition is that you must ensure that you use 8 | metavariables in a context where it's clear you're only referring to one of them. 9 | In other words, the `$val` metavariable in the last paragraph *must* be used within 10 | a nested repetition. 11 | 12 | ## Exercise 8: Nested Repetition 13 | 14 | In this task, you will be building a macro to load a data structure with 15 | an adjacency list from a graph. As a refresher, graphs are data structures 16 | that describe how different nodes are connected. 17 | 18 | Each will be a literal, and you will be specifying, for each node, 19 | which nodes it connects to. For example, 20 | 21 | ```rust,ignore 22 | graph!{ 23 | 1 -> (2, 3, 4, 5); 24 | 2 -> (1, 3); 25 | 3 -> (2); 26 | 4 -> (); 27 | 5 -> (1, 2, 3); 28 | } 29 | ``` 30 | 31 | should get translated into a `Vec` containing the pairs `(1, 2)`, `(1, 3)`, ... `(2, 1)`, ... `(5, 3)`. 32 | 33 | You may not edit the `main` function, but it should eventually look like the 34 | following: 35 | 36 | 37 | 38 | ```rust,ignore 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /exercises/08_nested_repetition/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_vec(vec: &Vec) { 3 | println!("{vec:#?}"); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | // TODO: create `graph!()` macro. 8 | 9 | ////////// DO NOT CHANGE BELOW HERE ///////// 10 | 11 | #[allow(clippy::vec_init_then_push)] 12 | fn main() { 13 | let my_graph = graph!( 14 | 1 -> (2, 3, 4, 5); 15 | 2 -> (1, 3); 16 | 3 -> (2); 17 | 4 -> (); 18 | 5 -> (1, 2, 3); 19 | ); 20 | 21 | print_vec(&my_graph); 22 | } 23 | -------------------------------------------------------------------------------- /exercises/08_nested_repetition/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | fn print_vec(vec: &Vec) { 3 | println!("{vec:#?}"); 4 | } 5 | ////////// DO NOT CHANGE ABOVE HERE ///////// 6 | 7 | macro_rules! graph { 8 | ( $($from:literal -> ( $( $to:literal),* );)* ) => { 9 | { 10 | let mut vec = Vec::new(); 11 | $( $(vec.push(($from, $to));)* )* 12 | 13 | vec 14 | } 15 | } 16 | } 17 | 18 | ////////// DO NOT CHANGE BELOW HERE ///////// 19 | 20 | #[allow(clippy::vec_init_then_push)] 21 | fn main() { 22 | let my_graph = graph!( 23 | 1 -> (2, 3, 4, 5); 24 | 2 -> (1, 3); 25 | 3 -> (2); 26 | 4 -> (); 27 | 5 -> (1, 2, 3); 28 | ); 29 | 30 | print_vec(&my_graph); 31 | } 32 | -------------------------------------------------------------------------------- /exercises/08_nested_repetition/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -1,10 +1,19 @@ 2 | ////////// DO NOT CHANGE BELOW HERE ///////// 3 | fn print_vec(vec: &Vec) { 4 | println!("{vec:#?}"); 5 | } 6 | ////////// DO NOT CHANGE ABOVE HERE ///////// 7 | 8 | -// TODO: create `graph!()` macro. 9 | +macro_rules! graph { 10 | + ( $($from:literal -> ( $( $to:literal),* );)* ) => { 11 | + { 12 | + let mut vec = Vec::new(); 13 | + $( $(vec.push(($from, $to));)* )* 14 | + 15 | + vec 16 | + } 17 | + } 18 | +} 19 | 20 | ////////// DO NOT CHANGE BELOW HERE ///////// 21 | 22 | -------------------------------------------------------------------------------- /exercises/09_ambiguity_and_ordering/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 9: Ambiguity and Ordering 2 | 3 | Up until this point, we've mostly been dealing with macros with a single rule. 4 | We saw earlier that macros can require more than one rule, but so far we've never 5 | had ambiguity in which rule should be followed. 6 | 7 | There are, however, multiple circumstances where rules could have ambiguity, 8 | so it's important to understand how macros deal with that ambiguity. 9 | 10 | The following is adapted from the [rust documentation on 11 | macros](https://doc.rust-lang.org/reference/macros-by-example.html#transcribing): 12 | 13 | - When a macro is invoked (i.e. someone writes `my_macro!()`), the compiler 14 | looks for a macro with that name, and tries each rule in turn. 15 | - To try a rule, it reads through each token in the parser in turn. There are 16 | three possibilities: 17 | 18 | 1. The token found matches the matcher. In this case, it keeps parsing the 19 | next token. If there are no tokens left, and the matcher is complete, then 20 | the rule matches. 21 | 2. The token found does not match the matcher. In this case, Rust tries the 22 | next rule. If there are no rules left, an error is raised as the macro 23 | cannot be expanded. 24 | 3. The rule is ambiguous. In other words, it's not clear from *just this 25 | token* what to do. If this happens, this is an error. 26 | 27 | - If it finds a rule that matches the tokens inside the brackets; it starts 28 | transcribing. *Once a match is found, no more rules are examined*. 29 | 30 | Let's have a look at some examples: 31 | 32 | ```rust,ignore 33 | macro_rules! ambiguity { 34 | ($($i:ident)* $j:ident) => { }; 35 | } 36 | 37 | # fn main() { 38 | ambiguity!(error); 39 | # } 40 | ``` 41 | 42 | This example fails because Rust is not able to determine what `$j` should be just by looking at 43 | the current token. If Rust could look forward, it would see that `$j` must be followed by a `)`, 44 | but it cannot, so it causes an error. 45 | 46 | ```rust 47 | macro_rules! ordering { 48 | ($j:expr) => { "This was an expression" }; 49 | ($j:literal) => { "This was a literal" }; 50 | } 51 | 52 | # fn main() { 53 | let expr1 = ordering!('a'); // => "This was an expression". 54 | let expr1 = ordering!(3 + 5); // => "This was an expression". 55 | # } 56 | ``` 57 | 58 | This example demonstrates an example where Rust macros can behave strangely due to 59 | ordering rules: even though `literal` is a much stricter condition than `expr`, 60 | because `literal`s are `expr`s, the first rule will always match. 61 | 62 | ## Exercise 9: Ambiguity and Ordering 63 | 64 | This task is a little bit different to previous tasks: we have given you 65 | a partially functional macro already, along with some invocations of that macro. 66 | 67 | You should adjust the macro's rules and syntax to make sure that you 68 | achieve the correct behaviour without any ambiguity. 69 | 70 | - `sum!()` should sum together two or more expressions together. 71 | - `get_number_type!()` should determine what sort of Rust syntax is being used: 72 | a positive literal, a negative literal, a block, or an expression. 73 | 74 | You may not edit the `main` function, but it should eventually look like the 75 | following: 76 | 77 | 78 | 79 | ```rust,ignore 80 | 81 | ``` 82 | -------------------------------------------------------------------------------- /exercises/09_ambiguity_and_ordering/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | 3 | /// This enum should represent what code the user wrote exactly. 4 | /// Even though to a compiled program there's no difference, 5 | /// this will let the program tell what sort of code the user wrote. 6 | #[derive(Debug)] 7 | enum NumberType { 8 | /// The user wrote a literal, positive number. 9 | PositiveNumber(u32), 10 | /// The user wrote a literal, negative number. 11 | NegativeNumber(i32), 12 | /// We can't tell if the user's code is positive or negative because it's a block. 13 | UnknownBecauseBlock(i32), 14 | /// We can't tell if the user's code is positive or negative because it's an expression. 15 | UnknownBecauseExpr(i32), 16 | } 17 | 18 | impl NumberType { 19 | fn show(&self) { 20 | println!("{self:#?}"); 21 | } 22 | } 23 | ////////// DO NOT CHANGE ABOVE HERE ///////// 24 | 25 | // Sum together at least two expressions. 26 | macro_rules! sum { 27 | ($($expr:expr),+ , $lastexpr:expr) => { 28 | $($expr + )+ $lastexpr 29 | } 30 | } 31 | 32 | macro_rules! get_number_type { 33 | ( $e:expr ) => { 34 | NumberType::UnknownBecauseExpr($e) 35 | }; 36 | ( $block:block ) => { 37 | NumberType::UnknownBecauseBlock($block) 38 | }; 39 | ( $positive:literal ) => { 40 | NumberType::PositiveNumber($positive) 41 | }; 42 | ( -$negative:literal ) => { 43 | NumberType::NegativeNumber(-$negative) 44 | }; 45 | } 46 | 47 | ////////// DO NOT CHANGE BELOW HERE ///////// 48 | fn main() { 49 | // PositiveNumber 50 | get_number_type!(5).show(); 51 | 52 | // NegativeNumber 53 | get_number_type!(-5).show(); 54 | 55 | // UnknownBecauseBlock 56 | #[allow(clippy::let_and_return)] 57 | get_number_type!({ 58 | let x = 6; 59 | x 60 | }) 61 | .show(); 62 | 63 | // UnknownBecauseExpr 64 | get_number_type!(sum!(1, 2, 3, 4)).show(); 65 | get_number_type!(3 + 5 - 1).show(); 66 | } 67 | -------------------------------------------------------------------------------- /exercises/09_ambiguity_and_ordering/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | 3 | /// This enum should represent what code the user wrote exactly. 4 | /// Even though to a compiled program there's no difference, 5 | /// this will let the program tell what sort of code the user wrote. 6 | #[derive(Debug)] 7 | enum NumberType { 8 | /// The user wrote a literal, positive number. 9 | PositiveNumber(u32), 10 | /// The user wrote a literal, negative number. 11 | NegativeNumber(i32), 12 | /// We can't tell if the user's code is positive or negative because it's a block. 13 | UnknownBecauseBlock(i32), 14 | /// We can't tell if the user's code is positive or negative because it's an expression. 15 | UnknownBecauseExpr(i32), 16 | } 17 | 18 | impl NumberType { 19 | fn show(&self) { 20 | println!("{self:#?}"); 21 | } 22 | } 23 | ////////// DO NOT CHANGE ABOVE HERE ///////// 24 | 25 | // Sum together at least two expressions. 26 | macro_rules! sum { 27 | ($firstexpr:expr , $($expr:expr),+) => { 28 | $firstexpr $( + $expr )+ 29 | } 30 | } 31 | 32 | macro_rules! get_number_type { 33 | ( -$negative:literal ) => { 34 | NumberType::NegativeNumber(-$negative) 35 | }; 36 | ( $positive:literal ) => { 37 | NumberType::PositiveNumber($positive) 38 | }; 39 | ( $block:block ) => { 40 | NumberType::UnknownBecauseBlock($block) 41 | }; 42 | ( $expr:expr ) => { 43 | NumberType::UnknownBecauseExpr($expr) 44 | }; 45 | } 46 | 47 | ////////// DO NOT CHANGE BELOW HERE ///////// 48 | fn main() { 49 | // PositiveNumber 50 | get_number_type!(5).show(); 51 | 52 | // NegativeNumber 53 | get_number_type!(-5).show(); 54 | 55 | // UnknownBecauseBlock 56 | #[allow(clippy::let_and_return)] 57 | get_number_type!({ 58 | let x = 6; 59 | x 60 | }) 61 | .show(); 62 | 63 | // UnknownBecauseExpr 64 | get_number_type!(sum!(1, 2, 3, 4)).show(); 65 | get_number_type!(3 + 5 - 1).show(); 66 | } 67 | -------------------------------------------------------------------------------- /exercises/09_ambiguity_and_ordering/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -24,23 +24,23 @@ 2 | 3 | // Sum together at least two expressions. 4 | macro_rules! sum { 5 | - ($($expr:expr),+ , $lastexpr:expr) => { 6 | - $($expr + )+ $lastexpr 7 | + ($firstexpr:expr , $($expr:expr),+) => { 8 | + $firstexpr $( + $expr )+ 9 | } 10 | } 11 | 12 | macro_rules! get_number_type { 13 | - ( $e:expr ) => { 14 | - NumberType::UnknownBecauseExpr($e) 15 | - }; 16 | - ( $block:block ) => { 17 | - NumberType::UnknownBecauseBlock($block) 18 | + ( -$negative:literal ) => { 19 | + NumberType::NegativeNumber(-$negative) 20 | }; 21 | ( $positive:literal ) => { 22 | NumberType::PositiveNumber($positive) 23 | }; 24 | - ( -$negative:literal ) => { 25 | - NumberType::NegativeNumber(-$negative) 26 | + ( $block:block ) => { 27 | + NumberType::UnknownBecauseBlock($block) 28 | + }; 29 | + ( $expr:expr ) => { 30 | + NumberType::UnknownBecauseExpr($expr) 31 | }; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /exercises/10_macros_calling_macros/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 10: Macros Calling Macros 2 | 3 | We briefly mentioned in a previous exercise that macros are able to call 4 | other macros. In this exercise we will look at a brief example of that. 5 | Before we do, there are three small notes we should mention. 6 | 7 | ## Useful built-in macros 8 | 9 | There are two useful macros which the standard library provides - `stringify!()` 10 | and `concat!()`. Both of them produce static string slices, made up of tokens. 11 | 12 | The `stringify!` macro takes tokens and turns them into a `&str` that 13 | textually represents what those tokens are. For example, `stringify!(1 + 1)` 14 | will become `"1 + 1"`. 15 | 16 | The `concat!` macro takes a comma-separated list of literals, and creates a 17 | `&str` which concatenates them. For example, `concat!("test", true, 99)` becomes 18 | `"testtrue99"`. 19 | 20 | It's useful to know that if either of these have a macro in their parameter, 21 | (i.e. `stringify!(test!())`), the internal macro will be expanded first. 22 | So, if `test!()` expanded to `1 + 1`, your string would be `"1 + 1"`, not 23 | `"test!()"`. 24 | 25 | ## The `tt` fragment specifier 26 | 27 | An important macro specifier which we have not, as of yet, discussed, 28 | is the `tt` macro. This captures a "Token Tree", which is any token, 29 | or a group of tokens inside brackets. This is the most flexible 30 | fragment specifier, because it imposes no meaning on what the captured 31 | tokens might be. For example: 32 | 33 | ``` rust 34 | macro_rules! stringify_number { 35 | (one) => {"1"}; 36 | (two) => {"2"}; 37 | ($tokens:tt) => { stringify!($tokens)}; 38 | } 39 | 40 | # fn main() { 41 | stringify_number!(one); // is "1" 42 | stringify_number!(while); // is "while" 43 | stringify_number!(bing_bang_boom); // is "bing_bang_boom" 44 | # } 45 | ``` 46 | 47 | It's really important to keep in mind with `tt` macros that you **must** 48 | ensure that anything after them can be unambiguously parsed. 49 | 50 | In other words, the metavariable `$($thing:tt)*` (ending with `*`, `+` OR `?`) *must* 51 | be the last fragment in the parser. Since anything can be a token tree, Rust could 52 | not know what to accept after that parser. 53 | 54 | To avoid this issue, you can either match a single `tt`, and make the user wrap multiple tokens 55 | inside brackets, or you can specify a delimiter for your match (i.e. `$($thing:tt),+`, since 56 | two token trees not separated by a `,` could not match). 57 | 58 | ## Restrictions on "Forwarding Macros" 59 | 60 | There is one important restriction when calling a macro using another macro. 61 | 62 | When forwarding a matched fragment to another macro-by-example, matchers in the 63 | second macro will be passed an 64 | [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) of the fragment type, 65 | which cannot be matched on except as a fragment of that type. The second macro 66 | can't use literal tokens to match the fragments in the matcher, only a fragment 67 | specifier of the same type. The `ident`, `lifetime`, and `tt` fragment types are 68 | an exception, and *can* be matched by literal tokens. The following illustrates 69 | this restriction: 70 | 71 | ```rust,ignore 72 | macro_rules! foo { 73 | ($l:expr) => { bar!($l); } 74 | // ERROR: ^^ no rules expected this token in macro call 75 | } 76 | 77 | macro_rules! bar { 78 | (3) => {} 79 | } 80 | 81 | # fn main() { 82 | foo!(3); 83 | # } 84 | ``` 85 | 86 | The following illustrates how tokens can be directly matched after matching a `tt` fragment: 87 | 88 | 89 | ```rust 90 | // compiles OK 91 | macro_rules! foo { 92 | ($l:tt) => { bar!($l); } 93 | } 94 | 95 | macro_rules! bar { 96 | (3) => {} 97 | } 98 | 99 | # fn main() { 100 | foo!(3); 101 | # } 102 | ``` 103 | 104 | ## Exercise 10: Macros Calling Macros 105 | 106 | In this exercise, you have already been provided with a macro called `digit`, which 107 | maps the identifiers `zero` through `nine` to a `&str` with their numeric value. 108 | 109 | Your task is to write a macro called `number!()` which takes at least one of the identifiers `zero` 110 | through `nine`, and converts them to a string containing numbers. 111 | 112 | For example, `number!(one two three)` should expand to `"123"`. 113 | 114 | **Note:** previously exercise 10 was about making a hashmap. The exercise has changed, but the old 115 | code is still available in the `archive/` directory. It will be removed on the next update of this book. 116 | -------------------------------------------------------------------------------- /exercises/10_macros_calling_macros/archive/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | use std::collections::HashMap; 3 | use std::fmt::Debug; 4 | 5 | fn print_pair(pair: (K, V)) { 6 | println!("{pair:#?}"); 7 | } 8 | fn print_hashmap(hashmap: &HashMap) { 9 | println!("{hashmap:#?}"); 10 | } 11 | ////////// DO NOT CHANGE ABOVE HERE ///////// 12 | 13 | // TODO: Create a `pair!()` macro. 14 | 15 | // TODO: Create a `hashmap!()` macro that uses the `pair!()` macro. 16 | 17 | ////////// DO NOT CHANGE BELOW HERE ///////// 18 | 19 | fn main() { 20 | let pair: (char, u8) = pair!('a' => 1); 21 | 22 | print_pair(pair); 23 | 24 | let value = "value"; 25 | 26 | let my_hashmap = hashmap!( 27 | String::from("Hash") => "map", 28 | String::from("Key") => value, 29 | ); 30 | 31 | print_hashmap(&my_hashmap); 32 | } 33 | -------------------------------------------------------------------------------- /exercises/10_macros_calling_macros/archive/solutions/main.rs: -------------------------------------------------------------------------------- 1 | ////////// DO NOT CHANGE BELOW HERE ///////// 2 | use std::collections::HashMap; 3 | use std::fmt::Debug; 4 | 5 | fn print_pair(pair: (K, V)) { 6 | println!("{pair:#?}"); 7 | } 8 | fn print_hashmap(hashmap: &HashMap) { 9 | println!("{hashmap:#?}"); 10 | } 11 | ////////// DO NOT CHANGE ABOVE HERE ///////// 12 | 13 | macro_rules! pair { 14 | ($i:expr => $e: expr) => { 15 | ($i, $e) 16 | }; 17 | } 18 | 19 | macro_rules! hashmap { 20 | ( $($k:expr => $v:expr,)* ) => { 21 | { 22 | HashMap::from([ 23 | $(pair!($k => $v)),* 24 | ]) 25 | } 26 | } 27 | } 28 | 29 | ////////// DO NOT CHANGE BELOW HERE ///////// 30 | 31 | fn main() { 32 | let pair: (char, u8) = pair!('a' => 1); 33 | 34 | print_pair(pair); 35 | 36 | let value = "value"; 37 | 38 | let my_hashmap = hashmap!( 39 | String::from("Hash") => "map", 40 | String::from("Key") => value, 41 | ); 42 | 43 | print_hashmap(&my_hashmap); 44 | } 45 | -------------------------------------------------------------------------------- /exercises/10_macros_calling_macros/archive/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -10,9 +10,21 @@ 2 | } 3 | ////////// DO NOT CHANGE ABOVE HERE ///////// 4 | 5 | -// TODO: Create a `pair!()` macro. 6 | +macro_rules! pair { 7 | + ($i:expr => $e: expr) => { 8 | + ($i, $e) 9 | + }; 10 | +} 11 | 12 | -// TODO: Create a `hashmap!()` macro that uses the `pair!()` macro. 13 | +macro_rules! hashmap { 14 | + ( $($k:expr => $v:expr,)* ) => { 15 | + { 16 | + HashMap::from([ 17 | + $(pair!($k => $v)),* 18 | + ]) 19 | + } 20 | + } 21 | +} 22 | 23 | ////////// DO NOT CHANGE BELOW HERE ///////// 24 | 25 | -------------------------------------------------------------------------------- /exercises/10_macros_calling_macros/main.rs: -------------------------------------------------------------------------------- 1 | macro_rules! digit { 2 | (zero) => { 3 | "0" 4 | }; 5 | (one) => { 6 | "1" 7 | }; 8 | (two) => { 9 | "2" 10 | }; 11 | (three) => { 12 | "3" 13 | }; 14 | (four) => { 15 | "4" 16 | }; 17 | (five) => { 18 | "5" 19 | }; 20 | (six) => { 21 | "6" 22 | }; 23 | (seven) => { 24 | "7" 25 | }; 26 | (eight) => { 27 | "8" 28 | }; 29 | (nine) => { 30 | "9" 31 | }; 32 | } 33 | 34 | ////////// DO NOT CHANGE ABOVE HERE ///////// 35 | 36 | // TODO: create `number!()` macro. 37 | 38 | ////////// DO NOT CHANGE BELOW HERE ///////// 39 | 40 | fn main() { 41 | let my_number = number!(nine three seven two zero).parse::().unwrap(); 42 | let my_other_number = number!(one two four six eight zero).parse::().unwrap(); 43 | println!("{}", my_number + my_other_number); // = 218400 44 | } 45 | -------------------------------------------------------------------------------- /exercises/10_macros_calling_macros/solutions/main.rs: -------------------------------------------------------------------------------- 1 | macro_rules! digit { 2 | (zero) => { 3 | "0" 4 | }; 5 | (one) => { 6 | "1" 7 | }; 8 | (two) => { 9 | "2" 10 | }; 11 | (three) => { 12 | "3" 13 | }; 14 | (four) => { 15 | "4" 16 | }; 17 | (five) => { 18 | "5" 19 | }; 20 | (six) => { 21 | "6" 22 | }; 23 | (seven) => { 24 | "7" 25 | }; 26 | (eight) => { 27 | "8" 28 | }; 29 | (nine) => { 30 | "9" 31 | }; 32 | } 33 | 34 | ////////// DO NOT CHANGE ABOVE HERE ///////// 35 | 36 | macro_rules! number { 37 | ($($num:ident )+) => (concat!($(digit!($num)),+)) 38 | } 39 | 40 | ////////// DO NOT CHANGE BELOW HERE ///////// 41 | 42 | fn main() { 43 | let my_number = number!(nine three seven two zero).parse::().unwrap(); 44 | let my_other_number = number!(one two four six eight zero).parse::().unwrap(); 45 | println!("{}", my_number + my_other_number); // = 218400 46 | } 47 | -------------------------------------------------------------------------------- /exercises/10_macros_calling_macros/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -33,7 +33,9 @@ 2 | 3 | ////////// DO NOT CHANGE ABOVE HERE ///////// 4 | 5 | -// TODO: create `number!()` macro. 6 | +macro_rules! number { 7 | + ($($num:ident )+) => (concat!($(digit!($num)),+)) 8 | +} 9 | 10 | ////////// DO NOT CHANGE BELOW HERE ///////// 11 | 12 | -------------------------------------------------------------------------------- /exercises/11_macro_recursion/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 11: Macro Recursion 2 | 3 | This exercise is a sort of culmination of everything you've learned so far about macros. 4 | 5 | To complete it, you'll need to note one important fact: macros can recurse into themselves. 6 | 7 | This allows very powerful expansions. As a simple example: 8 | 9 | ``` rust 10 | 11 | enum LinkedList { 12 | Node(i32, Box), 13 | Empty 14 | } 15 | 16 | macro_rules! linked_list { 17 | () => { 18 | LinkedList::Empty 19 | }; 20 | ($expr:expr $(, $exprs:expr)*) => { 21 | LinkedList::Node($expr, Box::new(linked_list!($($exprs),*))) 22 | } 23 | } 24 | 25 | fn main() { 26 | let my_list = linked_list!(3, 4, 5); 27 | } 28 | ``` 29 | 30 | The above example is very typical. The first rule is the "base case": an empty 31 | list of tokens implies an empty linked list. 32 | 33 | The second rule always matches one expression first (`expr`). This allows us 34 | to refer to it on its own, in this case to create the `Node`. The rest of 35 | the expressions (`exprs`) are stored in a repetition, and all we'll do with 36 | them is recurse into `linked_list!()`. If there's no expressions left, 37 | that call to `linked_list!()` will give back `Empty`, otherwise it'll 38 | repeat the same process. 39 | 40 | While macro recursion is incredibly powerful, it is also slow. As a result, 41 | there is a limit to the amount of recursion you are allowed to do. 42 | In rustc, the limit is `128`, but you can configure it with 43 | `#![recursion_limit = "256"]` as a crate-level attribute. 44 | 45 | 46 | ## Exercise 11: Currying 47 | 48 | Before you complete the exercise, let's briefly discuss a concept called "currying". 49 | If you're already familiar with the concept, perhaps from your own experience of 50 | functional programming, you can skip the next two paragraphs. 51 | 52 | In most imperative languages, the syntax to call a function with multiple arguments 53 | is `function(arg1, arg2, arg3)`. If you do not provide all the arguments, that is 54 | an error. In many functional languages, however, the syntax for function calls is 55 | more akin to `function(arg1)(arg2)(arg3)`. The advantage of this notation is that 56 | if you specify less than the required number of arguments, it's not an error: 57 | you get back a function that takes the rest of the arguments. A function that behaves 58 | this way is said to be "curried" (named after Haskell Curry, a famous mathematician). 59 | 60 | A good example of this is a curried `add` function. In regular Rust, we'd say `add` is 61 | `move |a, b| a + b`. If we curried that function, we'd instead have `move |a| move |b| a + b`. 62 | What this means is that we can write `let add_1 = add(1);`, and we now have a function 63 | which will add 1 to anything. 64 | 65 | In this exercise, you will build a macro which helps you understand currying, 66 | and build a curried function in Rust. The syntax for this macro will be 67 | `curry!((a: i32) => (b: i32) => _, {a + b})`. Each pair of `ident: ty` is an 68 | argument, and the last `_` indicates that the compiler will infer the return 69 | type. The block provided last is, of course, the computation we want to do after 70 | receiving all the arguments. 71 | 72 | Each step of the currying process, you should call the macro `print_curried_argument`. 73 | This takes in a value (which, for the purposes of the exercise, you can assume will 74 | always be `Copy`). It will print out the value that you have been provided as an argument. 75 | 76 | -------------------------------------------------------------------------------- /exercises/11_macro_recursion/main.rs: -------------------------------------------------------------------------------- 1 | // TODO: Create the `curry!()` macro. 2 | 3 | ////////// DO NOT CHANGE BELOW HERE ///////// 4 | 5 | fn print_numbers(nums: &Vec) { 6 | println!("Resulting Numbers: {nums:#?}"); 7 | } 8 | 9 | fn get_example_vec() -> Vec { 10 | vec![1, 3, 5, 6, 7, 9] 11 | } 12 | 13 | fn print_curried_argument(val: impl std::fmt::Debug) { 14 | println!("Currying value {:?}.", val); 15 | } 16 | 17 | fn main() { 18 | println!("=== defining functions ==="); 19 | let is_between = curry!((min: i32) => (max: i32) => (item: &i32) => _, { 20 | min < *item && *item < max 21 | }); 22 | 23 | let curry_filter_between = curry!((min: i32) => (max:i32) => (vec: &Vec) => _, { 24 | let filter_between = is_between(min)(max); 25 | vec.iter().filter_map(|i| if filter_between(i) { Some(*i) } else { None }).collect() 26 | }); 27 | 28 | println!("=== create between_3_7 ==="); 29 | let between_3_7 = curry_filter_between(3)(7); 30 | println!("=== create between_5_10 ==="); 31 | let between_5_10 = curry_filter_between(5)(10); 32 | 33 | let my_vec = get_example_vec(); 34 | 35 | println!("=== call between_3_7 ==="); 36 | let some_numbers: Vec = between_3_7(&my_vec); 37 | print_numbers(&some_numbers); 38 | 39 | println!("=== call between_5_10 ==="); 40 | let more_numbers: Vec = between_5_10(&my_vec); 41 | print_numbers(&more_numbers); 42 | } 43 | -------------------------------------------------------------------------------- /exercises/11_macro_recursion/solutions/advanced.rs: -------------------------------------------------------------------------------- 1 | macro_rules! curry_unwrapper { 2 | ($block:block) => { 3 | $block 4 | }; 5 | ( 6 | $argname:ident: $argtype:ty, 7 | $($argnames:ident: $argtypes:ty,)* 8 | $block:block 9 | ) => { 10 | Box::new(move |$argname : $argtype | { 11 | curry_unwrapper!($($argnames: $argtypes,)* $block) 12 | }) 13 | } 14 | } 15 | 16 | macro_rules! box_type { 17 | (=> $type:ty) => { 18 | $type 19 | }; 20 | ($type:ty $(,$argtypes:ty )* => $restype:ty) => { 21 | Box box_type!($($argtypes ),* => $restype)> 22 | } 23 | } 24 | 25 | macro_rules! curry_fn { 26 | ( 27 | $ident:ident, 28 | ($argname:ident: $argtype:ty) 29 | -> $(($argnames:ident: $argtypes:ty))->* 30 | => $restype:ty, $block:block 31 | ) => { 32 | fn $ident($argname: $argtype) -> box_type!($($argtypes ),* => $restype) { 33 | curry_unwrapper!($($argnames: $argtypes,)* $block) 34 | } 35 | } 36 | } 37 | 38 | fn main() { 39 | curry_fn!(add, (a: i32) -> (b: i32) -> (c: i32) -> (d: i32) => i32, { 40 | a + b + c + d 41 | }); 42 | 43 | let res = add(3)(2)(3)(4); 44 | 45 | 46 | println!("{res}"); 47 | } 48 | -------------------------------------------------------------------------------- /exercises/11_macro_recursion/solutions/main.rs: -------------------------------------------------------------------------------- 1 | macro_rules! curry { 2 | (_, $block:block) => {$block}; 3 | (($argident:ident : $argtype:ty) => $(($argidents:ident: $argtypes:ty) =>)* _, $block:block) => { 4 | move |$argident: $argtype| { 5 | print_curried_argument($argident); 6 | curry!($(($argidents: $argtypes) =>)* _, $block) 7 | } 8 | }; 9 | } 10 | 11 | ////////// DO NOT CHANGE BELOW HERE ///////// 12 | 13 | fn print_numbers(nums: &Vec) { 14 | println!("Resulting Numbers: {nums:#?}"); 15 | } 16 | 17 | fn get_example_vec() -> Vec { 18 | vec![1, 3, 5, 6, 7, 9] 19 | } 20 | 21 | fn print_curried_argument(val: impl std::fmt::Debug) { 22 | println!("Currying value {:?}.", val); 23 | } 24 | 25 | fn main() { 26 | println!("=== defining functions ==="); 27 | let is_between = curry!((min: i32) => (max: i32) => (item: &i32) => _, { 28 | min < *item && *item < max 29 | }); 30 | 31 | let curry_filter_between = curry!((min: i32) => (max:i32) => (vec: &Vec) => _, { 32 | let filter_between = is_between(min)(max); 33 | vec.iter().filter_map(|i| if filter_between(i) { Some(*i) } else { None }).collect() 34 | }); 35 | 36 | println!("=== create between_3_7 ==="); 37 | let between_3_7 = curry_filter_between(3)(7); 38 | println!("=== create between_5_10 ==="); 39 | let between_5_10 = curry_filter_between(5)(10); 40 | 41 | let my_vec = get_example_vec(); 42 | 43 | println!("=== call between_3_7 ==="); 44 | let some_numbers: Vec = between_3_7(&my_vec); 45 | print_numbers(&some_numbers); 46 | 47 | println!("=== call between_5_10 ==="); 48 | let more_numbers: Vec = between_5_10(&my_vec); 49 | print_numbers(&more_numbers); 50 | } 51 | -------------------------------------------------------------------------------- /exercises/11_macro_recursion/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -1,4 +1,12 @@ 2 | -// TODO: Create the `curry!()` macro. 3 | +macro_rules! curry { 4 | + (_, $block:block) => {$block}; 5 | + (($argident:ident : $argtype:ty) => $(($argidents:ident: $argtypes:ty) =>)* _, $block:block) => { 6 | + move |$argident: $argtype| { 7 | + print_curried_argument($argident); 8 | + curry!($(($argidents: $argtypes) =>)* _, $block) 9 | + } 10 | + }; 11 | +} 12 | 13 | ////////// DO NOT CHANGE BELOW HERE ///////// 14 | 15 | -------------------------------------------------------------------------------- /exercises/12_hygienic_macros/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 12: Hygiene 2 | 3 | To quote [the reference](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene): 4 | 5 | > By default, all identifiers referred to in a macro are expanded as-is, and are 6 | > looked up at the macro's invocation site. This can lead to issues if a macro 7 | > refers to an item (i.e. function/struct/enum/etc.) or macro which isn't in scope at the invocation site. To 8 | > alleviate this, the `$crate` metavariable can be used at the start of a path to 9 | > force lookup to occur inside the crate defining the macro. 10 | 11 | Here is an example to illustrate (again, taken from the reference linked above): 12 | 13 | ```rust,ignore 14 | // Definitions in the `helper_macro` crate. 15 | #[macro_export] 16 | macro_rules! helped { 17 | /* 18 | () => { helper!() } 19 | // ^^^^^^ This might lead to an error due to 'helper' not being in scope. 20 | */ 21 | () => { $crate::helper!() } 22 | } 23 | 24 | #[macro_export] 25 | macro_rules! helper { 26 | () => { () } 27 | } 28 | 29 | // Usage in another crate. 30 | // Note that `helper_macro::helper` is not imported! 31 | 32 | use helper_macro::helped; 33 | 34 | fn unit() { 35 | helped!(); 36 | } 37 | ``` 38 | 39 | In other words, this means that a macro needs to have all the functions/structs/macros it uses in scope 40 | at its *call site*, not at the place where it is defined. This is fine if the macro is used within a single file, but if a 41 | macro is exported, then it makes things complicated. 42 | 43 | The `$crate` metavariable lets you refer to things that are in the crate the macro was defined in (as opposed to the 44 | crate the macro was called in). If the macro was defined in crate `foo`, and used in crate `bar`, then a reference 45 | to a struct `Widget` like: `Widget::new()` is creating a new `bar::Widget` (and if one doesn't exist, you'll get an error). 46 | If you called `$crate::Widget::new()`, then you're always talking about `foo::Widget`, no matter what crate you're in. 47 | 48 | ## A footnote on how expanding macros into text is misleading 49 | 50 | (Based on the disclaimer for the brilliant 51 | [cargo-expand](https://github.com/dtolnay/cargo-expand/)) 52 | 53 | Be aware that macro expansion to text is a lossy process. That means that the 54 | expanded code we show in these kata should be used as a debugging aid only. 55 | There should be no expectation that the expanded code can be compiled 56 | successfully, nor that if it compiles then it behaves the same as the original 57 | code. In these kata, we try to avoid these issues as far as possible. 58 | 59 | For instance, `answer = 3` when compiled ordinarily by Rust, 60 | but the expanded code, when compiled, would set `answer = 4`. 61 | 62 | ```rust 63 | fn main() { 64 | let x = 1; 65 | 66 | macro_rules! first_x { 67 | () => { x } 68 | } 69 | 70 | let x = 2; 71 | 72 | let answer = x + first_x!(); 73 | } 74 | 75 | ``` 76 | 77 | Refer to [The Little Book Of Rust Macros](https://veykril.github.io/tlborm/decl-macros/minutiae/hygiene.html) 78 | for more on the considerations around macro hygiene. 79 | 80 | ## Exercise 12 81 | 82 | Exercise 12 consists of a file containing multiple modules. Fix the code so 83 | that the macro works correctly in all invocations. 84 | 85 | Note that you will need to use the `$crate` metavariable. 86 | -------------------------------------------------------------------------------- /exercises/12_hygienic_macros/main.rs: -------------------------------------------------------------------------------- 1 | macro_rules! coord { 2 | ($x:expr, $y:expr) => {}; 3 | ($x:expr, $y:expr, $z:expr) => {}; 4 | ($x:expr, $y:expr, $z:expr, $t:expr) => {}; 5 | } 6 | 7 | ////////// DO NOT CHANGE BELOW HERE ///////// 8 | 9 | #[derive(Debug)] 10 | pub struct Coordinate { 11 | pub x: i32, 12 | pub y: i32, 13 | } 14 | 15 | pub mod third_dimension { 16 | #[derive(Debug)] 17 | pub struct Coordinate { 18 | pub x: i32, 19 | pub y: i32, 20 | pub z: i32, 21 | } 22 | 23 | impl Coordinate { 24 | pub fn as_2d(self) -> super::Coordinate { 25 | coord!(self.x, self.y) 26 | } 27 | } 28 | } 29 | 30 | pub mod fourth_dimension { 31 | #[derive(Debug)] 32 | pub struct Coordinate { 33 | pub x: i32, 34 | pub y: i32, 35 | pub z: i32, 36 | pub t: i32, 37 | } 38 | 39 | impl Coordinate { 40 | pub fn as_3d(self) -> super::third_dimension::Coordinate { 41 | coord!(self.x, self.y, self.z) 42 | } 43 | } 44 | } 45 | 46 | fn main() { 47 | let four_dim = coord!(1, 2, 3, 1000); 48 | let three_dim = four_dim.as_3d(); 49 | let two_dim = three_dim.as_2d(); 50 | 51 | println!("{two_dim:?}"); 52 | } 53 | -------------------------------------------------------------------------------- /exercises/12_hygienic_macros/solutions/main.rs: -------------------------------------------------------------------------------- 1 | macro_rules! coord { 2 | ($x:expr, $y:expr) => { 3 | $crate::Coordinate { x: $x, y: $y } 4 | }; 5 | ($x:expr, $y:expr, $z:expr) => { 6 | $crate::third_dimension::Coordinate { 7 | x: $x, 8 | y: $y, 9 | z: $z, 10 | } 11 | }; 12 | ($x:expr, $y:expr, $z:expr, $t:expr) => { 13 | $crate::fourth_dimension::Coordinate { 14 | x: $x, 15 | y: $y, 16 | z: $z, 17 | t: $t, 18 | } 19 | }; 20 | } 21 | 22 | ////////// DO NOT CHANGE BELOW HERE ///////// 23 | 24 | #[derive(Debug)] 25 | pub struct Coordinate { 26 | pub x: i32, 27 | pub y: i32, 28 | } 29 | 30 | pub mod third_dimension { 31 | #[derive(Debug)] 32 | pub struct Coordinate { 33 | pub x: i32, 34 | pub y: i32, 35 | pub z: i32, 36 | } 37 | 38 | impl Coordinate { 39 | pub fn as_2d(self) -> super::Coordinate { 40 | coord!(self.x, self.y) 41 | } 42 | } 43 | } 44 | 45 | pub mod fourth_dimension { 46 | #[derive(Debug)] 47 | pub struct Coordinate { 48 | pub x: i32, 49 | pub y: i32, 50 | pub z: i32, 51 | pub t: i32, 52 | } 53 | 54 | impl Coordinate { 55 | pub fn as_3d(self) -> super::third_dimension::Coordinate { 56 | coord!(self.x, self.y, self.z) 57 | } 58 | } 59 | } 60 | 61 | fn main() { 62 | let four_dim = coord!(1, 2, 3, 1000); 63 | let three_dim = four_dim.as_3d(); 64 | let two_dim = three_dim.as_2d(); 65 | 66 | println!("{two_dim:?}"); 67 | } 68 | -------------------------------------------------------------------------------- /exercises/12_hygienic_macros/solutions/solution.diff: -------------------------------------------------------------------------------- 1 | @@ -1,7 +1,22 @@ 2 | macro_rules! coord { 3 | - ($x:expr, $y:expr) => {}; 4 | - ($x:expr, $y:expr, $z:expr) => {}; 5 | - ($x:expr, $y:expr, $z:expr, $t:expr) => {}; 6 | + ($x:expr, $y:expr) => { 7 | + $crate::Coordinate { x: $x, y: $y } 8 | + }; 9 | + ($x:expr, $y:expr, $z:expr) => { 10 | + $crate::third_dimension::Coordinate { 11 | + x: $x, 12 | + y: $y, 13 | + z: $z, 14 | + } 15 | + }; 16 | + ($x:expr, $y:expr, $z:expr, $t:expr) => { 17 | + $crate::fourth_dimension::Coordinate { 18 | + x: $x, 19 | + y: $y, 20 | + z: $z, 21 | + t: $t, 22 | + } 23 | + }; 24 | } 25 | 26 | ////////// DO NOT CHANGE BELOW HERE ///////// 27 | -------------------------------------------------------------------------------- /exercises/13_scoping_importing_and_exporting/README.md: -------------------------------------------------------------------------------- 1 | The subject of scoping, importing and exporting are well covered 2 | by the [Rust Reference](https://doc.rust-lang.org/reference/macros-by-example.html#scoping-exporting-and-importing). 3 | 4 | While practical examples of these may be useful, this first verison of `macrokata` does not 5 | include exercises for them. If you plan on using macros in a larger project, we suggest reading 6 | the above reference. 7 | -------------------------------------------------------------------------------- /exercises/99_extra_reading/README.md: -------------------------------------------------------------------------------- 1 | # Extra Reading 2 | 3 | There are two excellent resources for further reading on Rust's 4 | macro system: 5 | 6 | - [The Rust Reference](https://doc.rust-lang.org/reference/macros.html), 7 | - [The Little Book of Rust Macros](https://veykril.github.io/tlborm/) (note 8 | that an older version exists, so make sure you're reading this up-to-date one!) 9 | -------------------------------------------------------------------------------- /po/zh.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfpk/macrokata/a01b5d9f9269c9756b656fb54aa0b1c7b7be3484/po/zh.mo -------------------------------------------------------------------------------- /scripts/serve_book.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPTPATH="$( cd "$(dirname "$0")" || exit 1 >/dev/null 2>&1 ; pwd -P )" 4 | 5 | cd "$SCRIPTPATH/../book" || exit 1 6 | 7 | mdbook serve 8 | -------------------------------------------------------------------------------- /src/check.rs: -------------------------------------------------------------------------------- 1 | use glob::glob; 2 | use imara_diff::intern::InternedInput; 3 | use imara_diff::{diff, Algorithm, UnifiedDiffBuilder}; 4 | use std::path::PathBuf; 5 | use std::process::{Command, Output}; 6 | 7 | #[derive(Debug)] 8 | pub enum CheckError { 9 | MainFileDoesNotExist, 10 | SolutionFileDoesNotExist, 11 | DiffFileDoesNotExist, 12 | DiffFileDoesNotMatch { actual: String, expected: String }, 13 | SolutionFileDoesNotClippy, 14 | } 15 | 16 | pub fn check_all() { 17 | let exercise_main_glob: PathBuf = [env!("CARGO_MANIFEST_DIR"), "exercises", "*", "main.rs"] 18 | .iter() 19 | .collect(); 20 | 21 | let mut is_ok = true; 22 | for exercise in glob(&exercise_main_glob.to_string_lossy()).expect("Glob must work.") { 23 | let exercise = exercise.unwrap(); 24 | let exercise_name = exercise 25 | .parent() 26 | .expect("the glob has a parent") 27 | .file_name() 28 | .expect("the glob has a name"); 29 | if let Err(e) = check(&exercise_name.to_string_lossy()) { 30 | eprintln!("Check of {exercise_name:#?} failed: {e:?}"); 31 | is_ok = false; 32 | } 33 | } 34 | 35 | if !is_ok { 36 | std::process::exit(1); 37 | } 38 | } 39 | 40 | fn run_cargo_command(exercise: &str, command: &str) -> Result { 41 | let exercise_soln = format!("{exercise}_soln"); 42 | Command::new("cargo") 43 | .arg(command) 44 | .arg("--bin") 45 | .arg(&exercise_soln) 46 | .output() 47 | } 48 | 49 | pub fn check(exercise: &str) -> Result<(), CheckError> { 50 | let manifest_dir = env!("CARGO_MANIFEST_DIR"); 51 | let exercise_main_path: PathBuf = [manifest_dir, "exercises", exercise, "main.rs"] 52 | .iter() 53 | .collect(); 54 | let exercise_solution_path: PathBuf = 55 | [manifest_dir, "exercises", exercise, "solutions", "main.rs"] 56 | .iter() 57 | .collect(); 58 | let exercise_diff_path: PathBuf = [ 59 | manifest_dir, 60 | "exercises", 61 | exercise, 62 | "solutions", 63 | "solution.diff", 64 | ] 65 | .iter() 66 | .collect(); 67 | 68 | let exercise_main = std::fs::read_to_string(exercise_main_path) 69 | .map_err(|_| CheckError::MainFileDoesNotExist)?; 70 | let exercise_solution = std::fs::read_to_string(exercise_solution_path) 71 | .map_err(|_| CheckError::SolutionFileDoesNotExist)?; 72 | let exercise_diff = std::fs::read_to_string(exercise_diff_path) 73 | .map_err(|_| CheckError::DiffFileDoesNotExist)?; 74 | 75 | let input = InternedInput::new::<&str>(exercise_main.as_ref(), exercise_solution.as_ref()); 76 | let actual_diff = diff( 77 | Algorithm::Histogram, 78 | &input, 79 | UnifiedDiffBuilder::new(&input), 80 | ); 81 | 82 | if actual_diff == exercise_diff { 83 | let clippy = run_cargo_command(exercise, "clippy") 84 | .map(|o| o.status.success()) 85 | .unwrap_or(false); 86 | if !clippy { 87 | Err(CheckError::SolutionFileDoesNotClippy) 88 | } else { 89 | Ok(()) 90 | } 91 | } else { 92 | Err(CheckError::DiffFileDoesNotMatch { 93 | actual: actual_diff, 94 | expected: exercise_diff, 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/goal.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::process::{Command, Stdio}; 3 | 4 | pub fn goal(exercise: String) -> Result<(), Box> { 5 | Command::new("cargo") 6 | .arg("expand") 7 | .arg("--bin") 8 | .arg(format!("{exercise}_soln")) 9 | .arg("main") 10 | .stderr(Stdio::null()) 11 | .spawn() 12 | .unwrap() 13 | .wait() 14 | .unwrap(); 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use std::error::Error; 3 | 4 | mod check; 5 | mod goal; 6 | mod test; 7 | mod update_diff; 8 | 9 | use check::check_all; 10 | use goal::goal; 11 | use test::test; 12 | use update_diff::update_diff; 13 | 14 | #[derive(Subcommand, Debug)] 15 | enum SubCommands { 16 | Test { 17 | /// The name of the exercise to run. 18 | exercise: String, 19 | }, 20 | Goal { 21 | /// The name of the exercise to run. 22 | exercise: String, 23 | }, 24 | UpdateDiff { 25 | /// The name of the exercise to create a diff for. 26 | exercise: String, 27 | }, 28 | CheckAll, 29 | } 30 | 31 | /// MacroKata is a set of exercises to learn how to use 32 | /// macros well. 33 | #[derive(Parser, Debug)] 34 | #[command(author, version, about, long_about = None)] 35 | struct Args { 36 | /// Choose what MacroKata does. 37 | #[clap(subcommand)] 38 | command: SubCommands, 39 | } 40 | 41 | fn main() -> Result<(), Box> { 42 | let args = Args::parse(); 43 | 44 | match args.command { 45 | SubCommands::Test { exercise } => test(exercise), 46 | SubCommands::Goal { exercise } => goal(exercise), 47 | SubCommands::UpdateDiff { exercise } => update_diff(&exercise), 48 | SubCommands::CheckAll => { 49 | check_all(); 50 | Ok(()) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use imara_diff::intern::InternedInput; 2 | use imara_diff::{diff, Algorithm, UnifiedDiffBuilder}; 3 | use std::error::Error; 4 | use std::io::{self, Write}; 5 | use std::process::Command; 6 | 7 | pub fn test(exercise: String) -> Result<(), Box> { 8 | let color = if atty::is(atty::Stream::Stdout) { 9 | "always" 10 | } else { 11 | "never" 12 | }; 13 | 14 | let build_output = Command::new("cargo") 15 | .arg("build") 16 | .arg("--color") 17 | .arg(color) 18 | .arg("--quiet") 19 | .arg("--bin") 20 | .arg(&exercise) 21 | .output() 22 | .unwrap(); 23 | 24 | let stderr_is_empty = build_output.stderr.is_empty(); 25 | if !stderr_is_empty || !build_output.status.success() { 26 | println!("The following errors were encountered:"); 27 | io::stderr().write_all(&build_output.stderr)?; 28 | return Err("Build failed".into()); 29 | } 30 | 31 | let main_output = Command::new("cargo") 32 | .arg("expand") 33 | .arg("--color") 34 | .arg(color) 35 | .arg("--bin") 36 | .arg(&exercise) 37 | .arg("main") 38 | .output() 39 | .unwrap(); 40 | 41 | println!(); 42 | println!("This is the expansion you produced:"); 43 | println!(); 44 | io::stdout().write_all(&main_output.stdout)?; 45 | 46 | let soln_output = Command::new("cargo") 47 | .arg("expand") 48 | .arg("--color") 49 | .arg(color) 50 | .arg("--bin") 51 | .arg(format!("{exercise}_soln")) 52 | .arg("main") 53 | .output() 54 | .unwrap(); 55 | 56 | println!("\nThe expansion we expected is:\n"); 57 | 58 | io::stdout().write_all(&soln_output.stdout)?; 59 | 60 | let before = String::from_utf8_lossy(&main_output.stdout); 61 | let after = String::from_utf8_lossy(&soln_output.stdout); 62 | let input = InternedInput::new(before.as_ref(), after.as_ref()); 63 | let the_diff = diff( 64 | Algorithm::Histogram, 65 | &input, 66 | UnifiedDiffBuilder::new(&input), 67 | ); 68 | 69 | if the_diff.is_empty() { 70 | println!("\nCongratulations! You solved it.\n"); 71 | } else { 72 | println!("\nThe diff is:\n"); 73 | println!("{the_diff}"); 74 | } 75 | 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /src/update_diff.rs: -------------------------------------------------------------------------------- 1 | use imara_diff::intern::InternedInput; 2 | use imara_diff::{diff, Algorithm, UnifiedDiffBuilder}; 3 | use std::error::Error; 4 | use std::path::PathBuf; 5 | 6 | use std::fs::File; 7 | use std::io::prelude::*; 8 | 9 | pub fn update_diff(exercise: &str) -> Result<(), Box> { 10 | let manifest_dir = env!("CARGO_MANIFEST_DIR"); 11 | let exercise_main_path: PathBuf = [manifest_dir, "exercises", exercise, "main.rs"] 12 | .iter() 13 | .collect(); 14 | let exercise_solution_path: PathBuf = 15 | [manifest_dir, "exercises", exercise, "solutions", "main.rs"] 16 | .iter() 17 | .collect(); 18 | let exercise_diff_path: PathBuf = [ 19 | manifest_dir, 20 | "exercises", 21 | exercise, 22 | "solutions", 23 | "solution.diff", 24 | ] 25 | .iter() 26 | .collect(); 27 | 28 | let exercise_main = std::fs::read_to_string(exercise_main_path)?; 29 | let exercise_solution = std::fs::read_to_string(exercise_solution_path)?; 30 | 31 | let input = InternedInput::new::<&str>(exercise_main.as_ref(), exercise_solution.as_ref()); 32 | let actual_diff = diff( 33 | Algorithm::Histogram, 34 | &input, 35 | UnifiedDiffBuilder::new(&input), 36 | ); 37 | 38 | let mut file = File::create(exercise_diff_path)?; 39 | file.write_all(actual_diff.as_bytes())?; 40 | 41 | Ok(()) 42 | } 43 | --------------------------------------------------------------------------------