From 7f082a7367995b5803a83758d545c8bfffc320b8 Mon Sep 17 00:00:00 2001 From: Tom Kunc Date: Mon, 9 Jan 2023 17:57:01 -0700 Subject: [PATCH] Push all fixes related to issue #42 --- Cargo.toml | 2 +- README.md | 7 +- exercises/04_expression_variables/README.md | 5 +- exercises/05_more_complex_example/README.md | 5 +- exercises/06_repetition/README.md | 2 +- exercises/09_ambiguity_and_ordering/main.rs | 6 +- .../solutions/solution.diff | 26 +++---- exercises/10_macros_calling_macros/README.md | 12 ++-- exercises/12_hygienic_macros/README.md | 16 ++--- src/test.rs | 68 +++++++++---------- 10 files changed, 74 insertions(+), 75 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 84145b0..3920b42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "macrokata" -version = "0.3.0" +version = "0.3.1" edition = "2021" default-run = "macrokata" diff --git a/README.md b/README.md index bd035c9..e99ae03 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ macros in Rust. When completing each task, there are three goals: You should complete the kata in order, as they increase in difficulty, and depend on previous kata. +This set of exericses is written for people who have already spent some time +programming in Rust. Before completing this, work through a rust tutorial +and build some small programs yourself. + ## Getting Started Clone this repository: @@ -38,7 +42,8 @@ $ cargo build --bin macrokata ``` You can find the first kata (`my_first_macro`) inside `exercises/01_my_first_macro`. -Read the `README.md` file and get started by editing the `main.rs` file. +Read the [first chapter of the book](https://tfpk.github.io/macrokata/01_README.html) +and get started by editing the `main.rs` file. To compare your expanded code to the "goal", use the `test` subcommand: diff --git a/exercises/04_expression_variables/README.md b/exercises/04_expression_variables/README.md index a232769..aa1af26 100644 --- a/exercises/04_expression_variables/README.md +++ b/exercises/04_expression_variables/README.md @@ -19,8 +19,9 @@ It's also worth mentioning the fragment specifier `stmt`, which is similar to # Macros and the Precedence of Operators Macros do not affect the order of operations. If the expression `3 * math!(4, -plus, 2)` expands to `3 * 4 + 2`, the answer will be 14, not 18 (as you might -expect from the brackets). +plus, 2)` expands to `3 * 4 + 2`, you might expect rust to interpret it as +`3 * (4 + 2)`. Since Rust removes the macro before evaluating mathematical +operations though, Rust just sees `3 * 4 + 2`, which it evaluates as `14`. # "Follow-set Ambiguity Rules" diff --git a/exercises/05_more_complex_example/README.md b/exercises/05_more_complex_example/README.md index 6f59570..010eefe 100644 --- a/exercises/05_more_complex_example/README.md +++ b/exercises/05_more_complex_example/README.md @@ -24,12 +24,15 @@ for row in 1..5 { # } ``` +Note that the names of the variables may change (i.e. they could be `row` and +`col`, or `x` and `y`, or something else). + To complete this task, there more fragment specifiers you will need to know about: - `ident`: an "identifier", like a variable name. `ident` metavariables Can be followed by anything. - - `block`: a "block expression" (anything inside curly braces). + - `block`: a "block expression" (curly braces, and their contents). Can be followed by anything. - `ty`: a type. Can only be followed by `=>`, `,`, `=`, `|`, `;`, `:`, `>`, `>>`, `[`, `{`, `as`, `where`, or a `block` metavariable. diff --git a/exercises/06_repetition/README.md b/exercises/06_repetition/README.md index c761c1f..101b912 100644 --- a/exercises/06_repetition/README.md +++ b/exercises/06_repetition/README.md @@ -84,7 +84,7 @@ The line `$(my_vec.push($my_literal));+;` is nearly identical to the repetition - `$(` tells us that we're starting a repetition. - `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. - The `)` means that we're done describing the code that will be transcribed. - - The `;` means we're separating these lines with semicolons. + - 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). - The `+` ends the repetition. - The `;` adds a final semicolon after the expansion of everything. diff --git a/exercises/09_ambiguity_and_ordering/main.rs b/exercises/09_ambiguity_and_ordering/main.rs index 95eabf0..23581e7 100644 --- a/exercises/09_ambiguity_and_ordering/main.rs +++ b/exercises/09_ambiguity_and_ordering/main.rs @@ -1,6 +1,6 @@ ////////// DO NOT CHANGE BELOW HERE ///////// -/// This enum should represent what number the user wrote. +/// This enum should represent what code the user wrote exactly. /// Even though to a compiled program there's no difference, /// this will let the program tell what sort of code the user wrote. #[derive(Debug)] @@ -36,11 +36,11 @@ macro_rules! get_number_type { ( $block:block ) => { NumberType::UnknownBecauseBlock($block) }; - ( +$positive:literal ) => { + ( $positive:literal ) => { NumberType::PositiveNumber($positive) }; ( -$negative:literal ) => { - NumberType::NegativeNumber($negative) + NumberType::NegativeNumber(-$negative) }; } diff --git a/exercises/09_ambiguity_and_ordering/solutions/solution.diff b/exercises/09_ambiguity_and_ordering/solutions/solution.diff index 17d8b02..e437f4e 100644 --- a/exercises/09_ambiguity_and_ordering/solutions/solution.diff +++ b/exercises/09_ambiguity_and_ordering/solutions/solution.diff @@ -1,11 +1,3 @@ -@@ -1,6 +1,6 @@ - ////////// DO NOT CHANGE BELOW HERE ///////// - --/// This enum should represent what number the user wrote. -+/// This enum should represent what code the user wrote exactly. - /// Even though to a compiled program there's no difference, - /// this will let the program tell what sort of code the user wrote. - #[derive(Debug)] @@ -24,23 +24,23 @@ // Sum together at least two expressions. @@ -20,20 +12,20 @@ macro_rules! get_number_type { - ( $e:expr ) => { - NumberType::UnknownBecauseExpr($e) +- }; +- ( $block:block ) => { +- NumberType::UnknownBecauseBlock($block) + ( -$negative:literal ) => { + NumberType::NegativeNumber(-$negative) -+ }; -+ ( $positive:literal ) => { -+ NumberType::PositiveNumber($positive) }; - ( $block:block ) => { - NumberType::UnknownBecauseBlock($block) + ( $positive:literal ) => { + NumberType::PositiveNumber($positive) }; -- ( +$positive:literal ) => { -- NumberType::PositiveNumber($positive) -- }; - ( -$negative:literal ) => { -- NumberType::NegativeNumber($negative) +- NumberType::NegativeNumber(-$negative) ++ ( $block:block ) => { ++ NumberType::UnknownBecauseBlock($block) ++ }; + ( $expr:expr ) => { + NumberType::UnknownBecauseExpr($expr) }; diff --git a/exercises/10_macros_calling_macros/README.md b/exercises/10_macros_calling_macros/README.md index 2373d46..542a223 100644 --- a/exercises/10_macros_calling_macros/README.md +++ b/exercises/10_macros_calling_macros/README.md @@ -60,11 +60,13 @@ two token trees not separated by a `,` could not match). There is one important restriction when calling a macro using another macro. When forwarding a matched fragment to another macro-by-example, matchers in the -second macro will see an opaque AST of the fragment type. The second macro can't -use literal tokens to match the fragments in the matcher, only a fragment -specifier of the same type. The `ident`, `lifetime`, and `tt` fragment types are an -exception, and *can* be matched by literal tokens. The following illustrates this -restriction: +second macro will be passed an +[AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) of the fragment type, +which cannot matched on except as a fragment of that type. The second macro +can't use literal tokens to match the fragments in the matcher, only a fragment +specifier of the same type. The `ident`, `lifetime`, and `tt` fragment types are +an exception, and *can* be matched by literal tokens. The following illustrates +this restriction: ```rust,ignore macro_rules! foo { diff --git a/exercises/12_hygienic_macros/README.md b/exercises/12_hygienic_macros/README.md index 855c269..4e14f5b 100644 --- a/exercises/12_hygienic_macros/README.md +++ b/exercises/12_hygienic_macros/README.md @@ -4,20 +4,19 @@ To quote [the reference](https://doc.rust-lang.org/reference/macros-by-example.h > By default, all identifiers referred to in a macro are expanded as-is, and are > looked up at the macro's invocation site. This can lead to issues if a macro -> refers to an item or macro which isn't in scope at the invocation site. To +> refers to an item (i.e. function/struct/enum/etc.) or macro which isn't in scope at the invocation site. To > alleviate this, the `$crate` metavariable can be used at the start of a path to > force lookup to occur inside the crate defining the macro. Here is an example to illustrate (again, taken from the reference linked above): -```rust +```rust,ignore // Definitions in the `helper_macro` crate. - #[macro_export] macro_rules! helped { /* () => { helper!() } - ^^^^^^ This might lead to an error due to 'helper' not being in scope. + // ^^^^^^ This might lead to an error due to 'helper' not being in scope. */ () => { $crate::helper!() } } @@ -48,11 +47,11 @@ There should be no expectation that the expanded code can be compiled successfully, nor that if it compiles then it behaves the same as the original code. In these kata, we try to avoid these issues as far as possible. -For instance, the following function returns `3` when compiled ordinarily by Rust, -but the expanded code compiles and returns `4`. +For instance, `answer = 3` when compiled ordinarily by Rust, +but the expanded code, when compiled, would set `answer = 4`. ```rust -fn f() -> i32 { +fn main() { let x = 1; macro_rules! first_x { @@ -61,8 +60,9 @@ fn f() -> i32 { let x = 2; - x + first_x!() + let answer = x + first_x!(); } + ``` Refer to [The Little Book Of Rust Macros](https://veykril.github.io/tlborm/decl-macros/minutiae/hygiene.html) diff --git a/src/test.rs b/src/test.rs index ff54842..f0a3a64 100644 --- a/src/test.rs +++ b/src/test.rs @@ -21,49 +21,45 @@ pub fn test(exercise: String) -> Result<(), Box> { .output() .unwrap(); - if !main_output.stderr.is_empty() { - println!("Got some errors when expand the macro:"); + let stderr_is_empty = main_output.stderr.is_empty(); + if !stderr_is_empty || !main_output.status.success() { + println!("Got some errors when expanding the macro:"); println!(); io::stderr().write_all(&main_output.stderr)?; - } + } else { + println!("This is the expansion you produced:"); + println!(); + io::stdout().write_all(&main_output.stdout)?; - println!("This is the expansion you produced:"); - println!(); - io::stdout().write_all(&main_output.stdout)?; + let soln_output = Command::new("cargo") + .arg("expand") + .arg("--color") + .arg(color) + .arg("--bin") + .arg(format!("{exercise}_soln")) + .arg("main") + .output() + .unwrap(); - let soln_output = Command::new("cargo") - .arg("expand") - .arg("--color") - .arg(color) - .arg("--bin") - .arg(format!("{exercise}_soln")) - .arg("main") - .output() - .unwrap(); + println!("\nThe expansion we expected is:\n"); - println!(); - println!("The expansion we expected is:"); - println!(); - io::stdout().write_all(&soln_output.stdout)?; + io::stdout().write_all(&soln_output.stdout)?; - let before = String::from_utf8_lossy(&main_output.stdout); - let after = String::from_utf8_lossy(&soln_output.stdout); - let input = InternedInput::new(before.as_ref(), after.as_ref()); - let the_diff = diff( - Algorithm::Histogram, - &input, - UnifiedDiffBuilder::new(&input), - ); + let before = String::from_utf8_lossy(&main_output.stdout); + let after = String::from_utf8_lossy(&soln_output.stdout); + let input = InternedInput::new(before.as_ref(), after.as_ref()); + let the_diff = diff( + Algorithm::Histogram, + &input, + UnifiedDiffBuilder::new(&input), + ); - if the_diff.is_empty() { - println!(); - println!("Congratulation! You solved it."); - println!(); - } else { - println!(); - println!("The diff is:"); - println!(); - println!("{the_diff}"); + if the_diff.is_empty() { + println!("\nCongratulations! You solved it.\n"); + } else { + println!("\nThe diff is:\n"); + println!("{the_diff}"); + } } Ok(())