diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a609ea3c1..9273cb487 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,25 @@ jobs: - name: Install LLVM uses: ./.github/actions/install-llvm - - name: Cargo check + - name: Cargo build + uses: actions-rs/cargo@v1 + continue-on-error: ${{ matrix.config.toolchain == 'nightly' }} + with: + command: build + + - name: Install mdbook + if: ${{ matrix.config.os == 'ubuntu-latest' && matrix.config.toolchain == 'stable' }} + uses: actions-rs/install@v0.1 + with: + crate: mdbook + version: latest + use-tool-cache: true + + - name: mdbook test + if: ${{ matrix.config.os == 'ubuntu-latest' && matrix.config.toolchain == 'stable' }} + run: mdbook test book -L target/debug/deps + + - name: Cargo test uses: actions-rs/cargo@v1 continue-on-error: ${{ matrix.config.toolchain == 'nightly' }} with: diff --git a/.gitignore b/.gitignore index c3935cb09..8edb02e34 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,4 @@ Cargo.lock # Build artifacts *.munlib -**/target/* \ No newline at end of file +**/target/* diff --git a/.gitmodules b/.gitmodules index c33292cd3..2b2315dfe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,10 @@ [submodule "crates/mun_abi/c"] path = crates/mun_abi/c - url = ../../mun-lang/abi-c + url = https://github.com/mun-lang/abi-c.git [submodule "crates/mun_runtime_capi/ffi"] path = crates/mun_runtime_capi/ffi - url = ../../mun-lang/runtime-ffi.git + url = https://github.com/mun-lang/runtime-ffi.git +[submodule "book/vendor/highlight.js"] + path = book/vendor/highlight.js + url = https://github.com/mun-lang/highlight.js.git + branch = add-mun diff --git a/README.md b/README.md index b033324ff..a4c60abbb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![Build Status][build-badge]][build] [![Crates.io][crates-badge]][crates] -[![docs page][dosc-badge]][docs] +[![docs master][docs-master-badge]][docs-master] +[![docs v0.2][docs-v0.2-badge]][docs-v0.2] [![MIT/Apache][licence-badge]][license] [![Join us on Discord][discord-badge]][discord] [![codecov][coverage-badge]][coverage] @@ -17,8 +18,11 @@ [coverage-badge]: https://img.shields.io/codecov/c/github/mun-lang/mun.svg [coverage]: https://codecov.io/gh/mun-lang/mun -[dosc-badge]: https://img.shields.io/badge/docs-website-blue.svg -[docs]: https://docs.mun-lang.org/ +[docs-master-badge]: https://img.shields.io/badge/docs-master-blue.svg +[docs-master]: https://docs.mun-lang.org/ + +[docs-v0.2-badge]: https://img.shields.io/badge/docs-v0.2-blue.svg +[docs-v0.2]: https://docs.mun-lang.org/v0.2/ [licence-badge]: https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue [license]: COPYRIGHT @@ -32,31 +36,35 @@ ## Features -- **Ahead of time compilation** - Mun is compiled ahead of time (AOT), as opposed to being - interpreted or compiled just in time (JIT). By detecting errors in the code during AOT - compilation, an entire class of runtime errors is eliminated. This allows developers to stay - within the comfort of their IDE instead of having to switch between the IDE and target application - to debug runtime errors. - -- **Statically typed** - Mun resolves types at compilation time instead of at runtime, resulting in - immediate feedback when writing code and opening the door for powerful refactoring tools. - -- **First class hot-reloading** - Every aspect of Mun is designed with hot reloading in mind. Hot - reloading is the process of changing code and resources of a live application, removing the need - to start, stop and recompile an application whenever a function or value is changed. - -- **Performance** - AOT compilation combined with static typing ensure that Mun is compiled to - machine code that can be natively executed on any target platform. LLVM is used for compilation - and optimization, guaranteeing the best possible performance. Hot reloading does introduce a - slight runtime overhead, but it can be disabled for production builds to ensure the best possible +- **Ahead of time compilation** - Mun is compiled ahead of time (AOT), as + opposed to being interpreted or compiled just in time (JIT). By detecting + errors in the code during AOT compilation, an entire class of runtime errors + is eliminated. This allows developers to stay within the comfort of their IDE + instead of having to switch between the IDE and target application to debug + runtime errors. + +- **Statically typed** - Mun resolves types at compilation time instead of at + runtime, resulting in immediate feedback when writing code and opening the + door for powerful refactoring tools. + +- **First class hot-reloading** - Every aspect of Mun is designed with hot + reloading in mind. Hot reloading is the process of changing code and resources + of a live application, removing the need to start, stop and recompile an + application whenever a function or value is changed. + +- **Performance** - AOT compilation combined with static typing ensure that Mun + is compiled to machine code that can be natively executed on any target + platform. LLVM is used for compilation and optimization, guaranteeing the best + possible performance. Hot reloading does introduce a slight runtime overhead, + but it can be disabled for production builds to ensure the best possible runtime performance. -- **Cross compilation** - The Mun compiler is able to compile to all supported target platforms from - any supported compiler platform. +- **Cross compilation** - The Mun compiler is able to compile to all supported + target platforms from any supported compiler platform. -- **Powerful IDE integration** *not implemented yet* - The Mun language and compiler framework are - designed to support source code queries, allowing for powerful IDE integrations such as code - completion and refactoring tools. +- **Powerful IDE integration** *not implemented yet* - The Mun language and + compiler framework are designed to support source code queries, allowing for + powerful IDE integrations such as code completion and refactoring tools. ## Example @@ -119,14 +127,16 @@ fn on_heap() -> GC { ## Documentation -[The Mun Programming Language Book](https://docs.mun-lang.org/) +[The Mun Programming Language Book](https://docs.mun-lang.org/) is hosted on +[netlify](https://www.netlify.com/). ## Pre-Built Binaries **[NOTE] We do not provide support for milestone releases** -Download pre-built binaries of [milestone releases](https://github.com/mun-lang/mun/releases) for -macOS, Linux, and Windows (64-bit only). +Download pre-built binaries of [milestone +releases](https://github.com/mun-lang/mun/releases) for macOS, Linux, and +Windows (64-bit only). ## Building from Source @@ -141,16 +151,18 @@ rustup](https://www.rust-lang.org/tools/install). #### LLVM -Mun targets LLVM 7.1.0. Installing LLVM is platform dependant and as such can be a pain. The -following steps are how we install LLVM on [our CI +Mun targets LLVM 7.1.0. Installing LLVM is platform dependant and as such can be +a pain. The following steps are how we install LLVM on [our CI runners](.github/actions/install-llvm/index.js): -* ***nix**: Package managers of recent *nix distros can install binary versions of LLVM, e.g.: +* ***nix**: Package managers of recent *nix distros can install binary versions + of LLVM, e.g.: ```bash # Ubuntu 18.04 sudo apt install llvm-7 llvm-7-* liblld-7* ``` -* **Arch Linux** The binary version of LLVM can currently only be installed using an AUR helper, such as `yay`: +* **Arch Linux** The binary version of LLVM can currently only be installed + using an AUR helper, such as `yay`: ```bash yay -Syu lld7-headers lld7-libs-static ``` @@ -162,26 +174,29 @@ runners](.github/actions/install-llvm/index.js): cd lld7 makepkg -si ``` - When running `llvm-config`, an error can occur signalling that `/usr/lib/libtinfo.so.5` is - missing. If a newer version is present, create a symlink; e.g. `ln -s /usr/lib/libtinfo.so.6 - /usr/lib/libtinfo.so.5`), otherwise download the library. -* **macOS**: [Brew](https://brew.sh/) contains a binary distribution of LLVM 7.1.0. However, as it's - not the latest version, it won't be added to the path. We are using - [llvm-sys](https://crates.io/crates/llvm-sys) to manage version, but another option is to export - the `LLVM_SYS_70_PREFIX` variable, which will not clutter your `PATH`. To install: + When running `llvm-config`, an error can occur signalling that + `/usr/lib/libtinfo.so.5` is missing. If a newer version is present, create a + symlink; e.g. `ln -s /usr/lib/libtinfo.so.6 /usr/lib/libtinfo.so.5`), + otherwise download the library. +* **macOS**: [Brew](https://brew.sh/) contains a binary distribution of LLVM + 7.1.0. However, as it's not the latest version, it won't be added to the path. + We are using [llvm-sys](https://crates.io/crates/llvm-sys) to manage version, + but another option is to export the `LLVM_SYS_70_PREFIX` variable, which will + not clutter your `PATH`. To install: ```bash brew install llvm@7 # Export LLVM_SYS_PREFIX to not clubber PATH export LLVM_SYS_PREFIX=$(brew --prefix llvm@7) ``` -* **windows**: Binary distrubutions are available for Windows on the LLVM website, but they - do not contain a number of libraries that are required by Mun. To avoid having to go to the - trouble of compiling LLVM yourself, we created a - [repository](https://github.com/mun-lang/llvm-package-windows) that automatically compiles the - required binaries. It also contains a +* **windows**: Binary distrubutions are available for Windows on the LLVM + website, but they do not contain a number of libraries that are required by + Mun. To avoid having to go to the trouble of compiling LLVM yourself, we + created a [repository](https://github.com/mun-lang/llvm-package-windows) that + automatically compiles the required binaries. It also contains a [release](https://github.com/mun-lang/llvm-package-windows/releases/download/v7.1.0/llvm-7.1.0-windows-x64-msvc15.7z) - that you can download and extract to your machine. Once downloaded and extracted, add the `/bin` - folder to the `PATH` environment variable. + that you can download and extract to your machine. Once downloaded and + extracted, add the `/bin` folder to the `PATH` environment + variable. ### Clone source @@ -197,12 +212,66 @@ git submodule update --init --recursive cargo build --release ``` +## Building Documentation + +Building the book requires +[mdBook](https://github.com/rust-lang-nursery/mdBook), ideally version 0.3.x. To +install it, run: + +``` +$ cargo install mdbook --vers [version-num] +``` + +The Mun book uses a [custom version of +Highlight.js](https://github.com/mun-lang/highlight.js) to enable highlighting +of Mun code. The build version of Highlight.js is required by mdbook in the +`theme/` folder but it is not distributed with the source. Instead, it can be +build by invoking the build script: + +```bash +cd book +./ci/build-highlight-js +``` + +Every time you change something in the custom version of highlight.js you have +to call the above script to ensure you locally use the latest version. + +After generating the custom minified Highlight.js, to build the book, type: + +``` +$ mdbook build +``` + +The output will be in the book subdirectory. To view the book, open it in your +web browser. + +For local development use `mdbook serve` instead of `mdbook build`. This will +start a local webserver on port `3000` that serves the book and rebuilds the +content when changes are detected. + +All of the above is also combined in a single shell script that can be invoked +by simply running: + +```bash +./ci/build +``` + +To test the `rust` source code in the book, run: + +```bash +mdbook test -L path/to/target/debug/deps +``` + +For this to work, there can only be one `libmun_runtime-{HASH}.rlib` file in the +provided library path. + ## License The Mun Runtime is licensed under either of - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) at your option. diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 000000000..db0441990 --- /dev/null +++ b/book/.gitignore @@ -0,0 +1,2 @@ +/book +/theme/highlight.js diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 000000000..7528e53b1 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,9 @@ +[book] +authors = ["The Mun Team "] +language = "en" +multilingual = false +src = "src" +title = "The Mun Programming Language" + +[output.html] +additional-css = ["theme/mun.css"] diff --git a/book/ci/build b/book/ci/build new file mode 100755 index 000000000..b2d02dc39 --- /dev/null +++ b/book/ci/build @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euo pipefail + +pushd ./book + + # First build our custom highlight.js + ./ci/build-highlight-js + + # Check if mdbook is installed, otherwise download the binaries + mdbook="mdbook" + if ! [ -x "$(command -v $mdbook)" ]; then + echo "Installing mdbook.." + curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.3.1/mdbook-v0.3.1-x86_64-unknown-linux-gnu.tar.gz | tar zxv + mdbook="./mdbook" + fi + + # Actually build the book + echo 'Building book..' + $mdbook build + +popd \ No newline at end of file diff --git a/book/ci/build-highlight-js b/book/ci/build-highlight-js new file mode 100755 index 000000000..2b15b46ff --- /dev/null +++ b/book/ci/build-highlight-js @@ -0,0 +1,19 @@ +#!/bin/bash +set -euo pipefail + +main() { + # prepare the git submodule, if it hasn't been already + git submodule init + + # build the minified highlight.js source + pushd vendor/highlight.js + npm install + node tools/build.js rust bash mun cpp c typescript + popd + + # copy the minified sources to the theme directory + mkdir -p theme + cp vendor/highlight.js/build/highlight.pack.js theme/highlight.js +} + +main diff --git a/book/listings/ch01-getting-started/listing01.mun b/book/listings/ch01-getting-started/listing01.mun new file mode 100644 index 000000000..1810a20f3 --- /dev/null +++ b/book/listings/ch01-getting-started/listing01.mun @@ -0,0 +1,16 @@ +pub fn fibonacci_n() -> i64 { + let n = arg(); + fibonacci(n); +} + +fn arg() -> i64 { + 5 +} + +fn fibonacci(n: i64) -> i64 { + if n <= 1 { + n + } else { + fibonacci(n - 1) + fibonacci(n - 2) + } +} diff --git a/book/listings/ch01-getting-started/listing02.mun b/book/listings/ch01-getting-started/listing02.mun new file mode 100644 index 000000000..7e8a407dd --- /dev/null +++ b/book/listings/ch01-getting-started/listing02.mun @@ -0,0 +1,11 @@ +pub fn arg() -> i64 { + 5 +} + +pub fn fibonacci(n: i64) -> i64 { + if n <= 1 { + n + } else { + fibonacci(n - 1) + fibonacci(n - 2) + } +} diff --git a/book/listings/ch01-getting-started/listing03.cpp b/book/listings/ch01-getting-started/listing03.cpp new file mode 100644 index 000000000..58f7b0e02 --- /dev/null +++ b/book/listings/ch01-getting-started/listing03.cpp @@ -0,0 +1,24 @@ +#include + +#include "mun/runtime.h" + +int main() { + if (argc < 2) { + return 1; + } + + auto lib_path = argv[1]; + if (auto runtime = mun::make_runtime(lib_path)) { + while (true) { + auto arg = mun::invoke_fn(*runtime, "arg").wait(); + auto result = + mun::invoke_fn(*runtime, "fibonacci", arg).wait(); + std::cout << "fibonacci(" << std::to_string(arg) << ") = " << result + << std::endl; + + runtime->update(); + } + } + + return 2; +} diff --git a/book/listings/ch01-getting-started/listing04.rs b/book/listings/ch01-getting-started/listing04.rs new file mode 100644 index 000000000..74463cf84 --- /dev/null +++ b/book/listings/ch01-getting-started/listing04.rs @@ -0,0 +1,17 @@ +use mun_runtime::{invoke_fn, RetryResultExt, RuntimeBuilder}; +use std::{cell::RefCell, env, rc::Rc}; + +fn main() { + let lib_path = env::args().nth(1).expect("Expected path to a Mun library."); + + let mut runtime = RuntimeBuilder::new(lib_path) + .spawn() + .expect("Failed to spawn Runtime"); + + loop { + let arg: i64 = invoke_fn!(runtime, "arg").wait(); + let result: i64 = invoke_fn!(runtime, "fibonacci").wait(); + println!("fibonacci({}) = {}", arg, result); + runtime.borrow_mut().update(); + } +} diff --git a/book/listings/ch02-basic-concepts/listing01.mun b/book/listings/ch02-basic-concepts/listing01.mun new file mode 100644 index 000000000..1bde7f33d --- /dev/null +++ b/book/listings/ch02-basic-concepts/listing01.mun @@ -0,0 +1,5 @@ +extern fn random() -> i64; + +pub fn random_bool() -> bool { + random() % 2 == 0 +} diff --git a/book/listings/ch02-basic-concepts/listing02.rs b/book/listings/ch02-basic-concepts/listing02.rs new file mode 100644 index 000000000..3f04c1161 --- /dev/null +++ b/book/listings/ch02-basic-concepts/listing02.rs @@ -0,0 +1,11 @@ +use mun_runtime::{invoke_fn, RetryResultExt, RuntimeBuilder}; +use std::{cell::RefCell, rc::Rc}; + +fn main() { + let runtime = RuntimeBuilder::new("main.munlib") + .spawn() + .expect("Failed to spawn Runtime"); + + let result: bool = invoke_fn!(runtime, "random_bool").unwrap(); + println!("random bool: {}", result); +} diff --git a/book/listings/ch02-basic-concepts/listing03.rs b/book/listings/ch02-basic-concepts/listing03.rs new file mode 100644 index 000000000..71924e278 --- /dev/null +++ b/book/listings/ch02-basic-concepts/listing03.rs @@ -0,0 +1,18 @@ +use mun_runtime::{invoke_fn, RetryResultExt, RuntimeBuilder}; +use std::{cell::RefCell, rc::Rc}; + +extern "C" fn random() -> i64 { + let result = std::time::Instant::now().elapsed().subsec_nanos() as i64; + println!("random: {}", result); + result +} + +fn main() { + let runtime = RuntimeBuilder::new("main.munlib") + .insert_fn("random", random as extern "C" fn() -> i64) + .spawn() + .expect("Failed to spawn Runtime"); + + let result: bool = invoke_fn!(runtime, "random_bool").unwrap(); + println!("random_bool: {}", result); +} diff --git a/book/listings/ch03-structs/listing01.mun b/book/listings/ch03-structs/listing01.mun new file mode 100644 index 000000000..b170fe72c --- /dev/null +++ b/book/listings/ch03-structs/listing01.mun @@ -0,0 +1,4 @@ +struct Vector2 { + x: float, + y: float, +} diff --git a/book/listings/ch03-structs/listing02.mun b/book/listings/ch03-structs/listing02.mun new file mode 100644 index 000000000..204910c17 --- /dev/null +++ b/book/listings/ch03-structs/listing02.mun @@ -0,0 +1 @@ +struct Vector3(float, float, float) diff --git a/book/listings/ch03-structs/listing03.mun b/book/listings/ch03-structs/listing03.mun new file mode 100644 index 000000000..f5985c393 --- /dev/null +++ b/book/listings/ch03-structs/listing03.mun @@ -0,0 +1,8 @@ +# struct Vector2 { +# x: float, +# y: float, +# } +let xy = Vector2 { + x: 1.0, + y: -1.0, +}; diff --git a/book/listings/ch03-structs/listing04.mun b/book/listings/ch03-structs/listing04.mun new file mode 100644 index 000000000..556154ead --- /dev/null +++ b/book/listings/ch03-structs/listing04.mun @@ -0,0 +1,2 @@ +# struct Vector3(float, float, float) +let xyz = Vector3(-1.0, 0.0, 1.0); diff --git a/book/listings/ch03-structs/listing05.mun b/book/listings/ch03-structs/listing05.mun new file mode 100644 index 000000000..a26152478 --- /dev/null +++ b/book/listings/ch03-structs/listing05.mun @@ -0,0 +1,7 @@ +# struct Vector2 { +# x: float, +# y: float, +# } +pub fn vector2_new(x: float, y: float) -> Vector2 { + Vector2 { x, y } +} diff --git a/book/listings/ch03-structs/listing06.mun b/book/listings/ch03-structs/listing06.mun new file mode 100644 index 000000000..30a0cbec0 --- /dev/null +++ b/book/listings/ch03-structs/listing06.mun @@ -0,0 +1,9 @@ +# struct Vector2 { +# x: float, +# y: float, +# } +pub fn vector2_add(lhs: Vector2, rhs: Vector2) -> Vector2 { + lhs.x += rhs.x; + lhs.y += rhs.y; + lhs +} diff --git a/book/listings/ch03-structs/listing07.mun b/book/listings/ch03-structs/listing07.mun new file mode 100644 index 000000000..f6e55cb75 --- /dev/null +++ b/book/listings/ch03-structs/listing07.mun @@ -0,0 +1,7 @@ +# struct Vector3(float, float, float) +pub fn vector3_add(lhs: Vector3, rhs: Vector3) -> Vector3 { + lhs.0 += rhs.0; + lhs.1 += rhs.1; + lhs.2 += rhs.2; + lhs +} diff --git a/book/listings/ch03-structs/listing08.mun b/book/listings/ch03-structs/listing08.mun new file mode 100644 index 000000000..9a3b04e80 --- /dev/null +++ b/book/listings/ch03-structs/listing08.mun @@ -0,0 +1 @@ +struct Unit; diff --git a/book/listings/ch03-structs/listing09.mun b/book/listings/ch03-structs/listing09.mun new file mode 100644 index 000000000..b170fe72c --- /dev/null +++ b/book/listings/ch03-structs/listing09.mun @@ -0,0 +1,4 @@ +struct Vector2 { + x: float, + y: float, +} diff --git a/book/listings/ch03-structs/listing10.mun b/book/listings/ch03-structs/listing10.mun new file mode 100644 index 000000000..2e3c55992 --- /dev/null +++ b/book/listings/ch03-structs/listing10.mun @@ -0,0 +1,4 @@ +struct(value) Vector2 { + x: float, + y: float, +} diff --git a/book/listings/ch03-structs/listing11.rs b/book/listings/ch03-structs/listing11.rs new file mode 100644 index 000000000..8ab2c2e3f --- /dev/null +++ b/book/listings/ch03-structs/listing11.rs @@ -0,0 +1,15 @@ +# extern crate mun_runtime; +use mun_runtime::{invoke_fn, RuntimeBuilder, StructRef}; +use std::{cell::RefCell, env, rc::Rc}; + +fn main() { + let lib_path = env::args().nth(1).expect("Expected path to a Mun library."); + + let runtime = RuntimeBuilder::new(lib_path) + .spawn() + .expect("Failed to spawn Runtime"); + + let a: StructRef = invoke_fn!(runtime, "vector2_new", -1.0f64, 1.0f64).unwrap(); + let b: StructRef = invoke_fn!(runtime, "vector2_new", 1.0f64, -1.0f64).unwrap(); + let added: StructRef = invoke_fn!(runtime, "vector2_add", a, b).unwrap(); +} diff --git a/book/listings/ch03-structs/listing12.rs b/book/listings/ch03-structs/listing12.rs new file mode 100644 index 000000000..5e38446ff --- /dev/null +++ b/book/listings/ch03-structs/listing12.rs @@ -0,0 +1,17 @@ +# extern crate mun_runtime; +# use mun_runtime::{invoke_fn, RuntimeBuilder, StructRef}; +# use std::{cell::RefCell, env, rc::Rc}; +# +# fn main() { +# let lib_path = env::args().nth(1).expect("Expected path to a Mun library."); +# +# let mut runtime = +# RuntimeBuilder::new(lib_path) +# .spawn() +# .expect("Failed to spawn Runtime"); +# + let mut xy: StructRef = invoke_fn!(runtime, "vector2_new", -1.0f64, 1.0f64).unwrap(); + let x: f64 = xy.get("x").unwrap(); + xy.set("x", x * x).unwrap(); + let y = xy.replace("y", -1.0f64).unwrap(); +# } diff --git a/book/listings/ch03-structs/listing13.mun b/book/listings/ch03-structs/listing13.mun new file mode 100644 index 000000000..2310358f3 --- /dev/null +++ b/book/listings/ch03-structs/listing13.mun @@ -0,0 +1,11 @@ +extern fn log_f32(value: f32); + +struct SimContext; + +pub fn new_sim() -> SimContext { + SimContext +} + +pub fn sim_update(ctx: SimContext, elapsed_secs: f32) { + log_f32(elapsed_secs); +} diff --git a/book/listings/ch03-structs/listing14.rs b/book/listings/ch03-structs/listing14.rs new file mode 100644 index 000000000..500055ef2 --- /dev/null +++ b/book/listings/ch03-structs/listing14.rs @@ -0,0 +1,37 @@ +# extern crate mun_runtime; +use mun_runtime::{invoke_fn, RetryResultExt, RuntimeBuilder, StructRef}; +use std::{env, time}; + +extern "C" fn log_f32(value: f32) { + println!("{}", value); +} + +fn main() { + let lib_dir = env::args().nth(1).expect("Expected path to a Mun library."); + + let runtime = RuntimeBuilder::new(lib_dir) + .insert_fn("log_f32", log_f32 as extern "C" fn(f32)) + .spawn() + .expect("Failed to spawn Runtime"); + + let ctx: StructRef = invoke_fn!(runtime, "new_sim").wait(); + + let mut previous = time::Instant::now(); + const FRAME_TIME: time::Duration = time::Duration::from_millis(40); + loop { + let now = time::Instant::now(); + let elapsed = now.duration_since(previous); + + let elapsed_secs = if elapsed < FRAME_TIME { + std::thread::sleep(FRAME_TIME - elapsed); + FRAME_TIME.as_secs_f32() + } else { + elapsed.as_secs_f32() + }; + + let _: () = invoke_fn!(runtime, "sim_update", ctx.clone(), elapsed_secs).wait(); + previous = now; + + runtime.borrow_mut().update(); + } +} diff --git a/book/listings/ch03-structs/listing15.mun b/book/listings/ch03-structs/listing15.mun new file mode 100644 index 000000000..6d9435b36 --- /dev/null +++ b/book/listings/ch03-structs/listing15.mun @@ -0,0 +1,45 @@ +extern fn log_f32(value: f32); + +struct SimContext { + sphere: Sphere, + water: Water, + gravity: f32, +} + +struct Sphere { + radius: f32, + density: f32, + height: f32, + velocity: f32, +} + +struct Water { + density: f32, +} + +pub fn new_sim() -> SimContext { + SimContext { + sphere: new_sphere(), + water: new_water(), + gravity: 9.81, + } +} + +fn new_sphere() -> Sphere { + Sphere { + radius: 1.0, + density: 250.0, + height: 1.0, + velocity: 0.0, + } +} + +fn new_water() -> Water { + Water { + density: 1000.0, + } +} + +pub fn sim_update(ctx: SimContext, elapsed_secs: f32) { + log_f32(ctx.gravity); +} diff --git a/book/listings/ch03-structs/listing16.mun b/book/listings/ch03-structs/listing16.mun new file mode 100644 index 000000000..4629ceabe --- /dev/null +++ b/book/listings/ch03-structs/listing16.mun @@ -0,0 +1,58 @@ +extern fn log_f32(value: f32); + +struct SimContext { + sphere: Sphere, + water: Water, + gravity: f32, + token: u32, +} + +struct Sphere { + radius: f32, + density: f32, + height: f32, + velocity: f32, +} + +struct Water { + density: f32, +} + +pub fn new_sim() -> SimContext { + SimContext { + sphere: new_sphere(), + water: new_water(), + gravity: 9.81, + token: 0, + } +} + +fn new_sphere() -> Sphere { + Sphere { + radius: 1.0, + density: 250.0, + height: 1.0, + velocity: 0.0, + } +} + +fn new_water() -> Water { + Water { + density: 1000.0, + } +} + +fn hot_reload_token() -> u32 { + 1 +} + +pub fn sim_update(ctx: SimContext, elapsed_secs: f32) { + if ctx.token != hot_reload_token() { + let default = new_sim(); + ctx.sphere = default.sphere; + ctx.water = default.water; + ctx.gravity = default.gravity; + ctx.token = hot_reload_token(); + } + log_f32(ctx.gravity); +} diff --git a/book/listings/ch03-structs/listing17.mun b/book/listings/ch03-structs/listing17.mun new file mode 100644 index 000000000..ca7c028ea --- /dev/null +++ b/book/listings/ch03-structs/listing17.mun @@ -0,0 +1,106 @@ +extern fn log_f32(value: f32); + +struct SimContext { + sphere: Sphere, + water: Water, + gravity: f32, + token: u32, +} + +struct Sphere { + radius: f32, + mass: f32, // density: f32, + height: f32, + velocity: f32, +} + +struct Water { + density: f32, +} + +pub fn new_sim() -> SimContext { + SimContext { + sphere: new_sphere(), + water: new_water(), + gravity: 9.81, + token: 0, + } +} + +fn new_sphere() -> Sphere { + let radius = 1.0; + let density = 250.0; + + let volume = calc_sphere_volume(radius); + let mass = density * volume; + + Sphere { + radius, + mass, + height: 1.0, + velocity: 0.0, + } +} + +fn new_water() -> Water { + Water { + density: 1000.0, + } +} + +fn calc_submerged_ratio(s: Sphere) -> f32 { + let bottom = s.height - s.radius; + let diameter = 2.0 * s.radius; + if bottom >= 0.0 { + 0.0 + } else if bottom <= -diameter { + 1.0 + } else { + -bottom / diameter + } +} + +fn calc_sphere_volume(radius: f32) -> f32 { + let pi = 3.1415926535897; + let r = radius; + + 3.0/4.0 * pi * r * r * r +} + +fn calc_buoyancy_force(s: Sphere, w: Water, gravity: f32, submerged_ratio: f32) -> f32 { + let volume = calc_sphere_volume(s.radius); + volume * submerged_ratio * w.density * gravity +} + +fn hot_reload_token() -> u32 { + 1 +} + +pub fn sim_update(ctx: SimContext, elapsed_secs: f32) { + if ctx.token != hot_reload_token() { + let default = new_sphere(); + ctx.sphere = default; + ctx.token = hot_reload_token(); + } + + let submerged_ratio = calc_submerged_ratio(ctx.sphere); + if submerged_ratio > 0.0 { + // Accelerate using buoyancy + let buoyancy_force = calc_buoyancy_force( + ctx.sphere, + ctx.water, + ctx.gravity, + submerged_ratio + ); + let buoyancy_acc = buoyancy_force / ctx.sphere.mass; + ctx.sphere.velocity += buoyancy_acc * elapsed_secs; + } + + // Accelerate using gravity + ctx.sphere.velocity -= ctx.gravity * elapsed_secs; + + // Apply velocity + ctx.sphere.height += ctx.sphere.velocity * elapsed_secs; + + log_f32(ctx.sphere.height); +} diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 000000000..25c354751 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,21 @@ +# The Mun Programming Language + +[Introduction](ch00-00-introduction.md) +[Case studies](ch00-01-case-studio-abbey-games.md) + +- [Getting started](ch01-00-getting-started.md) + - [Installation](ch01-01-installation.md) + - [Hello, fibonacci?](ch01-02-hello-fibonacci.md) + - [Hello, hot reloading!](ch01-03-hello-hot-reloading.md) + +- [Basic concepts](ch02-00-basic-concepts.md) + - [Values and types](ch02-01-values-and-types.md) + - [Functions](ch02-02-functions.md) + - [Control flow](ch02-03-control-flow.md) + - [Extern functions](ch02-04-extern-fn.md) + +- [Structs](ch03-00-structs.md) + - [Records vs Tuples](ch03-01-records-vs-tuples.md) + - [Struct Memory Kind](ch03-02-struct-memory-kind.md) + - [Marshalling](ch03-03-marshalling.md) + - [Hot Reloading Structs](ch03-04-hot-reloading-structs.md) diff --git a/book/src/ch00-00-introduction.md b/book/src/ch00-00-introduction.md new file mode 100644 index 000000000..1109440a5 --- /dev/null +++ b/book/src/ch00-00-introduction.md @@ -0,0 +1,41 @@ +# Introduction + +> Note: Mun & this book are currently under active development, any and all +> content of this book is not final and may still change. + +*Mun* is an embeddable scripting language designed for developer productivity. + +* **Ahead of time compilation** + Mun is compiled ahead of time (AOT), as opposed to being interpreted or + compiled just in time (JIT). By detecting errors in the code during AOT + compilation, an entire class of runtime errors is eliminated. This allows + developers to stay within the comfort of their IDE instead of having to switch + between the IDE and target application to debug runtime errors. + +* **Statically typed** + Mun resolves types at compilation time instead of at runtime, resulting in + immediate feedback when writing code and opening the door for powerful + refactoring tools. + +* **First class hot-reloading** + Every aspect of Mun is designed with hot reloading in mind. Hot reloading is + the process of changing code and resources of a live application, removing the + need to start, stop and recompile an application whenever a function or value + is changed. + +* **Performance** + AOT compilation combined with static typing ensure that Mun is compiled to + machine code that can be natively executed on any target platform. LLVM is + used for compilation and optimization, guaranteeing the best possible + performance. Hot reloading does introduce a slight runtime overhead, but it + can be disabled for production builds to ensure the best possible runtime + performance. + +* **Cross compilation** + The Mun compiler is able to compile to all supported target platforms from any + supported compiler platform. + +* **Powerful IDE integration** + The Mun language and compiler framework are designed to support source code + queries, allowing for powerful IDE integrations such as code completion and + refactoring tools. diff --git a/book/src/ch00-01-case-studio-abbey-games.md b/book/src/ch00-01-case-studio-abbey-games.md new file mode 100644 index 000000000..54aa795d4 --- /dev/null +++ b/book/src/ch00-01-case-studio-abbey-games.md @@ -0,0 +1,37 @@ +## Case Studies + +A collection of case studies that inspired the design choices made in Mun. + +### Abbey Games + +Abbey Games uses Lua as its main gameplay programming language because of Lua's +ability to hot reload code. This allows for rapid iteration of game code, +enabling gameplay programmers and designers to quickly test and tweak systems +and content. Lua is a dynamically typed, JIT compiled language. Although this +has some definite advantages, it also introduces a lot of problems with bigger +codebases. + +Changes in Lua code can have large implications throughout the entire codebase +and since we cannot oversee the entire codebase at all times runtime errors are +bound to occur. Runtime errors are nasty beasts because they can pop up after a +long period of time and after work on the offending piece of code has already +finished. They are also often detected by someone different from the person who +worked on the code. This causes great frustration and delay, let alone when the +runtime error is detected by a user of the software. + +Lua amplifies this issue due to its dynamic and flexible nature. *It would be +great if we could turn some of these runtime errors into compile time errors.* +That way programmers are notified of errors way before someone else runs into +them. The risk of causing implicit runtime errors causes programmers to distrust +their refactoring tools. This in turn reduces the likelihood of programmers +refactoring their code. + +Even though Lua offers immense flexibility, we noticed that certain opinionated +patterns recur a lot and as such have become standard practice. Introducing +these practices assists us in daily development a lot, but requires more code +and complexity than desirable. Having syntactic sugar would greatly help reduce +complexity in our code base, but would also introduce *magic* or custom keywords +that are foreign to both new developers and IDE's. + +Rapid iteration is key to prototyping game concepts and features. *Proper +IDE-integration of a scripting language gives a huge boost to productivity.* diff --git a/book/src/ch01-00-getting-started.md b/book/src/ch01-00-getting-started.md new file mode 100644 index 000000000..6af27b925 --- /dev/null +++ b/book/src/ch01-00-getting-started.md @@ -0,0 +1,5 @@ +# Getting Started + +Let's start your Mun journey by installing the Mun CLI and creating a +simple Mun library. We'll then show you how to make it hot reloadable by +embedding it into an application. diff --git a/book/src/ch01-01-installation.md b/book/src/ch01-01-installation.md new file mode 100644 index 000000000..73e80c755 --- /dev/null +++ b/book/src/ch01-01-installation.md @@ -0,0 +1,9 @@ +## Installation + +First we need to install the Mun CLI (command-line interface), which acts as an +all-in-one tool for Mun application development. Pre-built binaries are +[available](https://github.com/mun-lang/mun/releases) for macOS, Linux, and +Windows (64-bit only). Download and extract the binaries to a location of your +preference. + +You are now ready to write your first Mun code! diff --git a/book/src/ch01-02-hello-fibonacci.md b/book/src/ch01-02-hello-fibonacci.md new file mode 100644 index 000000000..4ced272b7 --- /dev/null +++ b/book/src/ch01-02-hello-fibonacci.md @@ -0,0 +1,55 @@ +## Hello, fibonacci? + +Most programming languages start off with a "Hello, world!" example, but not +Mun. Mun is designed around the concept of hot reloading. Our philosophy is to +only add new language constructs when those can be hot reloaded. Since the first +building blocks of Mun were native types and functions our divergent example has +become fibonacci, hence "Hello, fibonacci?". + +### Creating a Project Directory + +The Mun compiler is agnostic to the location of a project directory, as long as +all source files are in the same place. Let's open a terminal to create our +first project directory: + +```bash +mkdir hello_fibonacci +cd hello_fibonacci +``` + +### Writing and Running a Mun Library + +Next, make a new source file and call it *hello_fibonacci.mun*. Mun files always +end with the *.mun extension*. If your file name consists of multiple words, +separate them using underscores. + +Open up the new source file and enter the code in Listing 1-1. + +Filename: hello_fibonacci.mun + + +```mun +{{#include ../listings/ch01-getting-started/listing01.mun}} +``` + +Listing 1-1: A function that calculates a fibonacci number + +Save the file and go back to your terminal window. You are now ready to compile +your first Mun library. Enter the following command to compile the file: + +```bash +mun build hello_fibonacci.mun +``` + +Contrary to many other languages, Mun doesn't support standalone applications, +instead it is shipped in the form of Mun libraries - recognisable by their +`*.munlib` extension. That's why Mun comes with a command-line interface (CLI) +that can both compile and run Mun libraries. To run a Mun library, enter the +following command: + +```bash +mun start hello_fibonacci.munlib --entry fibonacci_n +``` + +The result of `fibonacci_n` (i.e. `5`) should now appear in your terminal. +Congratulations! You just successfully created and ran your first Mun library. diff --git a/book/src/ch01-03-hello-hot-reloading.md b/book/src/ch01-03-hello-hot-reloading.md new file mode 100644 index 000000000..ffbddebd0 --- /dev/null +++ b/book/src/ch01-03-hello-hot-reloading.md @@ -0,0 +1,74 @@ +## Hello, Hot Reloading! + +Mun distinguishes itself from other languages by its inherent hot reloading +capabilities. The following example illustrates how you can create a hot +reloadable application by slightly modifying the [Hello, +fibonacci?](ch01-01-hello-fibonacci.md) example. In Listing 1-2, the +`fibonacci_n` function has been removed and the `pub` keyword has been added to +both `args` and `fibonacci`. + +Filename: hello_fibonacci.mun + + +```mun +{{#include ../listings/ch01-getting-started/listing02.mun}} +``` + +Listing 1-2: A function that calculates a fibonacci number + +Apart from running Mun libraries from the command-line interface, a common use +case is embedding them in other programming languages. + +### Mun embedded in C++ + +Mun [exposes](https://github.com/mun-lang/runtime-ffi) a C API and complementary +C++ bindings for the Mun Runtime. Listing 1-3 shows a C++ application that +constructs a Mun Runtime for the `hello_fibonacci` library and continuously +invokes the `fibonacci` function and outputs its result. + +Filename: main.cc + +```cpp +{{#include ../listings/ch01-getting-started/listing03.cpp}} +``` + +Listing 1-3: Hello, Fibonacci? embedded in a C++ application + +### Mun embedded in Rust + +As the Mun Runtime is written in Rust, it can be easily embedded in Rust +applications by adding the `mun_runtime` crate as a dependency. Listing 1-4 +illustrates a simple Rust application that builds a Mun Runtime and continuously +invokes the `fibonacci` function and prints its output. + +Filename: main.rs + +```rust,no_run,noplaypen +# extern crate mun_runtime; +{{#include ../listings/ch01-getting-started/listing04.rs}} +``` + +Listing 1-4: Hello, Fibonacci? embedded in a Rust application + +### Hot Reloading + +The prior examples both update the runtime every loop cycle. In the background, +this detects recompiled code and reloads the resulting Mun libraries. + +To ensure that the Mun compiler recompiles our code every time the +*hello_fibonacci.mun* source file from Listing 1-2 changes, the `--watch` +argument must be added: + +```bash +mun build hello_fibonacci.mun --watch +``` + +When saved, changes in the source file will automatically take effect in the +running example application. E.g. change the return value of the `arg` function +and the application will log the corresponding Fibonacci number. + +Some changes, such as a type mismatch between the compiled application and the +hot reloadable library, can lead to runtime errors. When these occur, the +runtime will log the error and halt until an update to the source code arrives. + +That's it! Now you are ready to start developing hot reloadable Mun libraries. diff --git a/book/src/ch02-00-basic-concepts.md b/book/src/ch02-00-basic-concepts.md new file mode 100644 index 000000000..6761ac0bf --- /dev/null +++ b/book/src/ch02-00-basic-concepts.md @@ -0,0 +1,3 @@ +# Basic Concepts + +This section describes the basic concepts of the Mun programming language. diff --git a/book/src/ch02-01-values-and-types.md b/book/src/ch02-01-values-and-types.md new file mode 100644 index 000000000..7f5d9895d --- /dev/null +++ b/book/src/ch02-01-values-and-types.md @@ -0,0 +1,241 @@ +## Values and types + +Mun is a statically typed language, which helps to detect type-related errors at +compile-time. A type error is an invalid operation on a given type, such as an +integer divided by a string, trying to access a field that doesn't exist, or +calling a function with the wrong number of arguments. + +Some languages require a programmer to explicitly annotate syntactic constructs +with type information: + +```c++ +int foo = 3 + 4; +``` + +However, often variable types can be inferred by their usage. Mun uses type +inferencing to determine variable types at compile time. However, you are still +forced to explicitly annotate variables in a few locations to ensure a contract +between interdependent code. + +```mun +fn bar(a: i32) -> i32 { + let foo = 3 + a; + foo +} +``` + +Here, the parameter `a` and the return type must be annotated because it +solidifies the signature of the function. The type of `foo` can be inferred +through its usage. + +> NOTE: Although the above works, as of version 0.2, Mun is not yet very good at +>type inferencing. This will be improved in the future. + +### Integer types + +An integer is a number without a fractional component. Table 3-1 shows the +built-in integer types in Mun. Each variant can be either signed or unsigned +and has an explicit size. *Signed* and *unsigned* refer to whether it is +necessary to have a sign that indicates the possibility for the number to be +negative or positive. + + +| Length | Signed | Unsigned | +|----------|---------|----------| +| 8-bit | `i8` | `u8` | +| 16-bit | `i16` | `u16` | +| 32-bit | `i32` | `u32` | +| 64-bit | `i64` | `u64` | +| 128-bit | `i128` | `u128` | +| arch | `isize` | `usize` | + +Table 2-1: Integer Types in Mun + +Signed integer types start with `i`, unsigned integer types with `u`, followed +by the number of bits that the integer value takes up. Each signed variant can +store numbers from -(2n - 1) to 2n - 1 - 1 inclusive, +where *n* is the number of bits that variant uses. Unsigned variants can store +numbers from 0 to 2n - 1. By default Mun uses 32-bit signed +integers. + +The size of the `isize` and `usize` types depend on the target architecture. On +64-bit architectures,`isize` and `usize` types are 64 bits large, whereas on 32-bit +architectures they are 32 bits in size. + +### Floating-Point Types + +Real (or *floating-point*) numbers (i.e. numbers with a fractional component) +are represented according to the IEEE-754 standard. The `f32` type is a +single-precision float of 32 bits, and the `f64` type has double precision - +requiring 64 bits. + +```mun +fn main() { + let f = 3.0; // f64 +} +``` + + +### The Boolean Type + +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() { + let t = true; + + let f: bool = false; // with explicit type annotation +} +``` + +### Literals + +There are three types of literals in Mun: integer, floating-point and boolean +literals. + +A boolean literal is either `true` or `false`. + +An integer literal is a number without a decimal separator (`.`). It can be +written as a decimal, hexadecimal, octal or binary value. These are all +examples of valid literals: + +```mun +let a = 367; +let b = 0xbeaf; +let c = 0o76532; +let d = 0b0101011; +``` + +A floating-point literal comes in two forms: + +* A decimal number followed by a dot which is optionally followed by another + decimal literal and an *optional* exponent. +* A decimal number followed by an exponent. + +Examples of valid floating-point literals are: + +```mun +let a: f64 = 3.1415; +let b: f64 = 3.; +let c: f64 = 314.1592654e-2; +``` + +#### Separators + +Both integer and floating-point literals can contain underscores (`_`) to +visually separate numbers from one another. They do not have any semantic +significance but can be useful to the eye. + +```mun +let a: i64 = 1_000_000; +let b: f64 = 1_000.12; +``` + +#### Type suffix + +Integer and floating-point literals may be followed by a type suffix to +explicitly specify the type of the literal. + +| Literal type | Suffixes | +|--------------|----------| +|Integer |`u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `i128`, `u128`, `usize`, `isize`, `f32`, `f64` | +| Floating-point | `f32`, `f64` | + +Table 2-2: Literal suffixes in Mun + +Note that integer literals can have floating-point suffixes. This is not the +case the other way around. + +```mun +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 +let a: u8 = 1123123124124_u8; // literal out of range for `u8` +``` + +### Numeric operations + +Mun supports all basic mathematical operations for number types: addition, +subtraction, division, multiplication, and remainder. + +```mun +fn main() { + // addition + let a = 10 + 5; + + // subtraction + let b = 10 - 4; + + // multiplication + let c = 5 * 10; + + // division + let d = 25 / 5; + + // remainder + let e = 21 % 5; +} +``` + +Each expression in these statements uses a mathematical operator and evaluates +to a single value. This is valid as long as both sides of the operator have the +same type. + +Unary operators are also supported: + +```mun +fn main() { + let a = 4; + // negate + let b = -a; + + let c = true; + // not + let d = !c; +} +``` + +### Shadowing + +Redeclaring a variable by the same name with a `let` statement is valid and will +shadow any previous declaration in the same block. This is often useful if you +want to change the type of a variable. + +```mun +let a: i32 = 3; +let a: f64 = 5.0; +``` + +### Use before initialization + +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 +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 +the above could have better been written by *returning* a value from the +`if`/`else` block instead of assigning to `a`. This avoids the use of an +uninitialized value. + +```mun +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 new file mode 100644 index 000000000..e84c7a729 --- /dev/null +++ b/book/src/ch02-02-functions.md @@ -0,0 +1,154 @@ +## Functions + +Together with `struct`, functions are the core building blocks of hot reloading +in Mun. Throughout the documentation you've already seen a lot of examples of +the `fn` keyword, which is used to define a function. + +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() { + another_function(); +} + +fn another_function() { + +} +``` + +Function definitions start with an optional access modifier (`pub`), followed +by the `fn` keyword, a name, an argument list enclosed by parentheses, an +optional return type specifier, and finally a body. + +Marking a function with the `pub` keyword allows you to publicly expose +that function, for usage in other modules or when hot reloading. Otherwise +the function will only be accessible from the current source file. + +### Function Access Modifier + +Marking a function with the `pub` keyword allows you to use it from outside of +the module it is defined in. + +```mun +// This function is not accessible outside of this code +fn foo() { + // ... +} +// This function is accessible from anywhere. +pub fn bar() { + // Because `bar` and `foo` are in the same file, this call is valid. + foo() +}``` + +When you want to interface from your host language (C++, Rust, etc.) with Mun, +you can only access `pub` functions. These functions are hot reloaded by the +runtime when they **or** functions they call have been modified. + +### Function Arguments + +Functions can have an argument list. Arguments are special variables that are +part of the function signature. Unlike regular variables you have to explicitly +specify the type of the arguments. This is a deliberate decision, as type +annotations in function definitions usually mean that the compiler can derive +types almost everywhere in your code. It also ensures that you as a developer +define a *contract* of what your function can accept as its input. + +The following is a rewritten version of `another_function` that shows what an +argument looks like: + +```mun +fn main() { + another_function(3); +} + +fn another_function(x: i32) { +} +``` + +The declaration of `another_function` specifies an argument `x` of the `i32` +type. When you want a function to use multiple arguments, separate them with +commas: + +```mun +fn main() { + another_function(3, 4); +} + +fn another_function(x: i32, y: i32) { +} +``` + +### Function Bodies + +Function bodies are made up of a sequence of statements and expressions. +*Statements* are instructions that perform some action and do not return any +value. *Expressions* evaluate to a result value. + +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() { + let y = 6; +} +``` + +Statements do not return values and can therefore not be assigned to another +variable. + +Expressions do evaluate to something. Consider a simple math operation `5 + 6`, +which is an expression that evaluates to `11`. Expressions can be part of a +statement, as can be seen in the example above. The expression `6` is assigned +to the variable `y`. Calling a function is also an expression. + +The body of a function is just a block. In Mun, not just bodies, but all blocks +evaluate to the last expression in them. Blocks can therefore also be used on +the right hand side of a `let` statement. + +```mun +fn foo() -> i32 { + let bar = { + let b = 3; + b + 3 + }; + // `bar` has a value 6 + bar + 3 +}``` +### Returning Values from Functions + +Functions can return values to the code that calls them. We don't name return +values in the function declaration, but we do declare their type after an arrow +(`->`). In Mun, a function implicitly returns the value of the last expression +in the function body. You can however return early from a function by using the +`return` keyword and specifying a value. + +```mun +fn five() -> i32 { + 5 +} + +fn main() { + let x = five(); +} +``` + +There are no function calls or statements in the body of the `five` function, +just the expression `5`. This is perfectly valid Mun. Note that the return type +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 +fn foo() -> i32 { + let bar = { + let b = 3; + return b + 3; + }; + + // This code will never be executed + return bar + 3; +} +``` diff --git a/book/src/ch02-03-control-flow.md b/book/src/ch02-03-control-flow.md new file mode 100644 index 000000000..4e11e6dbf --- /dev/null +++ b/book/src/ch02-03-control-flow.md @@ -0,0 +1,136 @@ +## Control flow + +Executing or repeating a block of code only under specific conditions are common +constructs that allow developers to control the flow of execution. Mun provides + `if`/`else` expressions and loops. + +### `if` expressions + +An `if` expression allows you to branch your code depending on conditions. + +```mun +fn main() { + let number = 3; + + if number < 5 { + number = 4; + } else { + number = 6; + } +} +``` + +All `if` expressions start with the keyword `if`, followed by a condition. As +opposed to many C-like languages, Mun omits parentheses around the condition. +Only when the condition is true - in the example, whether the `number` variable +is less than 5 - the consecutive code block (or *arm*) is executed. + +Optionally, an `else` expression can be added that will be executed when the +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() { + let number = 6; + if number > 10 { + // The number if larger than 10 + } else if number > 8 { + // The number is larger than 8 but smaller or equal to 10 + } else if number > 2 { + // The number is larger than 2 but smaller or equal to 8 + } else { + // The number is smaller than- or equal to 2. + } +} +``` + + +#### Using `if` in a `let` statement + +The `if` expression can be used on the right side of a `let` statement +just like a block: + +```mun +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 +the `if` block or the `else` block. This means that both the `if` and `else` +arms need to evaluate to the same type. If the types are mismatched the compiler +will report an error. + + +### `loop` expressions + +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() { + let i = 0; + loop { + if i > 5 { + break; + } + + i += 1; + } +} +``` + +Similar to `if`/`else` expressions, `loop` blocks can have a return value that +can be returned through the use of a `break` statement. + +```mun +fn count(i: i32, n: i32) -> i32 { + let loop_count = 0; + loop { + if i >= n { + break loop_count; + } + + loop_count += 1; + } +} +``` + +All `break` statements in a `loop` must have the same return type. + +```mun +let a = loop { + break 3; + break; // expected `{integer}`, found `nothing` +}; +``` + + +### `while` expressions + +`while` loops execute a block of code as long as a condition holds. A `while` +loop starts with the keyword `while` followed by a condition expression and a +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() { + let i = 0; + while i <= 5 { + i += 1; + } +} +``` + +A `break` statement inside the `while` loop immediately exits the loop. + +Unlike a `loop` expression, a `break` in a while loop cannot return a value +because a while loop can exit both through the use of a `break` statement and +because the condition no longer holds. Although we could explicitly return a +value from the `while` loop through the use of a `break` statement it is unclear +which value should be returned if the loop exits because the condition no longer +holds. diff --git a/book/src/ch02-04-extern-fn.md b/book/src/ch02-04-extern-fn.md new file mode 100644 index 000000000..c049dad17 --- /dev/null +++ b/book/src/ch02-04-extern-fn.md @@ -0,0 +1,49 @@ +## `extern` functions + +Extern functions are declared in Mun but their function bodies are defined +externally. They behave exactly the same as regular functions but their +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 +{{#include ../listings/ch02-basic-concepts/listing01.mun}} +``` + +Listing 2-1: Random bool in Mun + +The `random` function is marked as an `extern` function, which means that it +must be provided to the runtime when loading this library. + +First building the above code as `main.munlib` and then trying to load the +library in Rust using: + +```rust,no_run,noplaypen +# extern crate mun_runtime; +{{#include ../listings/ch02-basic-concepts/listing02.rs}} +``` + +Listing 2-2: Load listing 2-1 without adding extern function + +will result in an error: + +```bash +Failed to link: function `random` is missing. +``` + +This indicates that we have to provide the runtime with the `random` method, +which we can do through the use of the `insert_fn` method. Let's add a method +that uses the current time as the base of our `random` method: + +```rust,no_run,noplaypen +# extern crate mun_runtime; +{{#include ../listings/ch02-basic-concepts/listing03.rs}} +``` + +Listing 2-3: Load listing 2-1 with custom `random` function + +Note that we have to explicitly cast the function `random` to `extern "C" fn() +-> i64`. This is because each function in Rust has its own unique type. + +When we run this now, the error is gone and you should have a function that +returns a random boolean in Mun. diff --git a/book/src/ch03-00-structs.md b/book/src/ch03-00-structs.md new file mode 100644 index 000000000..c173b700b --- /dev/null +++ b/book/src/ch03-00-structs.md @@ -0,0 +1,5 @@ +# Structs + +A `struct` - or _structure_ - is a custom data type that groups related values together into a +named data structure. In this chapter we'll compare the two types of supported structures, +demonstrate how to use them, and how Mun's hot reloading works for structures. diff --git a/book/src/ch03-01-records-vs-tuples.md b/book/src/ch03-01-records-vs-tuples.md new file mode 100644 index 000000000..13a219c8c --- /dev/null +++ b/book/src/ch03-01-records-vs-tuples.md @@ -0,0 +1,102 @@ +## Records vs Tuples + +Mun supports two types of structures: _record structs_ and _tuple structs_. A record `struct` +definition specifies both the name and type of each piece of data, allowing you to retrieve the +_field_ by name. For example, Listing 3-1 shows a record `struct` that stores a 2-dimensional +vector. + + +```rust,ignore +{{#include ../listings/ch03-structs/listing01.mun}} +``` + +Listing 3-1: A record `struct` definition for a 2D vector + +In contrast, tuple `struct` definitions omit field names; only specifying the field types. Using a tuple `struct` makes sense when you want to associate a name with a tuple or distinguish it from +other tuples' types, but naming each field would be redundant. Listing 3-2 depicts a tuple `struct` +that stores a 3-dimensional vector. + + +```rust,ignore +{{#include ../listings/ch03-structs/listing02.mun}} +``` + +Listing 3-2: A tuple `struct` definition for a 3D vector + +### Create a Struct Instance + +To use a record `struct`, we create an _instance_ of that struct by stating the name of the +`struct` and then add curly braces containing `key: value` pairs for each of its fields. The keys +have to correspond to the field names in the `struct` definition, but can be provided in any order. +Let's create an instance of our `Vector2`, as illustrated in Listing 3-3. + + +```rust,ignore +{{#include ../listings/ch03-structs/listing03.mun}} +``` + +Listing 3-3: Creating a `Vector2` instance + +To create an instance of a tuple `struct`, you only need to state the name of the `struct` and +specify a comma-separated list of values between round brackets - as shown in Listing 3-4. As +values are not linked to field names, they have to appear in the order specified by the `struct` +definition. + + +```rust,ignore +{{#include ../listings/ch03-structs/listing04.mun}} +``` + +Listing 3-4: Creating a `Vector3` instance + +#### Field Init Shorthand + +It often makes sense to name function variables the same as the fields of a record `struct`. +Instead of having to repeat the `x` and `y` field names, the _field init shorthand syntax_ +demonstrated in Listing 3-5 allows you to avoid repetition. + + +```rust,ignore +{{#include ../listings/ch03-structs/listing05.mun}} +``` + +Listing 3-5: Creating a `Vector2` instance using the _field init shorthand syntax_ + + +### Access Struct Fields + +To access a record's fields, we use the dot notation: `vector.x`. The dot notation can be used both +to retrieve and to assign a value to the record's field, as shown in Listing 3-6. As you can see, +the record's name is used to indicate that the function expects two `Vector2` instances as function +arguments and returns a `Vector2` instance as result. + + +```rust,ignore +{{#include ../listings/ch03-structs/listing06.mun}} +``` + +Listing 3-6: Using `Vector2` instances' fields to calculate their addition + + +A tuple `struct` doesn't have field names, but instead accesses fields using indices - starting +from zero - corresponding to a field's position within the struct definition (see Listing 3-7). + + +```rust,ignore +{{#include ../listings/ch03-structs/listing07.mun}} +``` + +Listing 3-7: Using `Vector3` instances' fields to calculate their addition + + +### Unit Struct + +Sometimes it can be useful to define a `struct` without any fields. These so-called _unit structs_ +are defined using the `struct` keyword and a name, as shown in Listing 3-8. + + +```rust,ignore +{{#include ../listings/ch03-structs/listing08.mun}} +``` + +Listing 3-8: A unit `struct` definition. diff --git a/book/src/ch03-02-struct-memory-kind.md b/book/src/ch03-02-struct-memory-kind.md new file mode 100644 index 000000000..9f01e8341 --- /dev/null +++ b/book/src/ch03-02-struct-memory-kind.md @@ -0,0 +1,27 @@ +## Struct Memory Kind + +By default, Mun is a garbage collected language. This means that memory is *allocated* on the heap +and automatically *freed* by the Mun Runtime when your memory goes out of scope. Sometimes this +behavior is undesired, and you want to manually control when a value is freed. + +Mun allows you to specify this so-called *memory kind* in a `struct` definition: `gc` for garbage +collection or `value` to pass a `struct` by value; defaulting to `gc` when neither is specified. +Listing 3-9 shows the previously created struct definition of a `Vector2`, which has the default +`gc` memory kind. + + +```rust,ignore +{{#include ../listings/ch03-structs/listing09.mun}} +``` + +Listing 3-9: A record `struct` definition for a 2D vector, defaulting to the `gc` memory kind + +To manually specify the memory kind, add round brackets containing either `gc` or `value` after the +`struct` keyword, as illustrated in Listing 3-10. + + +```rust,ignore +{{#include ../listings/ch03-structs/listing10.mun}} +``` + +Listing 3-10: A record `struct` definition for a 2D vector, with the `value` memory kind diff --git a/book/src/ch03-03-marshalling.md b/book/src/ch03-03-marshalling.md new file mode 100644 index 000000000..26dd1b496 --- /dev/null +++ b/book/src/ch03-03-marshalling.md @@ -0,0 +1,36 @@ +## Marshalling + +When embedding Mun in other languages, you will probably want to retrieve, modify and send +structures across the *boundary* - of the two languages. When this so-called *marshalling* occurs, +there is often an associated performance penalty because the Mun Runtime needs to perform *runtime +checks* to validate the provided data types. + +Mun provides a homogeneous interface for marshalling any struct through a `StructRef`- a reference +to a heap-allocated struct. The Mun Runtime automatically handles the conversion from a function +return type into a `StructRef` and function arguments into Mun structs. + +> For structs with the `gc` memory kind, marshalling reuses the memory allocated by the garbage +> collector, but for structs with the `value` memory kind this requires their value to be copied +> into heap memory. + +Listing 3-11 shows how to *marshal* `Vector2` instances from Mun to Rust and vice versa, using the +`vector2_new` and `vector2_add` functions - previously [defined](ch03-01-records-vs-tuples.md). + +```rust,no_run,noplaypen +{{#include ../listings/ch03-structs/listing11.rs}} +``` + +Listing 3-11: Marshalling `Vector2` instances + +### Accessing Fields + +The API of `StructRef` consists of three generic methods for accessing fields: `get`, `set`, and +`replace`; respectively for retrieving, modifying, and replacing a struct field. The desired +field is specified using a string `field_name` parameter, which is identical to the one used with +the dot notation in Mun code. + +```rust,no_run,noplaypen +{{#include ../listings/ch03-structs/listing12.rs}} +``` + +Listing 3-12: Accessing fields of a `StructRef` diff --git a/book/src/ch03-04-hot-reloading-structs.md b/book/src/ch03-04-hot-reloading-structs.md new file mode 100644 index 000000000..ff494c5cd --- /dev/null +++ b/book/src/ch03-04-hot-reloading-structs.md @@ -0,0 +1,196 @@ +## Hot Reloading Structs + +To understand how we might use hot reloading of structures, let's create the +skeleton for a simulation, as shown in Listing 3-13. The `new_sim` function +constructs a `SimContext`, which maintains the simulation's state, and the +`sim_update` function will be called every frame to update the state of +`SimContext`. As Mun doesn't natively support logging, we'll use the extern +function `log_f32` to log values of the `f32` type. + +The subject of our simulation will be buoyancy; i.e. the upward force exerted by +a fluid on a (partially) immersed object that allows it to float. Currently, all +our simulation does it to log the elapsed time, every frame. + +Filename: buoyancy.mun + +```mun +{{#include ../listings/ch03-structs/listing13.mun}} +``` + + +Listing 3-13: The buoyancy simulation with state stored in `SimContext` + + +To be able to run our simulation, we need to embed it in a host language. +Listing 3-14 illustrates how to do this in Rust. + +```rust,no_run,noplaypen +{{#include ../listings/ch03-structs/listing14.rs}} +``` + + +Listing 3-14: The buoyancy simulation embedded in Rust + + +Now that we have a runnable host program, let's fire it up and see that hot +reloading magic at work! First we need to start the build watcher: + +```bash +mun build buoyancy.mun --watch +``` + +This will create the initial *buoyancy.munlib* that we can use to run our +host program in Rust: + +```bash +cargo run -- buoyancy.munlib +``` + +Your console should now receive a steady steam of 0.04... lines, indicating that +the simulation is indeed running at 25 Hz. Time to add some logic. + +### Insert Struct Fields + +Our simulation will contain a spherical object with radius *r* and density +*do* that is dropped from an initial height *h* into a body of water +with density *dw*. The simulation also takes the gravity, *g*, into +account, but for the sake of simplicity we'll only consider vertical movement. +Let's add this to the `SimContext` struct and update the `new_sim` function +accordingly, as shown in Listing 3-15. + +```mun +{{#include ../listings/ch03-structs/listing15.mun:3:41}} +``` + + +Listing 3-15: Struct definitions of the buoyancy simulation + + +#### Runtime Struct Field Initialization + +Upon successful compilation, the runtime will hot reload the new structs. Memory +of newly added structs will recursively be zero initialized. This means that all +fundamental types of a newly added structs and its child structs will be equal +to zero. + +We can verify this by replacing the `log_f32(elapsed_secs)` statement with: + +```mun +{{#include ../listings/ch03-structs/listing15.mun:44}} +``` + +Indeed the console now receives a stream of `0` lines. Luckily there is a trick +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 +{{#include ../listings/ch03-structs/listing16.mun:7}} +``` + +and set it to zero in the `new_sim` function: + +```mun +{{#include ../listings/ch03-structs/listing16.mun:26}} +``` + +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 +{{#include ../listings/ch03-structs/listing16.mun:45:47}} +``` + +Finally, we add this `if` statement to the `sim_update` function: + +```mun +{{#include ../listings/ch03-structs/listing16.mun:50:56}} +``` + +This piece of code will be triggered every time the `hot_reload_token` function +returns a different value, but only once - allowing us to initialize the value +of `SimContext`. + +### Edit Struct Fields + +Time to add the actual logic for simulating buoyancy. The formula for +calculating the buoyancy force is *force = submerged volume \* water density \* +gravity*. + +```mun +{{#include ../listings/ch03-structs/listing17.mun:51:73}} +``` + +Next we need to convert force into acceleration using *acc = force / mass*. We +don't readily have the sphere's mass available, but we can derive it using the +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 +{{#include ../listings/ch03-structs/listing17.mun:10:15}} +``` + +and pre-calculate it on construction: + +```mun +{{#include ../listings/ch03-structs/listing17.mun:30:43}} +``` + +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 +{{#include ../listings/ch03-structs/listing17.mun:80:84}} +``` + +Editing a field's name is only one of three ways that you can edit struct fields +in Mun. In order of priority, these are the changes that the Mun Runtime is able +to detect: + +1) If an old field and new field have the same name and type, they must have + remained unchanged. In this case, the field can be **moved**. +2) If an old field and new field have the same name, they must be the same + field. In this case, we accept a **type conversion** and the field can + potentially be **moved**. +3) If an old field and new field have different names but the same type, the + field *could* have been renamed. As there can be multiple candidates with the + same type, we accept the **renamed** and potentially **moved** field that is + closest to the original index of the old field. + +Some restrictions do apply: + +* A struct cannot simultaneously be **renamed** and its fields **edited**. +* A struct field cannot simultaneously be **renamed** and undergo a **type + conversion**. + +In both of the above cases, the difference will be recognised as two separate +changes: an insertion and a deletion of the struct/field. + +### Remove Struct Fields + +We now have all of the building blocks necessary to finish our buoyancy +simulation. If the sphere is (partially) submerged, we calculate and add the +buoyancy acceleration to the velocity. We also always subtract the gravitational +acceleration from the velocity to ensure that the sphere drops into the water. + +> One important thing to take into account when running simulations is to +> multiply the accelerations and velocities with the elapsed time, as we are +> working in discrete time. + +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 +{{#include ../listings/ch03-structs/listing17.mun:86:105}} +``` + +When the simulation has been hot reloaded, the console should now log height +values of the ball that are indicative of a sphere bobbing on the waves. + +Now that our simulation is completed, we no longer need the `token` field, +`hot_reload_token` function, and `if` statement. The `token` field can be +safely removed and the simulation hot reloaded without losing any state. + +Well done! You've just had your first experience of hot reloading strucs. diff --git a/book/theme/mun.css b/book/theme/mun.css new file mode 100644 index 000000000..b1dcf9364 --- /dev/null +++ b/book/theme/mun.css @@ -0,0 +1,9 @@ +span.caption { + font-size: .8em; + font-weight: 600; +} + +span.caption code { + font-size: 0.875em; + font-weight: 400; +} diff --git a/book/vendor/highlight.js b/book/vendor/highlight.js new file mode 160000 index 000000000..639f65f74 --- /dev/null +++ b/book/vendor/highlight.js @@ -0,0 +1 @@ +Subproject commit 639f65f74edd99b4613dd7642827a1e07cb3b8bb diff --git a/crates/mun/README.md b/crates/mun/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_abi/README.md b/crates/mun_abi/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_abi/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_codegen/README.md b/crates/mun_codegen/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_codegen/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_compiler/README.md b/crates/mun_compiler/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_compiler/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_compiler_daemon/README.md b/crates/mun_compiler_daemon/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_compiler_daemon/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_hir/README.md b/crates/mun_hir/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_hir/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_lld/README.md b/crates/mun_lld/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_lld/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_memory/README.md b/crates/mun_memory/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_memory/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_runtime/README.md b/crates/mun_runtime/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_runtime/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_runtime_capi/README.md b/crates/mun_runtime_capi/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_runtime_capi/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_syntax/README.md b/crates/mun_syntax/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_syntax/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/mun_target/README.md b/crates/mun_target/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/crates/mun_target/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..99de6dac5 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,7 @@ +[build] + ignore = "git diff --quiet HEAD^ HEAD book/" + +[[redirects]] + from = "/v0.2/*" + to = "https://release-v0-2--docs.mun-lang.org/:splat" + status = 200