Repository: mun-lang/mun (language: rust)

Issue:
Test Mun examples in the book
The book contains a lot of useful Mun code examples. We incorporated the Mun book into the main repository to be able to test all these examples while we develop the language. This hopefully ensures that the book stays up to date with the features we develop.

However, currently, we are not yet testing all these examples. To do so we probably need to create [a plugin for mdbook](https://github.com/rust-lang/mdBook#3rd-party-plugins).

**Good first issue process:**

- [x] [read our contributing guidelines](https://github.com/mun-lang/mun/blob/master/CONTRIBUTING.md)
- [x] claim this issue (assign yourself or comment below)
- [x] implement the feature and start a Pull Request with in the description: closes #209 . 

If this is your first PR, welcome 🎉 😄


Discussion / hints:
I would like to contribute to this issue 👀

Fix patch:
diff --git a/book/book.toml b/book/book.toml
index d743ef95f..db0a8692a 100644
--- a/book/book.toml
+++ b/book/book.toml
@@ -9,3 +9,6 @@ title = "The Mun Programming Language"
 additional-css = ["theme/mun.css"]
 additional-js = ["theme/trailing_slash_hack.js"]
 git-repository-url = "https://github.com/mun-lang/mun/tree/master/book"
+
+[output.mun_mdbook_test_plugin]
+command = "mun_mdbook_test_plugin"
diff --git a/book/listings/ch01-getting-started/listing01.mun b/book/listings/ch01-getting-started/listing01.mun
index 2695cb766..b26bbc18f 100644
--- a/book/listings/ch01-getting-started/listing01.mun
+++ b/book/listings/ch01-getting-started/listing01.mun
@@ -1,3 +1,7 @@
+# pub fn main() {
+#   fibonacci_n();
+# }
+
 pub fn fibonacci_n() -> i64 {
     let n = arg();
     fibonacci(n)
diff --git a/book/listings/ch01-getting-started/listing02.mun b/book/listings/ch01-getting-started/listing02.mun
index 7e8a407dd..2f4a6d287 100644
--- a/book/listings/ch01-getting-started/listing02.mun
+++ b/book/listings/ch01-getting-started/listing02.mun
@@ -1,3 +1,7 @@
+# pub fn main() {
+#   fibonacci(arg());
+# }
+
 pub fn arg() -> i64 {
     5
 }
diff --git a/book/src/ch01-02-hello-fibonacci.md b/book/src/ch01-02-hello-fibonacci.md
index 4ced272b7..f76dc3938 100644
--- a/book/src/ch01-02-hello-fibonacci.md
+++ b/book/src/ch01-02-hello-fibonacci.md
@@ -27,8 +27,7 @@ Open up the new source file and enter the code in Listing 1-1.
 
 Filename: hello_fibonacci.mun
 
-<!-- HACK: Add an extension to support hiding of Mun code -->
-```mun  
+```mun
 {{#include ../listings/ch01-getting-started/listing01.mun}}
 ```
 
diff --git a/book/src/ch01-03-hello-hot-reloading.md b/book/src/ch01-03-hello-hot-reloading.md
index ffbddebd0..f6d62ae46 100644
--- a/book/src/ch01-03-hello-hot-reloading.md
+++ b/book/src/ch01-03-hello-hot-reloading.md
@@ -10,7 +10,7 @@ both `args` and `fibonacci`.
 Filename: hello_fibonacci.mun
 
 <!-- HACK: Add an extension to support hiding of Mun code -->
-```mun 
+```mun
 {{#include ../listings/ch01-getting-started/listing02.mun}}
 ```
 
diff --git a/book/src/ch02-01-values-and-types.md b/book/src/ch02-01-values-and-types.md
index 7f5d9895d..a4c09b860 100644
--- a/book/src/ch02-01-values-and-types.md
+++ b/book/src/ch02-01-values-and-types.md
@@ -18,6 +18,9 @@ forced to explicitly annotate variables in a few locations to ensure a contract
 between interdependent code.
 
 ```mun
+# pub fn main() {
+#   bar(1);
+# }
 fn bar(a: i32) -> i32 {
     let foo = 3 + a;
     foo
@@ -70,7 +73,7 @@ single-precision float of 32 bits, and the `f64` type has double precision -
 requiring 64 bits.
 
 ```mun
-fn main() {
+pub fn main() {
     let f = 3.0; // f64
 }
 ```
@@ -82,7 +85,7 @@ The `bool` (or *boolean*) type has two values, `true` and `false`, that are
 used to  evaluate conditions. It takes up one 1 byte (or 8 bits).
 
 ```mun
-fn main() {
+pub fn main() {
     let t = true;
 
     let f: bool = false; // with explicit type annotation
@@ -101,10 +104,12 @@ written as a decimal, hexadecimal, octal or binary value. These are all
 examples of valid literals:
 
 ```mun
+# pub fn main() {
 let a = 367;
 let b = 0xbeaf;
 let c = 0o76532;
 let d = 0b0101011;
+# }
 ```
 
 A floating-point literal comes in two forms:
@@ -116,9 +121,11 @@ A floating-point literal comes in two forms:
 Examples of valid floating-point literals are:
 
 ```mun
+# pub fn main() {
 let a: f64 = 3.1415;
 let b: f64 = 3.;
 let c: f64 = 314.1592654e-2;
+# }
 ```
 
 #### Separators
@@ -128,8 +135,10 @@ visually separate numbers from one another. They do not have any semantic
 significance but can be useful to the eye.
 
 ```mun
+# pub fn main() {
 let a: i64 = 1_000_000;
 let b: f64 = 1_000.12;
+# }
 ```
 
 #### Type suffix
@@ -148,16 +157,20 @@ Note that integer literals can have floating-point suffixes. This is not the
 case the other way around.
 
 ```mun
+# pub fn main() {
 let a: u8 = 128_u8;
 let b: i128 = 99999999999999999_i128;
 let c: f32 = 10_f32; // integer literal with float suffix 
+# }
 ```
 
 When providing a literal, the compiler will always check if a literal value will
 fit the type. If not, an error will be emitted:
 
-```mun
+```mun,compile_fail
+# pub fn main() {
 let a: u8 = 1123123124124_u8; // literal out of range for `u8`
+# }
 ```
 
 ### Numeric operations 
@@ -166,7 +179,7 @@ Mun supports all basic mathematical operations for number types: addition,
 subtraction, division, multiplication, and remainder.
 
 ```mun
-fn main() {
+pub fn main() {
     // addition 
     let a = 10 + 5;
 
@@ -191,7 +204,7 @@ same type.
 Unary operators are also supported:
 
 ```mun
-fn main() {
+pub fn main() {
     let a = 4;
     // negate
     let b = -a;
@@ -209,8 +222,10 @@ shadow any previous declaration in the same block. This is often useful if you
 want to change the type of a variable.
 
 ```mun
+# pub fn main() {
 let a: i32 = 3;
 let a: f64 = 5.0; 
+# }
 ```
 
 ### Use before initialization
@@ -218,12 +233,15 @@ let a: f64 = 5.0;
 All variables in Mun must be initialized before usage. Uninitialized variables
 can be declared but they must be assigned a value before they can be read.
 
-```mun
+```mun,compile_fail
+# pub fn main() {
+# let some_conditional = false;
 let a: i32;
 if some_conditional {
     a = 4;
 }
 let b = a; // invalid: a is potentially uninitialized
+# }
 ```
 
 Note that declaring a variable without a value is often a bad code smell since
@@ -232,10 +250,13 @@ the above could have better been written by *returning* a value from the
 uninitialized value.
 
 ```mun
+# pub fn main() {
+# let some_conditional = true;
 let a: i32 = if some_conditional {
     4
 } else {
     5
 }
 let b = a;
+# }
 ```
diff --git a/book/src/ch02-02-functions.md b/book/src/ch02-02-functions.md
index e1684067f..0ae4c2bf6 100644
--- a/book/src/ch02-02-functions.md
+++ b/book/src/ch02-02-functions.md
@@ -8,7 +8,7 @@ Mun uses *snake case* as the conventional style for function and variable names.
 In snake case all letters are lowercase and words are separated by underscores. 
 
 ```mun
-fn main() {
+pub fn main() {
     another_function();
 }
 
@@ -31,6 +31,9 @@ Marking a function with the `pub` keyword allows you to use it from outside of
 the module it is defined in.
 
 ```mun
+# pub fn main() {
+#   bar();
+# }
 // This function is not accessible outside of this code
 fn foo() {
     // ...
@@ -59,7 +62,7 @@ The following is a rewritten version of `another_function` that shows what an
 argument looks like:
 
 ```mun
-fn main() {
+pub fn main() {
     another_function(3);
 }
 
@@ -72,7 +75,7 @@ type. When you want a function to use multiple arguments, separate them with
 commas:
 
 ```mun
-fn main() {
+pub fn main() {
     another_function(3, 4);
 }
 
@@ -90,7 +93,7 @@ Creating a variable and assigning a value to it with the `let` keyword is a
 statement. In the following example, `let y = 6;` is a statement.
 
 ```mun
-fn main() {
+pub fn main() {
     let y = 6;
 }
 ```
@@ -108,6 +111,9 @@ evaluate to the last expression in them. Blocks can therefore also be used on
 the right hand side of a `let` statement.
 
 ```mun
+# pub fn main() {
+#   foo();
+# }
 fn foo() -> i32 {
     let bar = {
         let b = 3;
@@ -131,7 +137,7 @@ fn five() -> i32 {
     5
 }
 
-fn main() {
+pub fn main() {
     let x = five();
 }
 ```
@@ -144,7 +150,10 @@ is specified too, as `-> i32`.
 Whereas the last expression in a block implicitly becomes that blocks return
 value, explicit `return` statements always return from the entire function:
 
-```mun
+```mun,ignore
+pub fn main() {
+    let _ = foo();
+}
 fn foo() -> i32 {
     let bar = {
         let b = 3;
diff --git a/book/src/ch02-03-control-flow.md b/book/src/ch02-03-control-flow.md
index 4e11e6dbf..28fc1d92c 100644
--- a/book/src/ch02-03-control-flow.md
+++ b/book/src/ch02-03-control-flow.md
@@ -9,7 +9,7 @@ constructs that allow developers to control the flow of execution. Mun provides
 An `if` expression allows you to branch your code depending on conditions.
 
 ```mun
-fn main() {
+pub fn main() {
     let number = 3;
 
     if number < 5 {
@@ -30,7 +30,7 @@ condition evaluates to false. You can also have multiple conditions by combining
 `if` and `else` in an `else if` expression. For example:
 
 ```mun
-fn main() {
+pub fn main() {
     let number = 6;
     if number > 10 {
         // The number if larger than 10
@@ -51,13 +51,14 @@ The `if` expression can be used on the right side of a `let` statement
 just like a block:
 
 ```mun
-fn main() {
+pub fn main() {
     let condition = true;
     let number = if condition {
         5
     } else {
         6
     };
+}
 ```
 
 Depending on the condition, the `number` variable will be bound to the value of
@@ -72,7 +73,7 @@ A `loop` expression can be used to create an infinite loop. Breaking out of the
 loop is done using the `break` statement.
 
 ```mun
-fn main() {
+pub fn main() {
     let i = 0;
     loop {
         if i > 5 {
@@ -88,6 +89,9 @@ Similar to `if`/`else` expressions, `loop` blocks can have a return value that
 can be returned through the use of a `break` statement.
 
 ```mun
+# pub fn main() {
+#   count(4, 4);
+# }
 fn count(i: i32, n: i32) -> i32 {
     let loop_count = 0;
     loop {
@@ -102,11 +106,13 @@ fn count(i: i32, n: i32) -> i32 {
 
 All `break` statements in a `loop` must have the same return type.
 
-```mun
+```mun,compile_fail
+# pub fn main() {
 let a = loop {
     break 3;
     break; // expected `{integer}`, found `nothing`
 };
+# }
 ```
 
 
@@ -118,7 +124,7 @@ block of code to execute upon each iteration. Just like with the `if`
 expression, no parentheses are required around the condition expression.
 
 ```mun
-fn main() {
+pub fn main() {
     let i = 0;
     while i <= 5 {
         i += 1;
diff --git a/book/src/ch02-04-extern-fn.md b/book/src/ch02-04-extern-fn.md
index c049dad17..34cc84211 100644
--- a/book/src/ch02-04-extern-fn.md
+++ b/book/src/ch02-04-extern-fn.md
@@ -6,7 +6,7 @@ definitions have to be provided to the runtime when loading a Mun library.
 Failure to do so will result in a runtime link error, and loading the library
 will fail. Take this code for example:
 
-```mun
+```mun,no_run
 {{#include ../listings/ch02-basic-concepts/listing01.mun}}
 ```
 
diff --git a/book/src/ch03-04-hot-reloading-structs.md b/book/src/ch03-04-hot-reloading-structs.md
index ff494c5cd..8371bcfd2 100644
--- a/book/src/ch03-04-hot-reloading-structs.md
+++ b/book/src/ch03-04-hot-reloading-structs.md
@@ -13,7 +13,7 @@ our simulation does it to log the elapsed time, every frame.
 
 Filename: buoyancy.mun
 
-```mun
+```mun,no_run
 {{#include ../listings/ch03-structs/listing13.mun}}
 ```
 
@@ -59,6 +59,10 @@ Let's add this to the `SimContext` struct and update the `new_sim` function
 accordingly, as shown in Listing 3-15.
 
 ```mun
+# pub fn main() {
+#   new_sim();
+#   new_sphere();
+# }
 {{#include ../listings/ch03-structs/listing15.mun:3:41}}
 ```
 
@@ -75,7 +79,7 @@ to zero.
 
 We can verify this by replacing the `log_f32(elapsed_secs)` statement with:
 
-```mun
+```mun,ignore
 {{#include ../listings/ch03-structs/listing15.mun:44}}
 ```
 
@@ -84,13 +88,13 @@ that we can employ to still manually initialize our memory to desired values by
 using this behaviour to our advantage. Let's first add `token: u32` to the
 `SimContext`:
 
-```mun
+```mun,ignore
 {{#include ../listings/ch03-structs/listing16.mun:7}}
 ```
 
 and set it to zero in the `new_sim` function:
 
-```mun
+```mun,ignore
 {{#include ../listings/ch03-structs/listing16.mun:26}}
 ```
 
@@ -98,13 +102,14 @@ As before, the `token` value will be initialized to zero when the library has
 been hot reloaded. Next, we add a `hot_reload_token` function that returns a
 non-zero `u32` value, e.g. `1`:
 
-```mun
+```mun,ignore
 {{#include ../listings/ch03-structs/listing16.mun:45:47}}
+
 ```
 
 Finally, we add this `if` statement to the `sim_update` function:
 
-```mun
+```mun,ignore
 {{#include ../listings/ch03-structs/listing16.mun:50:56}}
 ```
 
@@ -118,7 +123,7 @@ Time to add the actual logic for simulating buoyancy. The formula for
 calculating the buoyancy force is *force = submerged volume \* water density \*
 gravity*.
 
-```mun
+```mun,ignore
 {{#include ../listings/ch03-structs/listing17.mun:51:73}}
 ```
 
@@ -128,12 +133,13 @@ sphere's volume and density: *mass = volume \* density*. Instead of doing this
 every frame, let's replace the sphere's `density` field with a `mass` field:
 
 ```mun
+# pub fn main() {}
 {{#include ../listings/ch03-structs/listing17.mun:10:15}}
 ```
 
 and pre-calculate it on construction:
 
-```mun
+```mun,ignore
 {{#include ../listings/ch03-structs/listing17.mun:30:43}}
 ```
 
@@ -141,7 +147,7 @@ To initialize the sphere's `mass` field, we can employ the same trick as before;
 this time only initializing the sphere and incrementing `hot_reload_token` to
 `2`:
 
-```mun
+```mun,ignore
 {{#include ../listings/ch03-structs/listing17.mun:80:84}}
 ```
 
@@ -182,7 +188,7 @@ acceleration from the velocity to ensure that the sphere drops into the water.
 Last but not least, let's log the sphere's height to the console, so we can
 verify that the simulation is running correctly.
 
-```mun
+```mun,ignore
 {{#include ../listings/ch03-structs/listing17.mun:86:105}}
 ```
 


Test patch:
diff --git a/crates/mun_mdbook_test_plugin/Cargo.toml b/crates/mun_mdbook_test_plugin/Cargo.toml
new file mode 100644
index 000000000..7021feab5
--- /dev/null
+++ b/crates/mun_mdbook_test_plugin/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "mun_mdbook_test_plugin"
+version = "0.1.0"
+authors = ["The Mun Team <team@mun-lang.org>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+mdbook = "0.4.2"
+regex = "1"
+mun_test = { path = "../mun_test" }
+mun_runtime = { path = "../mun_runtime" }
diff --git a/crates/mun_mdbook_test_plugin/src/main.rs b/crates/mun_mdbook_test_plugin/src/main.rs
new file mode 100644
index 000000000..a71e27743
--- /dev/null
+++ b/crates/mun_mdbook_test_plugin/src/main.rs
@@ -0,0 +1,118 @@
+use mdbook::renderer::RenderContext;
+use mdbook::BookItem;
+
+use std::io;
+
+use regex::Regex;
+
+use mun_test::{CompileAndRunTestDriver, CompileTestDriver};
+
+use mun_runtime::{invoke_fn, RuntimeBuilder};
+
+use std::panic;
+
+enum TestingMethod {
+    Ignore,
+    CompileFail,
+    Compile,
+    CompileAndRun,
+}
+
+impl Default for TestingMethod {
+    fn default() -> Self {
+        TestingMethod::CompileAndRun
+    }
+}
+
+fn get_testing_method(code: &str) -> TestingMethod {
+    let mut testing_method = TestingMethod::default();
+
+    for line in code.lines() {
+        if let Some(first_char) = line.chars().next() {
+            if first_char != ',' {
+                return testing_method;
+            }
+        } else {
+            return testing_method;
+        }
+        if line == ",compile_fail" {
+            return TestingMethod::CompileFail;
+        }
+        if line == ",no_run" {
+            testing_method = TestingMethod::Compile;
+        }
+        if line == ",ignore" {
+            return TestingMethod::Ignore;
+        }
+    }
+
+    testing_method
+}
+
+fn test_code(code: &str) {
+    let testing_method = get_testing_method(code);
+
+    // Removing '#' from code
+    let mut code = code.replace("\n#", "\n");
+
+    // Removing testing commands
+    for testing_command in ["compile_fail", "no_run", "ignore"].iter() {
+        code = code.replace(format!(",{}", testing_command).as_str(), "");
+    }
+
+    fn config_fn(runtime_builder: RuntimeBuilder) -> RuntimeBuilder {
+        runtime_builder
+    }
+
+    match testing_method {
+        TestingMethod::Ignore => {}
+        TestingMethod::CompileFail => {
+            let previous_hook = panic::take_hook();
+            panic::set_hook(Box::new(|_| {}));
+
+            if panic::catch_unwind(|| CompileTestDriver::new(&code)).is_ok() {
+                panic::set_hook(previous_hook);
+                panic!("Code that should have caused the error compiled successfully ❌")
+            }
+
+            panic::set_hook(previous_hook);
+        }
+        TestingMethod::Compile => {
+            CompileTestDriver::new(&code);
+        }
+        TestingMethod::CompileAndRun => {
+            let compile_and_run_test_driver =
+                CompileAndRunTestDriver::new(&code, config_fn).unwrap();
+
+            if compile_and_run_test_driver
+                .runtime()
+                .borrow()
+                .get_function_definition("main")
+                .is_none()
+            {
+                panic!("Function `main` not found in mun code, but requested.");
+            }
+
+            let _: () =
+                invoke_fn!(compile_and_run_test_driver.runtime().borrow_mut(), "main").unwrap();
+        }
+    }
+}
+
+fn main() {
+    let mut stdin = io::stdin();
+
+    let ctx = RenderContext::from_json(&mut stdin).unwrap();
+
+    let re = Regex::new(r"```mun((?s:.)*?)```").unwrap();
+
+    for item in ctx.book.iter() {
+        if let BookItem::Chapter(ref ch) = *item {
+            println!("Testing code in chapter '{}':", ch.name);
+            for capture in re.captures_iter(&ch.content) {
+                test_code(&capture[1]);
+            }
+            println!("Pass ✔");
+        }
+    }
+}