├── .github └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── INSTALLATION.md ├── LICENSE ├── README.md ├── codegen ├── Cargo.toml └── src │ ├── aot.rs │ └── lib.rs ├── common ├── Cargo.toml └── src │ ├── lib.rs │ └── span.rs ├── diagnostics ├── Cargo.toml ├── src │ ├── lib.rs │ ├── span.rs │ └── write.rs └── tests │ └── test_diagnostics.rs ├── grammar ├── Cargo.toml ├── src │ ├── ast.rs │ ├── error.rs │ ├── lib.rs │ ├── parser.rs │ ├── token.rs │ └── tree.rs └── tests │ ├── test_parser.rs │ └── test_tokenizer.rs ├── hir ├── Cargo.toml ├── src │ ├── check.rs │ ├── error.rs │ ├── infer.rs │ ├── lib.rs │ ├── lower.rs │ ├── typed.rs │ └── warning.rs └── tests │ └── test_ast_lowering.rs ├── lib └── core │ ├── .tbconfig │ └── src │ ├── fmt.tb │ ├── intrinsics.tb │ ├── op.tb │ ├── option.tb │ ├── prelude.tb │ └── try.tb ├── mir ├── Cargo.toml └── src │ ├── lib.rs │ └── lower.rs ├── out.txt ├── rust-toolchain.toml ├── src ├── lib.rs └── main.rs └── test.trb /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | rustfmt: 12 | name: Rustfmt 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Install latest nightly with rustfmt 18 | uses: dtolnay/rust-toolchain@stable 19 | with: 20 | toolchain: nightly 21 | components: rustfmt 22 | 23 | - name: Check format 24 | run: cargo fmt --all -- --check 25 | 26 | clippy: 27 | name: Clippy 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | 32 | - name: Install latest nightly with clippy 33 | uses: dtolnay/rust-toolchain@stable 34 | with: 35 | toolchain: nightly 36 | components: clippy 37 | 38 | - name: Setup cache 39 | uses: Swatinem/rust-cache@v2 40 | 41 | - name: Clippy lint 42 | run: cargo clippy --workspace --all-features -- -D clippy::all -D clippy::pedantic -D clippy::nursery 43 | 44 | test: 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | os: [macos-latest, ubuntu-latest, windows-latest] 49 | 50 | runs-on: ${{ matrix.os }} 51 | 52 | steps: 53 | - uses: actions/checkout@v3 54 | 55 | - name: Install latest nightly 56 | uses: dtolnay/rust-toolchain@stable 57 | with: 58 | toolchain: nightly 59 | 60 | - name: Setup cache 61 | uses: Swatinem/rust-cache@v2 62 | 63 | - name: Run tests 64 | run: cargo test --verbose --workspace 65 | 66 | - name: Upload artifact 67 | uses: actions/upload-artifact@v3 68 | with: 69 | name: ${{ matrix.os }}-debug 70 | path: target/debug/terbium* 71 | 72 | miri_test: 73 | runs-on: ubuntu-latest 74 | 75 | steps: 76 | - uses: actions/checkout@v3 77 | 78 | - name: Install latest nightly with miri 79 | uses: dtolnay/rust-toolchain@stable 80 | with: 81 | toolchain: nightly 82 | components: miri 83 | 84 | - name: Setup cache 85 | uses: Swatinem/rust-cache@v2 86 | 87 | - name: Setup miri 88 | run: cargo miri setup 89 | 90 | - name: Run miri test 91 | run: cargo miri test --verbose --workspace 92 | 93 | wasm_test: 94 | runs-on: ubuntu-latest 95 | 96 | steps: 97 | - uses: actions/checkout@v3 98 | 99 | - name: Install latest nightly 100 | uses: dtolnay/rust-toolchain@stable 101 | with: 102 | toolchain: nightly 103 | override: true 104 | target: wasm32-unknown-unknown 105 | 106 | - name: Setup cache 107 | uses: Swatinem/rust-cache@v2 108 | 109 | - name: Build WASM 110 | run: cargo build --verbose --target wasm32-unknown-unknown 111 | 112 | - name: Upload artifact 113 | uses: actions/upload-artifact@v3 114 | with: 115 | name: wasm-debug 116 | path: target/wasm32-unknown-unknown/debug/terbium.wasm 117 | 118 | build: 119 | strategy: 120 | fail-fast: false 121 | matrix: 122 | include: 123 | - os: macos-latest 124 | target: "x86_64-apple-darwin" 125 | - os: macos-latest 126 | target: "aarch64-apple-darwin" 127 | - os: ubuntu-latest 128 | target: "x86_64-unknown-linux-gnu" 129 | - os: windows-latest 130 | target: "x86_64-pc-windows-msvc" 131 | ext: ".exe" 132 | 133 | runs-on: ${{ matrix.os }} 134 | steps: 135 | - uses: actions/checkout@v3 136 | 137 | - name: Install latest nightly 138 | uses: dtolnay/rust-toolchain@stable 139 | with: 140 | toolchain: nightly 141 | 142 | - name: Setup cache 143 | uses: Swatinem/rust-cache@v2 144 | 145 | - name: Install target 146 | run: rustup target add ${{ matrix.target }} 147 | 148 | - name: Build 149 | run: cargo build --verbose --release --target ${{ matrix.target }} 150 | 151 | - name: Rename bin 152 | run: mv target/${{ matrix.target }}/release/terbium${{ matrix.ext }} ./${{ matrix.target }}${{ matrix.ext }} 153 | 154 | - name: Upload artifact 155 | uses: actions/upload-artifact@v3 156 | with: 157 | name: releases 158 | path: ${{ matrix.target }}${{ matrix.ext }} 159 | 160 | nightly-release: 161 | runs-on: ubuntu-latest 162 | needs: [build] 163 | if: ${{ github.event_name == 'push' }} 164 | 165 | steps: 166 | - uses: actions/checkout@v3 167 | 168 | - name: Retrieve artifact 169 | uses: actions/download-artifact@v3 170 | with: 171 | name: releases 172 | path: releases 173 | 174 | - name: GitHub nightly release 175 | run: | 176 | git tag -d nightly || true 177 | git push origin --delete nightly || true 178 | git tag nightly 179 | git push origin nightly 180 | gh release delete ${{ env.VERSION }} -y || true 181 | gh release create ${{ env.VERSION }} -t "Nightly" -n "$(git rev-parse HEAD | cut -c 1-7) | $(git log -1 --pretty=%B)" ${{ env.FILES }} 182 | env: 183 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 184 | VERSION: 'nightly' 185 | FILES: releases/* 186 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .gitpod.yml 3 | .idea 4 | .DS_Store 5 | .vscode -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | We appreciate any contributions made to Terbium! 3 | Please ensure that your code meets the following requirements before contributing though: 4 | 5 | - Have experience in both Rust and Terbium 6 | - Run both `cargo check` and `cargo fmt` if you have modified Rust code 7 | - (`cargo fmt` will handle this) Make sure each line is under 120 characters long 8 | - We also advise you to run `cargo test` as well, but that will be tested by the GitHub workflow check. 9 | Doing such will probably save you an unnecessary commit if the check does fail. 10 | - For a similar reason, it is also advised that you run clippy with the following command as well: 11 | `cargo clippy --workspace --all-features -- -D clippy::all -D clippy::pedantic -D clippy::nursery` 12 | and make fixes accordingly. 13 | 14 | ## Tools needed 15 | - Rust/Cargo (Nightly) 16 | - LLVM 16.0+ 17 | 18 | ## How do I install LLVM? 19 | 20 | _If you are using macOS and have [Homebrew](https://brew.sh) installed, see [how to install LLVM using Homebrew](#using-homebrew)._ 21 | 22 | ### From GitHub releases 23 | 24 | 1. Go to the [LLVM release page](https://github.com/llvm/llvm-project/releases) and find the latest release. 25 | 26 | 2. Pick the file that matches your system architecture. 27 | - For Unix/Linux platforms you want to pick the one that is prefixed with `clang+llvm-16.x.x` 28 | - For Windows pick [`LLVM-16.x.x-win32.exe`](https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/LLVM-16.0.4-win32.exe) 29 | or [`LLVM-16.x.x-win64.exe`](https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/LLVM-16.0.4-win64.exe) 30 | depending on your system architecture 31 | 32 | 3. Extract LLVM *if* you are on Unix/Linux platforms. 33 | On Linux platforms you would run `tar -xf clang+llvm-16.x.x-the-file-that-I-downloaded.tar.xz`, 34 | to make it easier to type later on, it is recommended to rename this file to `llvm-16.0.0`. 35 | **On Windows it is already an executable so no extraction is needed.** 36 | 37 | 4. You can place the extraced LLVM folder/executable wherever you want, if you can't decide, on Windows you can place it 38 | in `C:/Program Files/`, on Unix/Linux you can place it in `~/` 39 | 40 | 5. Then set environment variable `LLVM_SYS_160_PREFIX=/path/to/llvm/folder/` when you are compiling Terbium. 41 | For example if the LLVM folder is at `/home/user/llvm-16.0.0` then I could build Terbium via: 42 | `LLVM_SYS_160_PREFIX=/home/user/llvm-16.0.0 cargo build --release` 43 | 44 | 6. Building Terbium should work now. 45 | 46 | Happy Coding! 47 | 48 | ### Using Homebrew 49 | 50 | *You must have [Homebrew](https://brew.sh) installed for this installation method. Homebrew is only available for macOS 51 | and Linux; if you are using Windows consider installing from [GitHub releases](#from-github-releases).* 52 | 53 | 1. Install Homebrew if you haven't already (see https://brew.sh for instructions). 54 | 55 | 2. Run `brew install llvm@16` to install LLVM 16. 56 | 57 | 3. After installing, Homebrew may emit a *Caveats* section with instructions on how to use and link LLVM: 58 | ```text 59 | ==> Caveats 60 | To use the bundled libc++ please add the following LDFLAGS: 61 | LDFLAGS="-L/opt/homebrew/opt/llvm/lib/c++ -Wl,-rpath,/opt/homebrew/opt/llvm/lib/c++" 62 | 63 | llvm is keg-only, which means it was not symlinked into /opt/homebrew, 64 | because macOS already provides this software and installing another version in 65 | parallel can cause all kinds of trouble. 66 | 67 | If you need to have llvm first in your PATH, run: 68 | fish_add_path /opt/homebrew/opt/llvm/bin 69 | 70 | For compilers to find llvm you may need to set: 71 | set -gx LDFLAGS "-L/opt/homebrew/opt/llvm/lib" 72 | set -gx CPPFLAGS "-I/opt/homebrew/opt/llvm/include" 73 | ``` 74 | 75 | 4. Run the commands in this section, especially the commands suggested after *For compilers to find llvm you may need to set...*: 76 | 77 | ```shell 78 | # These commands will vary. Refer to the Caveats section for the exact commands to run. 79 | $ set -gx LDFLAGS "-L/opt/homebrew/opt/llvm/lib" 80 | $ set -gx CPPFLAGS "-I/opt/homebrew/opt/llvm/include" 81 | ``` 82 | 83 | 5. Then set environment variable `LLVM_SYS_160_PREFIX=/path/to/llvm/folder/` when you are compiling Terbium. 84 | This is usually `/opt/homebrew/opt/llvm`. 85 | 86 | 6. Building Terbium should work now. 87 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "aho-corasick" 18 | version = "1.0.2" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 21 | dependencies = [ 22 | "memchr", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "cc" 39 | version = "1.0.79" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 42 | 43 | [[package]] 44 | name = "cfg-if" 45 | version = "1.0.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 48 | 49 | [[package]] 50 | name = "chumsky" 51 | version = "0.9.2" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d" 54 | dependencies = [ 55 | "hashbrown", 56 | "stacker", 57 | ] 58 | 59 | [[package]] 60 | name = "codegen" 61 | version = "0.1.0" 62 | dependencies = [ 63 | "common", 64 | "inkwell", 65 | "mir", 66 | ] 67 | 68 | [[package]] 69 | name = "common" 70 | version = "0.1.0" 71 | dependencies = [ 72 | "chumsky", 73 | "internment", 74 | ] 75 | 76 | [[package]] 77 | name = "console" 78 | version = "0.15.7" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" 81 | dependencies = [ 82 | "encode_unicode", 83 | "lazy_static", 84 | "libc", 85 | "unicode-width", 86 | "windows-sys", 87 | ] 88 | 89 | [[package]] 90 | name = "diagnostics" 91 | version = "0.1.0" 92 | dependencies = [ 93 | "common", 94 | "yansi", 95 | ] 96 | 97 | [[package]] 98 | name = "either" 99 | version = "1.8.1" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 102 | 103 | [[package]] 104 | name = "encode_unicode" 105 | version = "0.3.6" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 108 | 109 | [[package]] 110 | name = "getrandom" 111 | version = "0.2.9" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" 114 | dependencies = [ 115 | "cfg-if", 116 | "libc", 117 | "wasi", 118 | ] 119 | 120 | [[package]] 121 | name = "grammar" 122 | version = "0.1.0" 123 | dependencies = [ 124 | "chumsky", 125 | "common", 126 | "diagnostics", 127 | "unicode-xid", 128 | ] 129 | 130 | [[package]] 131 | name = "hashbrown" 132 | version = "0.12.3" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 135 | dependencies = [ 136 | "ahash", 137 | ] 138 | 139 | [[package]] 140 | name = "hir" 141 | version = "0.1.0" 142 | dependencies = [ 143 | "common", 144 | "diagnostics", 145 | "grammar", 146 | "internment", 147 | ] 148 | 149 | [[package]] 150 | name = "indexmap" 151 | version = "1.9.3" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 154 | dependencies = [ 155 | "autocfg", 156 | "hashbrown", 157 | ] 158 | 159 | [[package]] 160 | name = "indicatif" 161 | version = "0.17.5" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" 164 | dependencies = [ 165 | "console", 166 | "instant", 167 | "number_prefix", 168 | "portable-atomic", 169 | "unicode-width", 170 | ] 171 | 172 | [[package]] 173 | name = "inkwell" 174 | version = "0.2.0" 175 | source = "git+https://github.com/TheDan64/inkwell?branch=master#ec963b95da169d0927112126a204a2e9d0bcaee0" 176 | dependencies = [ 177 | "either", 178 | "inkwell_internals", 179 | "libc", 180 | "llvm-sys", 181 | "once_cell", 182 | "parking_lot", 183 | ] 184 | 185 | [[package]] 186 | name = "inkwell_internals" 187 | version = "0.8.0" 188 | source = "git+https://github.com/TheDan64/inkwell?branch=master#ec963b95da169d0927112126a204a2e9d0bcaee0" 189 | dependencies = [ 190 | "proc-macro2", 191 | "quote", 192 | "syn", 193 | ] 194 | 195 | [[package]] 196 | name = "instant" 197 | version = "0.1.12" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 200 | dependencies = [ 201 | "cfg-if", 202 | ] 203 | 204 | [[package]] 205 | name = "internment" 206 | version = "0.7.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "2a798d7677f07d6f1e77be484ea8626ddb1566194de399f1206306820c406371" 209 | dependencies = [ 210 | "hashbrown", 211 | "parking_lot", 212 | ] 213 | 214 | [[package]] 215 | name = "lazy_static" 216 | version = "1.4.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 219 | 220 | [[package]] 221 | name = "libc" 222 | version = "0.2.142" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" 225 | 226 | [[package]] 227 | name = "llvm-sys" 228 | version = "160.1.2" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "c23bd4905c97f88d0dfec97095829f4ac7eff8ce28fec74a8f8eb8e64830a86e" 231 | dependencies = [ 232 | "cc", 233 | "lazy_static", 234 | "libc", 235 | "regex", 236 | "semver", 237 | ] 238 | 239 | [[package]] 240 | name = "lock_api" 241 | version = "0.4.9" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 244 | dependencies = [ 245 | "autocfg", 246 | "scopeguard", 247 | ] 248 | 249 | [[package]] 250 | name = "memchr" 251 | version = "2.5.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 254 | 255 | [[package]] 256 | name = "mir" 257 | version = "0.1.0" 258 | dependencies = [ 259 | "common", 260 | "diagnostics", 261 | "hir", 262 | "indexmap", 263 | ] 264 | 265 | [[package]] 266 | name = "number_prefix" 267 | version = "0.4.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 270 | 271 | [[package]] 272 | name = "once_cell" 273 | version = "1.17.1" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 276 | 277 | [[package]] 278 | name = "parking_lot" 279 | version = "0.12.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 282 | dependencies = [ 283 | "lock_api", 284 | "parking_lot_core", 285 | ] 286 | 287 | [[package]] 288 | name = "parking_lot_core" 289 | version = "0.9.7" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 292 | dependencies = [ 293 | "cfg-if", 294 | "libc", 295 | "redox_syscall", 296 | "smallvec", 297 | "windows-sys", 298 | ] 299 | 300 | [[package]] 301 | name = "portable-atomic" 302 | version = "1.3.3" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" 305 | 306 | [[package]] 307 | name = "proc-macro2" 308 | version = "1.0.63" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 311 | dependencies = [ 312 | "unicode-ident", 313 | ] 314 | 315 | [[package]] 316 | name = "psm" 317 | version = "0.1.21" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" 320 | dependencies = [ 321 | "cc", 322 | ] 323 | 324 | [[package]] 325 | name = "quote" 326 | version = "1.0.29" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" 329 | dependencies = [ 330 | "proc-macro2", 331 | ] 332 | 333 | [[package]] 334 | name = "redox_syscall" 335 | version = "0.2.16" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 338 | dependencies = [ 339 | "bitflags", 340 | ] 341 | 342 | [[package]] 343 | name = "regex" 344 | version = "1.8.4" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" 347 | dependencies = [ 348 | "aho-corasick", 349 | "memchr", 350 | "regex-syntax", 351 | ] 352 | 353 | [[package]] 354 | name = "regex-syntax" 355 | version = "0.7.2" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 358 | 359 | [[package]] 360 | name = "scopeguard" 361 | version = "1.1.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 364 | 365 | [[package]] 366 | name = "semver" 367 | version = "1.0.17" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" 370 | 371 | [[package]] 372 | name = "smallvec" 373 | version = "1.10.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 376 | 377 | [[package]] 378 | name = "stacker" 379 | version = "0.1.15" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" 382 | dependencies = [ 383 | "cc", 384 | "cfg-if", 385 | "libc", 386 | "psm", 387 | "winapi", 388 | ] 389 | 390 | [[package]] 391 | name = "syn" 392 | version = "2.0.22" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" 395 | dependencies = [ 396 | "proc-macro2", 397 | "quote", 398 | "unicode-ident", 399 | ] 400 | 401 | [[package]] 402 | name = "terbium" 403 | version = "0.0.1" 404 | dependencies = [ 405 | "codegen", 406 | "common", 407 | "diagnostics", 408 | "grammar", 409 | "hir", 410 | "indicatif", 411 | "mir", 412 | ] 413 | 414 | [[package]] 415 | name = "unicode-ident" 416 | version = "1.0.9" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 419 | 420 | [[package]] 421 | name = "unicode-width" 422 | version = "0.1.10" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 425 | 426 | [[package]] 427 | name = "unicode-xid" 428 | version = "0.2.4" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 431 | 432 | [[package]] 433 | name = "version_check" 434 | version = "0.9.4" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 437 | 438 | [[package]] 439 | name = "wasi" 440 | version = "0.11.0+wasi-snapshot-preview1" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 443 | 444 | [[package]] 445 | name = "winapi" 446 | version = "0.3.9" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 449 | dependencies = [ 450 | "winapi-i686-pc-windows-gnu", 451 | "winapi-x86_64-pc-windows-gnu", 452 | ] 453 | 454 | [[package]] 455 | name = "winapi-i686-pc-windows-gnu" 456 | version = "0.4.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 459 | 460 | [[package]] 461 | name = "winapi-x86_64-pc-windows-gnu" 462 | version = "0.4.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 465 | 466 | [[package]] 467 | name = "windows-sys" 468 | version = "0.45.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 471 | dependencies = [ 472 | "windows-targets", 473 | ] 474 | 475 | [[package]] 476 | name = "windows-targets" 477 | version = "0.42.2" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 480 | dependencies = [ 481 | "windows_aarch64_gnullvm", 482 | "windows_aarch64_msvc", 483 | "windows_i686_gnu", 484 | "windows_i686_msvc", 485 | "windows_x86_64_gnu", 486 | "windows_x86_64_gnullvm", 487 | "windows_x86_64_msvc", 488 | ] 489 | 490 | [[package]] 491 | name = "windows_aarch64_gnullvm" 492 | version = "0.42.2" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 495 | 496 | [[package]] 497 | name = "windows_aarch64_msvc" 498 | version = "0.42.2" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 501 | 502 | [[package]] 503 | name = "windows_i686_gnu" 504 | version = "0.42.2" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 507 | 508 | [[package]] 509 | name = "windows_i686_msvc" 510 | version = "0.42.2" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 513 | 514 | [[package]] 515 | name = "windows_x86_64_gnu" 516 | version = "0.42.2" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 519 | 520 | [[package]] 521 | name = "windows_x86_64_gnullvm" 522 | version = "0.42.2" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 525 | 526 | [[package]] 527 | name = "windows_x86_64_msvc" 528 | version = "0.42.2" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 531 | 532 | [[package]] 533 | name = "yansi" 534 | version = "0.5.1" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 537 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terbium" 3 | authors = ["jay3332"] 4 | version = "0.0.1" 5 | edition = "2021" 6 | description = "The performant yet elegant and feature-packed programming language. Made with Rust." 7 | license = "AGPL-3.0-or-later" 8 | homepage = "https://github.com/TerbiumLang/Terbium" 9 | repository = "https://github.com/TerbiumLang/Terbium" 10 | readme = "README.md" 11 | keywords = ["terbium", "language", "lang", "interpreter", "compiler", "trb"] 12 | categories = ["compilers", "command-line-interface", "parsing", "wasm"] 13 | 14 | # [[bin]] 15 | # name = "terbium" 16 | # path = "src/bin/terbium.rs" 17 | 18 | [workspace] 19 | members = ["codegen", "common", "diagnostics", "grammar", "hir", "mir"] 20 | 21 | [dependencies] 22 | codegen = { path = "codegen" } 23 | common = { path = "common" } 24 | diagnostics = { path = "diagnostics" } 25 | grammar = { path = "grammar" } 26 | hir = { path = "hir" } 27 | mir = { path = "mir" } 28 | indicatif = "0.17" 29 | 30 | [profile.dev] 31 | opt-level = 0 32 | 33 | [profile.release] 34 | lto = "fat" 35 | strip = true 36 | codegen-units = 1 37 | opt-level = 3 38 | -------------------------------------------------------------------------------- /INSTALLATION.md: -------------------------------------------------------------------------------- 1 | # Installing Terbium 2 | 3 | *This page has moved to [the Terbium Documentation](https://terbium-lilac.vercel.app/docs/quickstart#install-terbium).* 4 | 5 | https://github.com/terbium-lang/terbium/releases/tag/nightly, install the build for your operating system: 6 | 7 | * aarch-64-apple-darwin for Apple Silicon macOS devices, 8 | * x86_64-apple-darwin for Intel-based macOS devices, 9 | * x86_64-pc-windows-msvc.exe for Windows, 10 | * and x86_64-unknown_linux_gnu for Linux. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terbium 2 | 3 | Write bug-free code that runs at native performance with ease by using Terbium, a high-level programming language that doesn't compromise in performance, readability, reliability and developer experience. 4 | This is the rewrite branch of Terbium. See legacy Terbium [here](https://github.com/TerbiumLang/Terbium/tree/legacy). 5 | 6 | ## Specification 7 | 8 | A semi-formal language specification can be found [here](https://jay3332.gitbook.io/terbium/spec). The specification may be outdated with this specific implementation. 9 | 10 | ## Installation 11 | 12 | Terbium is still a work in progress. If you want to: 13 | 14 | * *contribute to Terbium*, you can clone this repository. See [CONTRIBUTING.md](https://github.com/terbium-lang/terbium/blob/main/CONTRIBUTING.md) for more information. 15 | * *use Terbium*, see [the quickstart guide](https://terbium-lilac.vercel.app/guide/quickstart) for more information. 16 | 17 | ## Why Terbium? 18 | 19 | Terbium is designed to be: 20 | 21 | * **Concise**. Write less code wherever possible. High-level constructs and language features make Terbium elegant to write. 22 | * **Readable**. Being explicit and unambiguous in the code where needed while still being concise. 23 | * **Simple**. Complexity is abstracted away from the developer when writing a Terbium program, but low-level features are still accessable whenever needed. 24 | * **Performant**. With LLVM, Terbium can compile to optimized native machine code, so your high-level code won't compromise runtime performance. All abstractions in Terbium incur zero runtime overhead. 25 | * **Robust**. Write error-free code with Terbium. Null is a concept that does not exist in Terbium, and all errors must explicitly be handled. Mutability is explicit and memory is automatically freed. 26 | * **Rapid**. Rapid-prototyping and developer experience is just as important as runtime performance. A *debug mode* compiles semi-optimized code that compiles fast and suppresses pedantic errors as warnings. 27 | * **Typed**. A comprehensive type system allows Terbium to infer the types of most values and functions at compile-time. If a type cannot be inferred, it must be explicitly specified. 28 | 29 | ## Examples 30 | 31 | ### Hello, world! 32 | 33 | ```swift 34 | // Main function is recommended but not required 35 | func main() { 36 | println("Hello, world!"); 37 | } 38 | ``` 39 | 40 | ### Fibonacci 41 | 42 | ```swift 43 | func fib(n: uint) = if n <= 1 then n else fib(n - 1) + fib(n - 2); 44 | 45 | func main() { 46 | (0..=10).map(fib).for_each(println); 47 | } 48 | ``` 49 | 50 | ### Optional values 51 | 52 | ```swift 53 | // Optional values are of type T? and can be either some(value) or none 54 | // Any `value` of type T can coerce into T? by some(value). 55 | 56 | func maybe_five() -> int? = none; 57 | 58 | func main() { 59 | println(maybe_five() else 10); // 10 60 | } 61 | ``` 62 | 63 | ### Error handling 64 | 65 | ```swift 66 | import std.io.{Error, stdout}; 67 | 68 | // T throws E is a type itself: 69 | type Result = T throws Error; 70 | 71 | // throws E expands to void throws E, void being the unit type 72 | type VoidResult = throws Error; 73 | 74 | // Specify the return type as `throws Error` to indicate that the return value might fail 75 | func fallible() -> throws Error { 76 | let mut out = stdout(); 77 | out.write("This might fail")!; // use ! to propagate the error to the function 78 | 79 | // implied return value of `void` is coerced to `ok(void)` 80 | // in fact, any `value` of type T can be coerced to `T throws _` as `ok(value)` 81 | // 82 | // this explicit type cast will result in `ok(5)`: 83 | // let x = 5 to /* `to` is the cast operator */ int throws Error; 84 | } 85 | 86 | func main() { 87 | // Use `catch` to handle a fallible value 88 | fallible() catch err { 89 | eprintln($"Failed to print: {err}"); // string interpolation 90 | }; 91 | 92 | // If there is absolutely no way to handle an error, 93 | // you can halt the program's execution with `panic`: 94 | // 95 | // Note: `else` is equivalent to `catch _` 96 | fallible() else panic("Oh no!"); 97 | // You can also use `.unwrap()` to panic with no message: 98 | fallible().unwrap(); 99 | } 100 | ``` 101 | 102 | ### Functional programming 103 | 104 | ```swift 105 | /// Calculate double the sum of all multiples of 3 which are <= n 106 | func example(n: uint) = 107 | (0..=n) 108 | .filter(\x do x % 3 == 0) // Lambda expressions 109 | .map { _ * 2 } // Shorthand for .map(\x do x * 2) 110 | .reduce { $0 + $1 } // Shorthand for .map(\x, y do x + y) 111 | |> println; // Pipeline operator 112 | ``` 113 | 114 | ### Subtyping 115 | 116 | ```swift 117 | // A `struct` defines a type that is composed of many values called "fields", 118 | // which are all stored in a contiguous block of memory. 119 | // Each field is given a name and a type. 120 | struct Rectangle { 121 | width: int, 122 | height: int, 123 | } 124 | 125 | // Explicit nominal subtyping with traits. 126 | // 127 | // A `trait` defines a set of functions that a type must implement. 128 | // It is similar to an interface in object-oriented languages. 129 | trait Shape { 130 | // A shape must have an `area` method that returns an `int`. 131 | func area(self) -> int; 132 | } 133 | 134 | // A `extend` block can extend a type's functionality with a trait. 135 | // The type must implement all functions defined in the trait. 136 | extend Shape for Rectangle { 137 | /// Calculate the area of the rectangle. 138 | func area(self) = self.width * self.height; 139 | } 140 | 141 | func main() { 142 | // Create a new rectangle using the generated constructor. 143 | let rect = Rectangle(width: 10, height: 5); 144 | 145 | // Call the `area` method on the rectangle. 146 | println(rect.area()); // 50 147 | } 148 | ``` 149 | -------------------------------------------------------------------------------- /codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codegen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | common = { path = "../common" } 10 | inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm16-0"] } 11 | mir = { path = "../mir" } 12 | -------------------------------------------------------------------------------- /codegen/src/aot.rs: -------------------------------------------------------------------------------- 1 | use common::span::Spanned; 2 | use inkwell::{ 3 | attributes::Attribute, 4 | basic_block::BasicBlock, 5 | builder::Builder, 6 | context::Context, 7 | module::Module, 8 | passes::PassManager, 9 | types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum, IntType, StringRadix}, 10 | values::{AnyValue, BasicValue, BasicValueEnum, FunctionValue, PointerValue}, 11 | IntPredicate, 12 | }; 13 | use mir::{ 14 | BlockId, Constant, Expr, Func, Ident, IntIntrinsic, IntSign, IntWidth, LocalEnv, LocalId, 15 | LookupId, Node, PrimitiveTy, Ty, UnaryIntIntrinsic, 16 | }; 17 | use std::{collections::HashMap, mem::MaybeUninit, ops::Not}; 18 | 19 | #[derive(Copy, Clone)] 20 | struct Local<'ctx> { 21 | value: PointerValue<'ctx>, 22 | ty: BasicTypeEnum<'ctx>, 23 | } 24 | 25 | /// Compiles a function. 26 | pub struct Compiler<'a, 'ctx> { 27 | pub context: &'ctx Context, 28 | pub builder: &'a Builder<'ctx>, 29 | pub fpm: &'a PassManager>, 30 | pub module: &'a Module<'ctx>, 31 | 32 | lowering: MaybeUninit, 33 | fn_value: MaybeUninit>, 34 | functions: HashMap>, 35 | locals: HashMap>>, 36 | blocks: HashMap>, 37 | increment: usize, 38 | } 39 | 40 | const VOID: Expr = Expr::Constant(Constant::Void); 41 | 42 | impl<'a, 'ctx> Compiler<'a, 'ctx> { 43 | #[inline] 44 | fn get_int_type(&self, width: IntWidth) -> IntType<'ctx> { 45 | self.context.custom_width_int_type(width as usize as _) 46 | } 47 | 48 | #[inline] 49 | fn lowering_mut(&mut self) -> &mut Func { 50 | unsafe { self.lowering.assume_init_mut() } 51 | } 52 | 53 | #[inline] 54 | const fn fn_value(&self) -> FunctionValue<'ctx> { 55 | unsafe { self.fn_value.assume_init() } 56 | } 57 | 58 | /// Lowers a constant value. 59 | pub fn lower_constant(&self, constant: Constant) -> BasicValueEnum<'ctx> { 60 | match constant { 61 | Constant::Int(i, sign, width) if width as usize <= 64 => BasicValueEnum::IntValue( 62 | self.get_int_type(width as _) 63 | .const_int(i as u64, sign.is_signed()), 64 | ), 65 | Constant::Int(i, sign, width) => { 66 | let string = match sign.is_signed() { 67 | true => (i as i128).to_string(), 68 | false => i.to_string(), 69 | }; 70 | // TODO: we can use the from_arbitrary_precision method instead of converting to string which is much slower 71 | BasicValueEnum::IntValue( 72 | self.get_int_type(width) 73 | .const_int_from_string(&string, StringRadix::Decimal) 74 | .unwrap(), 75 | ) 76 | } 77 | Constant::Bool(b) => { 78 | BasicValueEnum::IntValue(self.context.bool_type().const_int(b as u64, false)) 79 | } 80 | Constant::Void => unimplemented!("void constants should be handled as a special-case"), 81 | _ => todo!(), 82 | } 83 | } 84 | 85 | /// Get the next temporary local name increment. 86 | #[inline] 87 | pub fn next_increment(&mut self) -> String { 88 | let increment = self.increment; 89 | self.increment += 1; 90 | format!("tmp.{increment}") 91 | } 92 | 93 | /// Lowers an integer intrinsic 94 | pub fn lower_int_intrinsic( 95 | &mut self, 96 | intr: IntIntrinsic, 97 | sign: IntSign, 98 | ) -> Option> { 99 | Some(match intr { 100 | IntIntrinsic::Unary(op, val) => { 101 | let next = self.next_increment(); 102 | let val = self.lower_expr(*val)?.into_int_value(); 103 | BasicValueEnum::IntValue(match op { 104 | UnaryIntIntrinsic::Neg => self.builder.build_int_neg(val, &next), 105 | UnaryIntIntrinsic::BitNot => self.builder.build_not(val, &next), 106 | }) 107 | } 108 | IntIntrinsic::Binary(op, lhs, rhs) => { 109 | use mir::BinaryIntIntrinsic as B; 110 | 111 | let lhs = self.lower_expr(*lhs)?.into_int_value(); 112 | let rhs = self.lower_expr(*rhs)?.into_int_value(); 113 | let next = self.next_increment(); 114 | let int_value = match op { 115 | B::Add => self.builder.build_int_add(lhs, rhs, &next), 116 | B::Sub => self.builder.build_int_sub(lhs, rhs, &next), 117 | B::Mul => self.builder.build_int_mul(lhs, rhs, &next), 118 | B::Div if sign.is_signed() => { 119 | self.builder.build_int_signed_div(lhs, rhs, &next) 120 | } 121 | B::Div => self.builder.build_int_unsigned_div(lhs, rhs, &next), 122 | B::Mod if sign.is_signed() => { 123 | self.builder.build_int_signed_rem(lhs, rhs, &next) 124 | } 125 | B::Mod => self.builder.build_int_unsigned_rem(lhs, rhs, &next), 126 | b if b.is_cmp() => { 127 | let cmp = match (b, sign) { 128 | (B::Eq, _) => IntPredicate::EQ, 129 | (B::Ne, _) => IntPredicate::NE, 130 | (B::Lt, IntSign::Signed) => IntPredicate::SLT, 131 | (B::Lt, IntSign::Unsigned) => IntPredicate::ULT, 132 | (B::Le, IntSign::Signed) => IntPredicate::SLE, 133 | (B::Le, IntSign::Unsigned) => IntPredicate::ULE, 134 | (B::Gt, IntSign::Signed) => IntPredicate::SGT, 135 | (B::Gt, IntSign::Unsigned) => IntPredicate::UGT, 136 | (B::Ge, IntSign::Signed) => IntPredicate::SGE, 137 | (B::Ge, IntSign::Unsigned) => IntPredicate::UGE, 138 | _ => unreachable!(), 139 | }; 140 | self.builder.build_int_compare(cmp, lhs, rhs, &next) 141 | } 142 | B::BitOr => self.builder.build_or(lhs, rhs, &next), 143 | B::BitAnd => self.builder.build_and(lhs, rhs, &next), 144 | B::BitXor => self.builder.build_xor(lhs, rhs, &next), 145 | B::Shl => self.builder.build_left_shift(lhs, rhs, &next), 146 | B::Shr if sign.is_signed() => { 147 | self.builder.build_right_shift(lhs, rhs, true, &next) 148 | } 149 | B::Shr => self.builder.build_right_shift(lhs, rhs, false, &next), 150 | _ => unreachable!(), 151 | }; 152 | BasicValueEnum::IntValue(int_value) 153 | } 154 | }) 155 | } 156 | 157 | /// Lowers an expression. 158 | pub fn lower_expr(&mut self, expr: Spanned) -> Option> { 159 | Some(match expr.into_value() { 160 | Expr::Local(id) => { 161 | let ptr = self.locals[&id]?; 162 | self.builder 163 | .build_load(ptr.ty, ptr.value, &self.next_increment()) 164 | } 165 | Expr::Constant(c) => self.lower_constant(c), 166 | Expr::IntIntrinsic(intr, sign, _) => self.lower_int_intrinsic(intr, sign)?, 167 | Expr::BoolIntrinsic(intr) => { 168 | use mir::BoolIntrinsic as B; 169 | 170 | macro_rules! lower { 171 | ($($tgt:ident),+ => $e:expr) => {{ 172 | $(let $tgt = self.lower_expr(*$tgt)?.into_int_value();)+ 173 | $e 174 | }} 175 | } 176 | 177 | let next = self.next_increment(); 178 | let bool_value = match intr { 179 | B::And(lhs, rhs) => lower!(lhs, rhs => self.builder.build_and(lhs, rhs, &next)), 180 | B::Or(lhs, rhs) => lower!(lhs, rhs => self.builder.build_or(lhs, rhs, &next)), 181 | B::Xor(lhs, rhs) => lower!(lhs, rhs => self.builder.build_xor(lhs, rhs, &next)), 182 | B::Not(val) => lower!(val => self.builder.build_not(val, &next)), 183 | }; 184 | BasicValueEnum::IntValue(bool_value) 185 | } 186 | Expr::Call(func, args) => { 187 | let args = args 188 | .into_iter() 189 | .map(|arg| self.lower_expr(arg).unwrap().into()) 190 | .collect::>(); 191 | 192 | self.builder 193 | .build_call(self.functions[&func], &args, &self.next_increment()) 194 | .try_as_basic_value() 195 | .left() 196 | .unwrap() 197 | } 198 | }) 199 | } 200 | 201 | /// Lowers a MIR node. 202 | pub fn lower_node(&mut self, node: Spanned) { 203 | match node.into_value() { 204 | Node::Store(loc, val) => { 205 | let Some(val) = self.lower_expr(*val) else { 206 | return; 207 | }; 208 | let loc = self.locals[&loc].as_ref().unwrap().value; 209 | self.builder.build_store(loc, val); 210 | } 211 | Node::Register(loc, expr, ty) => { 212 | let local = self.lower_expr(expr).map(|expr| { 213 | let ty = self.lower_ty(&ty); 214 | let ptr = self.builder.build_alloca(ty, &loc.name()); 215 | self.builder.build_store(ptr, expr); 216 | 217 | Local { value: ptr, ty } 218 | }); 219 | self.locals.insert(loc, local); 220 | } 221 | Node::Local(id, ty) => { 222 | let local = ty.is_zst().not().then(|| { 223 | let ty = self.lower_ty(&ty); 224 | let ptr = self.builder.build_alloca(ty, &id.name()); 225 | Local { value: ptr, ty } 226 | }); 227 | self.locals.insert(id, local); 228 | } 229 | Node::Return(None | Some(Spanned(VOID, _))) => { 230 | self.builder.build_return(None); 231 | } 232 | Node::Return(Some(expr)) => { 233 | let expr = self.lower_expr(expr); 234 | self.builder 235 | .build_return(expr.as_ref().map(|expr| expr as &dyn BasicValue)); 236 | } 237 | Node::Jump(block) => { 238 | let block = *self.blocks.get(&block).unwrap(); 239 | self.builder.build_unconditional_branch(block); 240 | } 241 | Node::Branch(cond, then_block, else_block) => { 242 | let cond = self.lower_expr(cond).unwrap().into_int_value(); 243 | let then_block = *self.blocks.get(&then_block).unwrap(); 244 | let else_block = *self.blocks.get(&else_block).unwrap(); 245 | self.builder 246 | .build_conditional_branch(cond, then_block, else_block); 247 | } 248 | Node::Expr(expr) => { 249 | self.lower_expr(expr); 250 | } 251 | } 252 | } 253 | 254 | /// Lowers a block given its ID. 255 | pub fn lower_block(&mut self, block_id: BlockId) { 256 | let block = self.lowering_mut().blocks.remove(&block_id).unwrap(); 257 | self.builder 258 | .position_at_end(*self.blocks.get(&block_id).unwrap()); 259 | 260 | for node in block { 261 | self.lower_node(node) 262 | } 263 | } 264 | 265 | /// Lowers a THIR type value. 266 | /// 267 | /// TODO: THIR type value should be monomorphized in the THIR->MIR lowering stage. 268 | pub fn lower_ty(&self, ty: &Ty) -> BasicTypeEnum<'ctx> { 269 | use Ty::Primitive as P; 270 | 271 | match ty { 272 | P(PrimitiveTy::Int(_, width)) => BasicTypeEnum::IntType(self.get_int_type(*width)), 273 | P(PrimitiveTy::Bool) => BasicTypeEnum::IntType(self.context.bool_type()), 274 | _ => todo!(), 275 | } 276 | } 277 | 278 | /// Creates a new stack allocation instruction in the entry block of the function. 279 | fn create_entry_block_alloca( 280 | &self, 281 | name: &str, 282 | ty: impl BasicType<'ctx>, 283 | ) -> PointerValue<'ctx> { 284 | let builder = self.context.create_builder(); 285 | let entry = self.fn_value().get_first_basic_block().unwrap(); 286 | 287 | match entry.get_first_instruction() { 288 | Some(first_instr) => builder.position_before(&first_instr), 289 | None => builder.position_at_end(entry), 290 | } 291 | 292 | builder.build_alloca(ty, name) 293 | } 294 | 295 | #[inline] 296 | fn get_simple_attribute(&self, attr: &str) -> Attribute { 297 | self.context 298 | .create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0) 299 | } 300 | 301 | /// Registers the specified function into an LLVM `FunctionValue`. 302 | fn register_fn(&mut self, id: LookupId, func: &Func) -> Vec { 303 | let (names, param_tys) = func 304 | .params 305 | .iter() 306 | .filter_map(|(name, ty)| { 307 | ty.is_zst() 308 | .not() 309 | .then(|| (*name, BasicMetadataTypeEnum::from(self.lower_ty(ty)))) 310 | }) 311 | .unzip::<_, _, Vec<_>, Vec<_>>(); 312 | 313 | let fn_ty = match func.ret_ty.is_zst() { 314 | true => self.context.void_type().fn_type(¶m_tys, false), 315 | false => self.lower_ty(&func.ret_ty).fn_type(¶m_tys, false), 316 | }; 317 | 318 | // TODO: qualified name 319 | let name = func.name.to_string(); 320 | let fn_value = self.module.add_function(&name, fn_ty, None); 321 | self.functions.insert(id, fn_value); 322 | names 323 | } 324 | 325 | /// Compiles the body of the given function. 326 | fn compile_fn(&mut self, fn_value: FunctionValue<'ctx>, func: Func, names: Vec) { 327 | let block_ids = func.blocks.keys().copied().collect::>(); 328 | self.lowering = MaybeUninit::new(func); 329 | self.fn_value.write(fn_value); 330 | self.locals.clear(); 331 | self.blocks.clear(); 332 | self.increment = 0; 333 | 334 | // Create blocks 335 | for id in &block_ids { 336 | let bb = self 337 | .context 338 | .append_basic_block(self.fn_value(), &id.to_string()); 339 | self.blocks.insert(*id, bb); 340 | } 341 | self.builder.position_at_end(self.blocks[&BlockId::entry()]); 342 | 343 | for (param, ident) in self.fn_value().get_param_iter().zip(names) { 344 | let name = ident.to_string(); 345 | param.set_name(&name); 346 | 347 | let alloca = self.create_entry_block_alloca(&name, param.get_type()); 348 | self.builder.build_store(alloca, param); 349 | 350 | // Populate locals 351 | let local = Local { 352 | value: alloca, 353 | ty: param.get_type(), 354 | }; 355 | self.locals 356 | .insert(LocalId(ident, LocalEnv::Standard), Some(local)); 357 | } 358 | 359 | // Compile body 360 | block_ids.into_iter().for_each(|id| self.lower_block(id)); 361 | self.fn_value().print_to_string(); 362 | unsafe { self.lowering.assume_init_drop() }; 363 | 364 | // Verify and run optimizations 365 | if self.fn_value().verify(true) { 366 | self.fpm.run_on(&self.fn_value()); 367 | } else { 368 | unsafe { 369 | self.fn_value().delete(); 370 | } 371 | } 372 | } 373 | 374 | /// Compiles the specified `Function` in the given `Context` and using the specified `Builder`, `PassManager`, and `Module`. 375 | pub fn compile( 376 | context: &'ctx Context, 377 | builder: &'a Builder<'ctx>, 378 | pass_manager: &'a PassManager>, 379 | module: &'a Module<'ctx>, 380 | functions: HashMap, 381 | ) { 382 | let mut compiler = Self { 383 | context, 384 | builder, 385 | fpm: pass_manager, 386 | module, 387 | functions: HashMap::with_capacity(functions.len()), 388 | lowering: MaybeUninit::uninit(), 389 | fn_value: MaybeUninit::uninit(), 390 | locals: HashMap::new(), 391 | blocks: HashMap::new(), 392 | increment: 0, 393 | }; 394 | 395 | let mut names = Vec::with_capacity(functions.len()); 396 | for (id, func) in &functions { 397 | names.push(compiler.register_fn(*id, func)); 398 | } 399 | for ((id, func), names) in functions.into_iter().zip(names) { 400 | compiler.compile_fn(compiler.functions[&id], func, names); 401 | } 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! LLVM IR codegen from Terbium MIR. 2 | 3 | mod aot; 4 | 5 | pub use inkwell::{ 6 | context::Context, 7 | module::Module, 8 | targets::{CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine}, 9 | values::FunctionValue, 10 | OptimizationLevel, 11 | }; 12 | 13 | use inkwell::passes::PassManager; 14 | use mir::{Func, LookupId}; 15 | use std::collections::HashMap; 16 | 17 | pub fn compile_llvm(context: &Context, functions: HashMap) -> Module { 18 | let module = context.create_module("root"); 19 | let builder = context.create_builder(); 20 | 21 | // Create FPM 22 | let fpm = PassManager::create(&module); 23 | fpm.add_instruction_combining_pass(); 24 | fpm.add_reassociate_pass(); 25 | fpm.add_gvn_pass(); 26 | fpm.add_cfg_simplification_pass(); 27 | fpm.add_basic_alias_analysis_pass(); 28 | fpm.add_promote_memory_to_register_pass(); 29 | fpm.add_instruction_combining_pass(); 30 | fpm.add_reassociate_pass(); 31 | fpm.initialize(); 32 | 33 | aot::Compiler::compile(&context, &builder, &fpm, &module, functions); 34 | module 35 | } 36 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | chumsky = "0.9" 10 | internment = "0.7" 11 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Common utilities for the compiler. 2 | 3 | #![feature(lint_reasons)] 4 | #![feature(const_trait_impl)] 5 | 6 | pub mod span; 7 | 8 | /// Compilation target. 9 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 10 | pub enum Target { 11 | /// Compile to a native binary for the current platform with LLVM. Default for the `release` 12 | /// profile. 13 | Native, 14 | /// Compile to MIR and interpret it using MIRI. Default for the `debug` profile. Results in 15 | /// much faster compile times as it skips LLVM, but execution time is much slower. 16 | Mir, 17 | } 18 | 19 | /// Debug information to emit in the compiled binary. 20 | /// 21 | /// A higher debug level makes it easier to debug the compiled binary if any errors occur, but 22 | /// increases the size of the binary. 23 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 24 | pub enum DebugInfo { 25 | /// No debug information. Default for the `release` profile. 26 | None, 27 | /// Emit a low amount of debug information (call stacks, item definition line/column numbers). 28 | /// Default for all built-in profiles except `debug` and `release`. 29 | Low, 30 | /// Emit full debug information (everything in `Low`, plus local symbols, types, spans). 31 | /// Default for the `debug` profile. 32 | Full, 33 | } 34 | 35 | /// Compiler configuration. 36 | #[derive(Copy, Clone, Debug)] 37 | pub struct CompileOptions { 38 | /// Compilation target. 39 | pub target: Target, 40 | /// Debug information to emit in the compiled binary. Only applicable when compiling to a 41 | /// native binary and not MIR. 42 | pub debug_info: DebugInfo, 43 | /// Whether to run debug assertions. Defaults to `true` for all built-in profiles except 44 | /// `release`. 45 | pub debug_assertions: bool, 46 | } 47 | -------------------------------------------------------------------------------- /common/src/span.rs: -------------------------------------------------------------------------------- 1 | use internment::Intern; 2 | use std::{ 3 | borrow::Cow, 4 | fmt::{self, Debug, Display, Formatter}, 5 | ops::Range, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | /// Represents the span of a token or a node in the AST. Can be represented as [start, end). 10 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 11 | pub struct Span { 12 | /// The source of the span. 13 | pub src: Src, 14 | /// The index of the first byte of the span. 15 | pub start: usize, 16 | /// One more than the index of the last byte of the span. 17 | pub end: usize, 18 | } 19 | 20 | impl Display for Span { 21 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 22 | write!(f, "{}:{}..{}", self.src, self.start, self.end) 23 | } 24 | } 25 | 26 | impl Debug for Span { 27 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 28 | ::fmt(self, f) 29 | } 30 | } 31 | 32 | mod sealed { 33 | use std::ops::{Range, RangeInclusive}; 34 | 35 | pub trait RangeInclusiveExt { 36 | fn to_range(self) -> RangeInclusive; 37 | } 38 | 39 | impl RangeInclusiveExt for RangeInclusive { 40 | fn to_range(self) -> RangeInclusive { 41 | self 42 | } 43 | } 44 | 45 | impl RangeInclusiveExt for Range { 46 | #[allow(clippy::range_minus_one, reason = "required for conversion")] 47 | fn to_range(self) -> RangeInclusive { 48 | self.start..=(self.end - 1) 49 | } 50 | } 51 | } 52 | 53 | #[allow( 54 | clippy::len_without_is_empty, 55 | reason = "semantically incorrect to include is_empty method" 56 | )] 57 | impl Span { 58 | /// A placeholder span with no source. 59 | pub const PLACEHOLDER: Self = Self::new(Src::None, 0, 0); 60 | 61 | /// Creates a new span from the given start, end, and source. 62 | #[must_use] 63 | pub const fn new(src: Src, start: usize, end: usize) -> Self { 64 | Self { src, start, end } 65 | } 66 | 67 | /// Creates a new span from the given range and source. 68 | #[must_use] 69 | pub fn from_range(src: Src, range: R) -> Self { 70 | let range = range.to_range(); 71 | Self::new(src, *range.start(), *range.end() + 1) 72 | } 73 | 74 | /// Creates a single-length span. 75 | #[must_use] 76 | pub const fn single(src: Src, index: usize) -> Self { 77 | Self::new(src, index, index + 1) 78 | } 79 | 80 | /// Returns the length of the span. 81 | #[must_use] 82 | pub const fn len(&self) -> usize { 83 | self.end - self.start 84 | } 85 | 86 | /// Converts this span into a range. 87 | #[must_use] 88 | pub const fn range(&self) -> Range { 89 | self.start..self.end 90 | } 91 | 92 | /// Merges this span with another. 93 | #[must_use] 94 | pub fn merge(self, other: Self) -> Self { 95 | Self::new( 96 | self.src, 97 | self.start.min(other.start), 98 | self.end.max(other.end), 99 | ) 100 | } 101 | 102 | /// Merges this span with another, or leaves the span unchanged if the other span is `None`. 103 | #[must_use] 104 | pub fn merge_opt(self, other: Option) -> Self { 105 | other.map_or(self, |other| self.merge(other)) 106 | } 107 | 108 | /// Merges an iterator of spans. 109 | /// 110 | /// # Panics 111 | /// * If the iterator is empty. 112 | #[must_use] 113 | pub fn from_spans>(spans: I) -> Self { 114 | spans 115 | .into_iter() 116 | .reduce(Self::merge) 117 | .expect("Cannot create a span from an empty iterator") 118 | } 119 | 120 | /// Extends the span one character to the left. 121 | #[must_use] 122 | pub const fn extend_back(mut self) -> Self { 123 | self.start -= 1; 124 | self 125 | } 126 | 127 | /// Creates a new span from the same source. 128 | #[must_use] 129 | pub const fn get_span(&self, start: usize, end: usize) -> Self { 130 | Self::new(self.src, start, end) 131 | } 132 | 133 | /// Gets the last index of the span as another span. 134 | #[must_use] 135 | pub const fn last_span(&self) -> Self { 136 | self.get_span(self.end - 1, self.end) 137 | } 138 | 139 | /// Gets the span representing [0, 1). 140 | #[must_use] 141 | pub const fn begin(src: Src) -> Self { 142 | Self::new(src, 0, 1) 143 | } 144 | } 145 | 146 | impl chumsky::Span for Span { 147 | type Context = Src; 148 | type Offset = usize; 149 | 150 | fn new(src: Self::Context, range: Range) -> Self { 151 | Self::from_range(src, range) 152 | } 153 | 154 | fn context(&self) -> Self::Context { 155 | self.src 156 | } 157 | 158 | fn start(&self) -> Self::Offset { 159 | self.start 160 | } 161 | 162 | fn end(&self) -> Self::Offset { 163 | self.end 164 | } 165 | } 166 | 167 | /// A compound of a span and a value. 168 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 169 | pub struct Spanned(pub T, pub Span); 170 | 171 | impl Spanned { 172 | /// Returns the value. 173 | #[must_use] 174 | pub const fn value(&self) -> &T { 175 | &self.0 176 | } 177 | 178 | /// Returns the value as a mutable reference. 179 | #[must_use] 180 | pub fn value_mut(&mut self) -> &mut T { 181 | &mut self.0 182 | } 183 | 184 | /// Consumes this span and returns the inner value. 185 | #[must_use] 186 | #[allow(clippy::missing_const_for_fn, reason = "destructors can't be const")] 187 | pub fn into_value(self) -> T { 188 | self.0 189 | } 190 | 191 | /// Returns the span. 192 | #[must_use] 193 | pub const fn span(&self) -> Span { 194 | self.1 195 | } 196 | 197 | /// Converts from `Spanned` to `Spanned<&T>`. 198 | #[must_use] 199 | pub const fn as_ref(&self) -> Spanned<&T> { 200 | Spanned(&self.0, self.1) 201 | } 202 | 203 | /// Consumes and maps the inner value. 204 | #[must_use] 205 | #[allow( 206 | clippy::missing_const_for_fn, 207 | reason = "closure is not supposed to be const" 208 | )] 209 | pub fn map(self, f: impl FnOnce(T) -> U) -> Spanned { 210 | Spanned(f(self.0), self.1) 211 | } 212 | 213 | /// Consumes and maps the fallible inner value, mapping the span if the inner value is `Ok` 214 | /// and propagating the error otherwise. 215 | /// 216 | /// # Errors 217 | /// If the inner value is `Err`, the error is propagated. 218 | #[allow( 219 | clippy::missing_const_for_fn, 220 | reason = "closure is not supposed to be const" 221 | )] 222 | pub fn try_map(self, f: impl FnOnce(T) -> Result) -> Result, E> { 223 | Ok(Spanned(f(self.0)?, self.1)) 224 | } 225 | 226 | /// Nests the span into the inner value. 227 | #[must_use] 228 | pub fn nest(self, f: impl FnOnce(Self) -> U) -> Spanned { 229 | let span = self.span(); 230 | Spanned(f(self), span) 231 | } 232 | 233 | /// Returns a tuple (value, span). 234 | #[must_use] 235 | #[allow(clippy::missing_const_for_fn, reason = "destructors can't be const")] 236 | pub fn into_inner(self) -> (T, Span) { 237 | (self.0, self.1) 238 | } 239 | 240 | /// Returns a tuple (&value, span). 241 | #[must_use] 242 | pub const fn as_inner(&self) -> (&T, Span) { 243 | (&self.0, self.1) 244 | } 245 | } 246 | 247 | impl Spanned> { 248 | /// Converts from `Spanned>` to `Result, E>`, 249 | /// mapping the span if the inner value is `Ok` and propagating the error otherwise. 250 | /// 251 | /// # Errors 252 | /// If the inner value is `Err`, the error is propagated. 253 | pub fn transpose(self) -> Result, E> { 254 | self.0.map(|v| Spanned(v, self.1)) 255 | } 256 | } 257 | 258 | impl Display for Spanned { 259 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 260 | write!(f, "{}", self.0) 261 | } 262 | } 263 | 264 | impl Debug for Spanned { 265 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 266 | self.0.fmt(f)?; 267 | write!(f, " @ {}", self.1) 268 | } 269 | } 270 | 271 | pub trait SpannedExt { 272 | type Item; 273 | 274 | fn spanned(self, span: Span) -> Spanned; 275 | } 276 | 277 | impl SpannedExt for T { 278 | type Item = Self; 279 | 280 | fn spanned(self, span: Span) -> Spanned { 281 | Spanned(self, span) 282 | } 283 | } 284 | 285 | /// The source of a span. 286 | #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] 287 | pub enum Src { 288 | /// Unknown source, or source unapplicable. 289 | #[default] 290 | None, 291 | /// Read-eval-print loop. 292 | Repl, 293 | /// Path to file. 294 | Path(Intern>), 295 | } 296 | 297 | impl Display for Src { 298 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 299 | match self { 300 | Self::None => f.write_str(""), 301 | Self::Repl => f.write_str(""), 302 | Self::Path(path) => { 303 | if path.is_empty() { 304 | f.write_str("") 305 | } else { 306 | write!(f, "{}", path.clone().join("/")) 307 | } 308 | } 309 | } 310 | } 311 | } 312 | 313 | impl Src { 314 | /// Creates a source from the provided path. 315 | #[must_use = "this returns the created Src"] 316 | pub fn from_path(path: impl AsRef) -> Self { 317 | Self::Path(Intern::new( 318 | path.as_ref() 319 | .iter() 320 | .map(|chunk| chunk.to_string_lossy().into_owned()) 321 | .collect(), 322 | )) 323 | } 324 | 325 | /// Returns this source as a [`PathBuf`]. 326 | pub fn as_path(&self) -> PathBuf { 327 | match self { 328 | Self::Path(path) => path.iter().map(ToString::to_string).collect(), 329 | _ => self.to_string().into(), 330 | } 331 | } 332 | } 333 | 334 | /// A complete source provider, storing the [`Src`] and its content. 335 | #[derive(Clone)] 336 | pub struct Provider<'a>(pub Src, pub Cow<'a, str>); 337 | 338 | impl<'a> Provider<'a> { 339 | /// Creates a new provider with the given source and content. 340 | pub fn new(src: Src, content: impl Into>) -> Self { 341 | Self(src, content.into()) 342 | } 343 | 344 | /// The source of the provider. 345 | #[must_use] 346 | pub const fn src(&self) -> Src { 347 | self.0 348 | } 349 | 350 | /// The content of the provider. 351 | #[must_use] 352 | pub fn content(&'a self) -> &'a str { 353 | self.1.as_ref() 354 | } 355 | 356 | /// The "eof" span of the provider content. 357 | #[must_use] 358 | pub fn eof(&self) -> Span { 359 | Span::single(self.src(), self.1.chars().count()) 360 | } 361 | } 362 | 363 | impl Provider<'static> { 364 | /// Resolves a provider from a file path. 365 | /// 366 | /// # Errors 367 | /// * If the file cannot be read. 368 | pub fn read_from_file(path: impl AsRef) -> std::io::Result { 369 | Ok(Self( 370 | Src::from_path(path.as_ref()), 371 | Cow::Owned(std::fs::read_to_string(path)?), 372 | )) 373 | } 374 | } 375 | 376 | /// Resolves a provider from a file path at compile-time. 377 | #[macro_export] 378 | macro_rules! include_provider { 379 | ($path:literal $(,)?) => {{ 380 | Provider(Src::from_path($path), include_str!($path)) 381 | }}; 382 | } 383 | 384 | pub use include_provider; 385 | -------------------------------------------------------------------------------- /diagnostics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "diagnostics" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | common = { path = "../common" } 10 | yansi = "0.5" -------------------------------------------------------------------------------- /diagnostics/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Terbium diagnostic reporting. 2 | 3 | #![feature(let_chains)] 4 | 5 | use common::span::Span; 6 | 7 | pub mod span; 8 | pub mod write; 9 | 10 | /// The severity of a diagnostic. 11 | #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] 12 | pub enum Severity { 13 | Error(usize), 14 | Warning(&'static str), 15 | #[default] 16 | Info, 17 | } 18 | 19 | /// A diagnostic report. 20 | #[derive(Clone, Debug, Default)] 21 | pub struct Diagnostic { 22 | /// The severity of the diagnostic. 23 | pub severity: Severity, 24 | /// The message of the diagnostic. 25 | pub message: String, 26 | /// The diagnostic sections. 27 | pub sections: Vec, 28 | /// Extra lines at the end of the diagnostic. 29 | pub end: Vec<(Option, String)>, 30 | /// If `Some`, the diagnostic will be wrapped to this number of columns. 31 | pub wrap: Option, 32 | } 33 | 34 | impl Diagnostic { 35 | pub fn new(severity: Severity, message: impl ToString) -> Self { 36 | Self { 37 | severity, 38 | message: message.to_string(), 39 | sections: Vec::new(), 40 | end: Vec::new(), 41 | wrap: None, 42 | } 43 | } 44 | 45 | pub fn with_section(mut self, section: Section) -> Self { 46 | self.sections.push(SectionKind::Standard(section)); 47 | self 48 | } 49 | 50 | pub fn with_fix(mut self, fix: Fix) -> Self { 51 | self.sections.push(SectionKind::Fix(fix)); 52 | self 53 | } 54 | 55 | pub fn with_end(mut self, label: Option<&str>, message: impl ToString) -> Self { 56 | self.end 57 | .push((label.map(ToString::to_string), message.to_string())); 58 | self 59 | } 60 | 61 | pub fn with_help(self, help: impl ToString) -> Self { 62 | self.with_end(Some("help"), help.to_string()) 63 | } 64 | 65 | pub fn with_note(self, note: impl ToString) -> Self { 66 | self.with_end(Some("note"), note.to_string()) 67 | } 68 | 69 | pub fn wrap_to(mut self, cols: usize) -> Self { 70 | self.wrap = Some(cols); 71 | self 72 | } 73 | } 74 | 75 | #[derive(Clone, Debug)] 76 | pub enum SectionKind { 77 | Standard(Section), 78 | Fix(Fix), 79 | } 80 | 81 | impl SectionKind { 82 | pub fn full_span(&self) -> Span { 83 | match self { 84 | Self::Standard(s) => s.span.unwrap_or_else(|| { 85 | Span::from_spans( 86 | s.labels 87 | .iter() 88 | .map(|lbl| lbl.context_span.unwrap_or(lbl.span)), 89 | ) 90 | }), 91 | Self::Fix(f) => f.action.span(), 92 | } 93 | } 94 | 95 | pub const fn explicit_span(&self) -> Option { 96 | match self { 97 | Self::Standard(s) => s.span, 98 | Self::Fix(f) => f.span, 99 | } 100 | } 101 | 102 | pub fn note(&self) -> Option<&str> { 103 | match self { 104 | Self::Standard(s) => s.note.as_deref(), 105 | Self::Fix(f) => f.note.as_deref(), 106 | } 107 | } 108 | 109 | pub fn inner_context_spans(&self) -> Vec { 110 | match self { 111 | Self::Standard(s) => s.labels.iter().map(Label::full_span).collect(), 112 | Self::Fix(f) => vec![f.action.span()], 113 | } 114 | } 115 | } 116 | 117 | /// A diagnostic section, which previews a section of code. 118 | #[derive(Clone, Debug)] 119 | pub struct Section { 120 | /// The header of the section. 121 | pub header: Option, 122 | /// The span of the section. If no span is provided, the lines will be automatically determined 123 | /// by the labels. 124 | pub span: Option, 125 | /// Labels added to the section. 126 | pub labels: Vec