├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── extensions.json └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── differences.md └── promo.svg ├── examples └── helloworld.tal ├── rust-toolchain ├── src ├── anomalies.rs ├── bin │ └── ruxnasm │ │ ├── argument_parser.rs │ │ ├── main.rs │ │ ├── reader.rs │ │ ├── reporter │ │ ├── diagnostic.rs │ │ ├── display.rs │ │ ├── file.rs │ │ └── mod.rs │ │ └── writer.rs ├── emitter.rs ├── instruction.rs ├── lib.rs ├── scanner.rs ├── span.rs ├── token.rs ├── tokenizer │ ├── hex_number.rs │ └── mod.rs ├── traits.rs └── walker.rs └── tests ├── generator ├── Cargo.toml └── src │ ├── lib.rs │ ├── test.rs │ ├── tests.rs │ ├── tests_mod.rs │ └── utils.rs └── integration.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - 'docs/**' 9 | - 'README.md' 10 | pull_request: 11 | branches: 12 | - main 13 | paths-ignore: 14 | - 'docs/**' 15 | - 'README.md' 16 | 17 | jobs: 18 | build: 19 | runs-on: ${{ matrix.os }} 20 | 21 | strategy: 22 | matrix: 23 | os: [ubuntu-latest, windows-latest, macos-latest] 24 | include: 25 | - os: ubuntu-latest 26 | ARCHIVE_NAME: ruxnasm-x86_64-unknown-linux-gnu.tar.gz 27 | - os: windows-latest 28 | ARCHIVE_NAME: ruxnasm-x86_64-pc-windows-msvc.zip 29 | - os: macos-latest 30 | ARCHIVE_NAME: ruxnasm-x86_64-apple-darwin.tar.gz 31 | 32 | steps: 33 | - name: Checkout sources 34 | uses: actions/checkout@v2 35 | - name: Install Rust toolchain 36 | uses: actions-rs/toolchain@v1 37 | - name: Build Ruxnasm 38 | run: cargo build --release 39 | - name: Bundle Ruxnasm 40 | if: matrix.os == 'windows-latest' 41 | run: Compress-Archive -CompressionLevel Optimal -Force -Path target/release/ruxnasm.exe -DestinationPath ${{ matrix.ARCHIVE_NAME }} 42 | - name: Bundle Ruxnasm 43 | if: matrix.os != 'windows-latest' 44 | run: tar -C target/release -czf ${{ matrix.ARCHIVE_NAME }} ruxnasm 45 | - name: Upload artifact 46 | uses: actions/upload-artifact@v2 47 | with: 48 | name: ${{ matrix.ARCHIVE_NAME }} 49 | path: ${{ matrix.ARCHIVE_NAME }} 50 | 51 | test: 52 | runs-on: ubuntu-latest 53 | 54 | steps: 55 | - name: Checkout sources 56 | uses: actions/checkout@v2 57 | with: 58 | submodules: 'recursive' 59 | - name: Install Rust toolchain 60 | uses: actions-rs/toolchain@v1 61 | - name: Test Ruxnasm 62 | run: cargo test 63 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, windows-latest, macos-latest] 15 | include: 16 | - os: ubuntu-latest 17 | ARCHIVE_NAME: ruxnasm-x86_64-unknown-linux-gnu.tar.gz 18 | - os: windows-latest 19 | ARCHIVE_NAME: ruxnasm-x86_64-pc-windows-msvc.zip 20 | - os: macos-latest 21 | ARCHIVE_NAME: ruxnasm-x86_64-apple-darwin.tar.gz 22 | 23 | steps: 24 | - name: Get tag 25 | id: tag 26 | uses: dawidd6/action-get-tag@v1 27 | - name: Checkout sources 28 | uses: actions/checkout@v2 29 | - name: Install Rust toolchain 30 | uses: actions-rs/toolchain@v1 31 | - name: Build Ruxnasm 32 | run: cargo build --release 33 | - name: Bundle Ruxnasm 34 | if: matrix.os == 'windows-latest' 35 | run: Compress-Archive -CompressionLevel Optimal -Force -Path target/release/ruxnasm.exe -DestinationPath ${{ matrix.ARCHIVE_NAME }} 36 | - name: Bundle Ruxnasm 37 | if: matrix.os != 'windows-latest' 38 | run: tar -C target/release -czf ${{ matrix.ARCHIVE_NAME }} ruxnasm 39 | - name: Upload release 40 | uses: svenstaro/upload-release-action@v2 41 | with: 42 | repo_token: ${{ secrets.GITHUB_TOKEN }} 43 | file: ${{ matrix.ARCHIVE_NAME }} 44 | asset_name: ${{ matrix.ARCHIVE_NAME }} 45 | tag: ${{ github.ref }} 46 | overwrite: true 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.swp 3 | *.bin 4 | *.rom 5 | *.hex 6 | *.chr 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/suite"] 2 | path = tests/suite 3 | url = git@github.com:karolbelina/uxntal-test-suite.git 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "matklad.rust-analyzer", 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": ["bin"], 3 | } 4 | -------------------------------------------------------------------------------- /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 = "ansi_term" 7 | version = "0.12.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.44" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" 19 | 20 | [[package]] 21 | name = "cfg-if" 22 | version = "1.0.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 25 | 26 | [[package]] 27 | name = "codespan-reporting" 28 | version = "0.11.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 31 | dependencies = [ 32 | "termcolor", 33 | "unicode-width", 34 | ] 35 | 36 | [[package]] 37 | name = "ctor" 38 | version = "0.1.21" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" 41 | dependencies = [ 42 | "quote", 43 | "syn", 44 | ] 45 | 46 | [[package]] 47 | name = "diff" 48 | version = "0.1.12" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" 51 | 52 | [[package]] 53 | name = "generator" 54 | version = "0.0.0" 55 | dependencies = [ 56 | "anyhow", 57 | "proc-macro2", 58 | "quote", 59 | "syn", 60 | ] 61 | 62 | [[package]] 63 | name = "output_vt100" 64 | version = "0.1.2" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" 67 | dependencies = [ 68 | "winapi", 69 | ] 70 | 71 | [[package]] 72 | name = "pretty-hex" 73 | version = "0.2.1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" 76 | 77 | [[package]] 78 | name = "pretty_assertions" 79 | version = "1.0.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" 82 | dependencies = [ 83 | "ansi_term", 84 | "ctor", 85 | "diff", 86 | "output_vt100", 87 | ] 88 | 89 | [[package]] 90 | name = "proc-macro2" 91 | version = "1.0.27" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 94 | dependencies = [ 95 | "unicode-xid", 96 | ] 97 | 98 | [[package]] 99 | name = "quote" 100 | version = "1.0.9" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 103 | dependencies = [ 104 | "proc-macro2", 105 | ] 106 | 107 | [[package]] 108 | name = "ruxnasm" 109 | version = "0.2.0" 110 | dependencies = [ 111 | "codespan-reporting", 112 | "generator", 113 | "pretty-hex", 114 | "pretty_assertions", 115 | "test-case", 116 | ] 117 | 118 | [[package]] 119 | name = "syn" 120 | version = "1.0.80" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "unicode-xid", 127 | ] 128 | 129 | [[package]] 130 | name = "termcolor" 131 | version = "1.1.2" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 134 | dependencies = [ 135 | "winapi-util", 136 | ] 137 | 138 | [[package]] 139 | name = "test-case" 140 | version = "1.1.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "956044ef122917dde830c19dec5f76d0670329fde4104836d62ebcb14f4865f1" 143 | dependencies = [ 144 | "cfg-if", 145 | "proc-macro2", 146 | "quote", 147 | "syn", 148 | "version_check", 149 | ] 150 | 151 | [[package]] 152 | name = "unicode-width" 153 | version = "0.1.8" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 156 | 157 | [[package]] 158 | name = "unicode-xid" 159 | version = "0.2.2" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 162 | 163 | [[package]] 164 | name = "version_check" 165 | version = "0.9.3" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 168 | 169 | [[package]] 170 | name = "winapi" 171 | version = "0.3.9" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 174 | dependencies = [ 175 | "winapi-i686-pc-windows-gnu", 176 | "winapi-x86_64-pc-windows-gnu", 177 | ] 178 | 179 | [[package]] 180 | name = "winapi-i686-pc-windows-gnu" 181 | version = "0.4.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 184 | 185 | [[package]] 186 | name = "winapi-util" 187 | version = "0.1.5" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 190 | dependencies = [ 191 | "winapi", 192 | ] 193 | 194 | [[package]] 195 | name = "winapi-x86_64-pc-windows-gnu" 196 | version = "0.4.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 199 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ruxnasm" 3 | version = "0.2.0" 4 | authors = ["Karol Belina "] 5 | edition = "2018" 6 | description = "Alternative Uxntal assembler focused on error reporting" 7 | readme = "README.md" 8 | repository = "https://github.com/karolbelina/ruxnasm" 9 | license = "MIT" 10 | keywords = ["assembler", "uxn", "uxntal"] 11 | categories = ["command-line-utilities", "compilers"] 12 | exclude = [".github", ".vscode", "docs"] 13 | 14 | [workspace] 15 | members = ["tests/generator"] 16 | 17 | [[bin]] 18 | name = "ruxnasm" 19 | required-features = ["bin"] 20 | 21 | [features] 22 | default = ["bin"] 23 | # Feature required for the Ruxnasm binary. Should be disabled when depending on 24 | # Ruxnasm as a library. 25 | bin = ["codespan-reporting"] 26 | 27 | [dependencies] 28 | codespan-reporting = { version = "0.11.1", optional = true } 29 | 30 | [dev-dependencies] 31 | generator = { path = "tests/generator" } 32 | pretty_assertions = "1.0.0" 33 | pretty-hex = "0.2" 34 | test-case = "1.1" 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Karol Belina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > This project is out-of-date and has been discontinued. 3 | 4 | # ruxnasm 5 | 6 | 7 | 8 | [![CI](https://github.com/karolbelina/ruxnasm/actions/workflows/ci.yml/badge.svg)](https://github.com/karolbelina/ruxnasm/actions/workflows/ci.yml) 9 | [![crates.io](https://img.shields.io/crates/v/ruxnasm.svg)](https://crates.io/crates/ruxnasm) 10 | [![docs.rs](https://img.shields.io/badge/docs.rs-latest-informational.svg)](https://docs.rs/ruxnasm) 11 | 12 | Ruxnasm is an assembler for [Uxntal][uxntal] — a programming language for the [Uxn][uxn] stack-machine by [Hundred Rabbits](https://github.com/hundredrabbits). Ruxnasm strives to be an alternative to [Uxnasm][uxnasm], featuring more user-friendly error reporting, warnings, and helpful hints, reminiscent of those seen in modern compilers for languages such as Rust or Elm. 13 | 14 | ## Quick start 15 | 16 | ```console 17 | cargo run -- examples/helloworld.tal helloworld.rom 18 | uxncli helloworld.rom 19 | ``` 20 | 21 | ## Compatibility with Uxnasm 22 | 23 | Currently, Uxntal doesn't have an official language specification, which means it is defined by the programs it's processed by — the assemblers. The official assembler for Uxntal is [Uxnasm][uxnasm], written in ANSI C. Ruxnasm does not try to be a 1:1 reimplementation of Uxnasm; it's too opinionated to be so. Instead, it tries to define a more elegant and modern version of Uxntal, while at the same time preserving the software already written with Uxnasm in mind. 24 | 25 | Although they are _mostly_ the same, there are programs that are valid in Uxnasm and invalid in Ruxnasm and vice versa. This means that the language defined by Ruxnasm is neither a subset nor a superset of the language defined by Uxnasm. All known differences between Ruxnasm and Uxnasm have been documented in the [docs/differences.md](docs/differences.md) file and are kept up-to-date as the project is being developed. 26 | 27 | Interacting with Uxnasm from the command line is no different for Ruxnasm — just append an "r" at the start. 28 | 29 | ## Installation 30 | 31 | ### From binaries 32 | 33 | Check out the [releases page](https://github.com/karolbelina/ruxnasm/releases) for prebuilt releases of Ruxnasm for various operating systems. If you want to get the most recent Linux, Windows, or macOS build, check out the artifacts of the latest CI workflow run on the [actions page](https://github.com/karolbelina/ruxnasm/actions). 34 | 35 | ### From source 36 | 37 | You can build and install Ruxnasm from source using Cargo — Rust's package manager. You can get it by installing the most recent release of [Rust](https://www.rust-lang.org/). Both of the methods listed below should build the Ruxnasm binary and place it in Cargo installation root's `bin` folder (`~/.cargo/bin` as the default, check out [this guide](https://doc.rust-lang.org/cargo/commands/cargo-install.html) for more information). 38 | 39 | - #### From the Git repository 40 | 41 | To build and install the most recent version of Ruxnasm, clone the repository, `cd` into it, and run 42 | ```console 43 | cargo install --path . 44 | ``` 45 | - #### From crates.io 46 | 47 | Ruxnasm can be fetched from the [crates.io](https://crates.io/crates/ruxnasm) package registry. To build and install the most recent release of Ruxnasm, run 48 | ```console 49 | cargo install ruxnasm 50 | ``` 51 | from anywhere. 52 | 53 | ## Library 54 | 55 | Besides being a command-line tool, Ruxnasm is also available as a library for the Rust programming language. It exposes the `assemble` function, which can turn a string with an Uxntal program into an Uxn binary. 56 | ```rust 57 | pub fn assemble(source: &[u8]) -> Result> 58 | ``` 59 | The library is available on [crates.io](https://crates.io/crates/ruxnasm) and can be included in your Cargo-enabled project like this: 60 | ```toml 61 | [dependencies] 62 | ruxnasm = { version = "*", default-features = false } # Disable the default "bin" feature 63 | ``` 64 | and then used in your code like this: 65 | ```rust 66 | let (binary, _) = ruxnasm::assemble(b"|0100 #02 #03 ADD").unwrap(); 67 | 68 | assert_eq!(binary, [0x01, 0x02, 0x01, 0x03, 0x18]); 69 | ``` 70 | The code above unwraps the result, but could just as well handle all the errors and warnings returned from the `assemble` function in case there were any. 71 | 72 | ## License 73 | 74 | This software is licensed under the MIT license. 75 | 76 | See the [LICENSE](LICENSE) file for more details. 77 | 78 | [uxn]: https://100r.co/site/uxn.html 79 | [uxntal]: https://wiki.xxiivv.com/site/uxntal.html 80 | [uxnasm]: https://git.sr.ht/~rabbits/uxn/tree/main/item/src/uxnasm.c 81 | -------------------------------------------------------------------------------- /docs/differences.md: -------------------------------------------------------------------------------- 1 | # Differences between Uxnasm and Ruxnasm 2 | 3 | This file lists all known differences between Uxnasm and Ruxnasm. These features are either already implemented or will be implemented in the future. Please note that the error codes (Exxxx) are not final yet. 4 | 5 | #### Validity symbols 6 | 7 | - / — valid in Uxnasm, invalid in Ruxnasm 8 | - \ — invalid in Uxnasm, valid in Ruxnasm 9 | - \- — no change in validity or not applicable 10 | 11 | ## Errors 12 | 13 | | N | Uxnasm | Ruxnasm | V[*](#validity-symbols) | 14 | |:-:|--------|---------|:-----------------------:| 15 | | 1 | Ignores any misplaced closing parentheses i.e. the ones that are not a part of any comment because they don't have a matching opening parenthesis. | A misplaced closing parenthesis results in error E0003. | / | 16 | | 2 | A comment doesn't have to be closed at the end of the file. | Any unclosed comments, i.e. opening parentheses that do not have a matching closing parenthesis, result in error E0004. | / | 17 | | 3 | Omitting the sublabel name after a sublabel definition rune is valid and results in a `label/` label, where `label` is a name of the previously defined label. | Omitting the sublabel name after a sublabel definition rune results in error E0006. | / | 18 | | 4 | Allows you to omit a hexadecimal number after the relative pad rune and treats the value as zero. | Reports error E0001 if no hexadecimal number after the relative pad rune is provided. | / | 19 | | 5 | Allows you to provide a string of characters that is not a valid hexadecimal number after the relative pad rune, in which case it ignores the invalid digits. | Reports error E0002 if the string of characters after the relative pad rune is not a valid hexadecimal number (i.e. there are invalid hexadecimal digits in the number string). | / | 20 | | 6 | Omitting the label or a sublabel name in a sublabel path after address runes is valid and specifies a `/sublabel` and `label/` label respectively. | Omitting the label or a sublabel name in a sublabel path after address runes results in error E0008 for labels and error E0009 for sublabels. | / | 21 | | 7 | Label names can have a "`&`" as the first character. This is valid code:
@&label &label .&label
| Label names cannot have a "`&`" as the first character, as it clashes with the `.&label` syntax. Any such label name results in error E0010. | / | 22 | | 8 | Allows you to include "`/`" characters in label and sublabel names. | "`/`" characters in label and sublabel names are invalid, as they make sublabel paths unnecessarily ambiguous, and result in errors E0011 for labels and errors E0012 for sublabels. | / | 23 | | 9 | Omitting the character after a raw character rune is valid and becomes a raw byte with value 0. | Omitting the character after a raw character rune results in error E0021. | / | 24 | | 10 | Ignores all closing brackets. | Still ignores all closing brackets, but any misplaced closing bracket i.e. one that does not have a matching opening bracket results in error E0024. | / | 25 | | 11 | Ignores all opening brackets. | Still ignores all opening brackets, but any opening bracket that does not have a matching closing bracket results in error E0025. | / | 26 | | 12 | Recursive macros result in a segmentation fault when expanded. | Any instance of a direct or undirect recursion in macros is detected at assembly time (except when the recursive macro is never expanded) and reported as error E0026. See [Recursive macros](#recursive-macros) for the details. | - | 27 | | 13 | After a raw character rune, ignores all bytes after the first one. | More than one character or a multibyte Unicode character after a raw character rune results in error E0027. | / | 28 | | 14 | Using the `.&label` syntax without a previously defined label is valid and generates a label out of garbage memory. | Using the `.&label` syntax without a previously defined label results in error E0030. | / | 29 | | 15 | Defining a sublabel without a previously defined label is valid and generates a label out of garbage memory. | Defining a sublabel without a previously defined label results in error E0029. | / | 30 | | 16 | Sublabel paths can have more than one slash. | Sublabel paths with more than one slash are invalid and result in error E0014. | / | 31 | | 17 | Programs smaller than one page (256 bytes) result in an integer underflow in the program length variable during the trimming process, which outputs a 1.2 MB binary full of zeros. For programs equal or larger than 256 bytes, any bytes in the zeroth page are trimmed off. | Any bytes in the zeoth page (first 256 bytes) of the binary result in error E0031. | / | 32 | | 18 | Programs longer that 65536 bytes result in an integer overflow — the program length wraps back to zero. | Programs longer than 65536 bytes result in error E0028. | / | 33 | | 19 | Specifying a hexadecimal number with more than 4 digits after a pad rune is valid. | Specifying a hexadecimal number with more than 4 digits after a pad rune results in error E0032. | / | 34 | 35 | ## Quirks 36 | 37 | | N | Uxnasm | Ruxnasm | V[*](#validity-symbols) | 38 | |:-:|--------|---------|:-----------------------:| 39 | | 20 | Opening and closing parentheses (i.e. comments) allow you only to enable or disable the parsing. | Comments can be nested. | - | 40 | | 21 | Tokens are split by whitespace. See [Delimiters](#delimiters) for the details and implications. | Splits the tokens not only by whitespace but by the delimiters as well. See [Delimiters](#delimiters) for the details. | - | 41 | | 22 | Opening brace after a macro definition can be omitted. | A macro definition not directly followed by an opening brace is a valid, but empty macro. | - | 42 | | 23 | Label definitions, sublabel definitions, macro definitions, and absolute pads are not allowed in macros. | Definitions and absolute pads are valid in macros. See [Definitions and absolute pads in macros](#definitions-and-absolute-pads-in-macros) for the details. | \ | 43 | | 24 | Comments and brackets are not allowed in macros. | Comments and brackets are valid in macros: | \ | 44 | | 25 | Attempting to define a label that is a valid hexadecimal number or a valid instruction results in "Label name is hex number" and "Label name is invalid" errors, respectively. | Labels can be valid hexadecimal numbers or instructions. Labels must be preceded by an address rune — they don't clash with numbers or instructions in any way. | \ | 45 | | 26 | Labels and sublabels share the same namespace and the names can have at most 64 characters. Sublabel names are cut off if the sum of the lengths of (1) the scope name, (2) the slash character, and (3) the sublabel name is greater than 64 characters. | Labels and sublabels have separate namespaces and the labels are defined as a pair consisting of the scope name and the sublabel name with 64 characters each. | - | 46 | | 27 | `#x` syntax for specyfing a literal ASCII byte is invalid and results in an "Invalid hexadecimal literal" error. | `#x` syntax produces a valid literal ASCII byte code (LIT byte followed by the ASCII byte based on the character after the `#` rune). | \ | 47 | 48 | ## Examples 49 | 50 | ### Delimiters 51 | 52 | Uxnasm splits the tokens by whitespace (spaces, tabs, and newlines), which means that for tokens starting with "`(`", "`)`", "`[`", "`]`", "`{`", or "`}`" (tokens in which only the first character matters), any character between the start of the token and a whitespace is ignored. This has some implications regarding comments: the string "`1 (2) 3 ( 4 ) 5 ( 6 )7`" is split into tokens \[`1`, `(2)`, `3`, `(`, `4`, `)`, `5`, `(`, `6`, `)7`\], which are interpreted as \[`1`, `(`, `3`, `(`, `4`, `)`, `5`, `(`, `6`, `)`\], and by taking into account the comment skipping, the final list of tokens is \[`1`, `5`\]. I consider this slightly unintuitive, so Ruxnasm (additionally to whitespaces) separates the tokens by the delimiters: the "`(`", "`)`", "`[`", "`]`", "`{`", and "`}`" characters. In Ruxnasm, the same string "`1 (2) 3 ( 4 ) 5 ( 6 )7`" is tokenized into \[`1`, `3`, `5`, `7`\]. 53 | 54 | ### Definitions and absolute pads in macros 55 | 56 | Ruxnasm allows you to put any token in a macro definition, including other macro definitions, label and sublabel definitions, as well as absolute pads. This has the following implications: 57 | - Macro definitions, label definitions, and absolute pads are pretty much useless. They are obviously useless if the macro is never expanded, and also useless when the macro is expanded more than one time — this causes an error (defining a macro/label with the same name multiple, or a memory overwrite in case of the absolute pad). They are fine if the macro is expanded exactly once, but at this point, you might as well put them outside of the macro. 58 | 59 | Macros can be put inside other macros. For example, this code: 60 | ```uxntal 61 | %macro1 { %macro2 { ADD } } 62 | macro1 macro2 63 | ``` 64 | results in a single `ADD` instruction. 65 | - Sublabel definitions have an interesting use case: 66 | ```uxntal 67 | %define-sublabels { &one $1 &two $1 &three $1 } 68 | @Label1 define-sublabels @Label2 define-sublabels 69 | .Label1/two 70 | ``` 71 | 72 | ### Recursive macros 73 | 74 | Uxnasm allows you to put an macro expansion inside of the definition of the same macro, which is the simplest (direct) case of a recursive macro. 75 | ```uxntal 76 | %macro { macro } 77 | macro 78 | ``` 79 | This causes an infinite recursion and a stack overflow during the parsing process. A slightly more intricate example of recursion is the indirect recursion, which involves several macros: 80 | ```uxntal 81 | %macro-a { macro-b } 82 | %macro-b { macro-a } 83 | macro-a 84 | ``` 85 | Ruxnasm detects both cases of recursion during the assembly and reports an appropriate error. 86 | -------------------------------------------------------------------------------- /docs/promo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/helloworld.tal: -------------------------------------------------------------------------------- 1 | ( dev/console ) 2 | 3 | |10 @Console [ &pad $8 &char ] 4 | 5 | ( init ) 6 | 7 | |0100 ( -> ) 8 | 9 | ;hello-world 10 | 11 | &loop 12 | ( send ) LDAk .Console/char DEO 13 | ( incr ) #0001 ADD2 14 | ( loop ) LDAk ,&loop JCN 15 | POP2 16 | 17 | BRK 18 | 19 | @hello-world "Hello 20 "World! 20 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | stable-2021-05-10 2 | -------------------------------------------------------------------------------- /src/anomalies.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | /// Enum representing every warning that can be reported from Ruxnasm. 4 | #[derive(Debug, Clone, PartialEq, Eq)] 5 | pub enum Warning { 6 | /// This warnings gets reported when a token is longer than 64 characters and must be cut off. 7 | /// 8 | /// # Example 9 | /// 10 | /// ```uxntal 11 | /// @a-really-long-label-name-like-seriously-this-is-so-long-why-would-anyone-do-this 12 | /// ``` 13 | TokenTrimmed { 14 | /// Span of the cut off part of the token. 15 | span: Range, 16 | }, 17 | /// This warning gets reported when an instruction mode is defined multiple times for a 18 | /// single instruction, which is valid, but unnecessary. 19 | /// 20 | /// # Example 21 | /// 22 | /// ```uxntal 23 | /// ADD2k2 24 | /// ``` 25 | InstructionModeDefinedMoreThanOnce { 26 | /// Character representing the instruction mode. 27 | instruction_mode: char, 28 | /// The whole instruction. 29 | instruction: String, 30 | /// Span of the unnecessary instruction mode character. 31 | span: Range, 32 | /// Span of the instruction mode character defined for the first time. 33 | other_span: Range, 34 | }, 35 | /// This warning gets reported when a macro is never used. 36 | /// 37 | /// # Example 38 | /// 39 | /// ```uxntal 40 | /// %macro { #0001 } 41 | /// ``` 42 | MacroUnused { 43 | /// Name of the unused macro. 44 | name: String, 45 | /// Span of the macro definition. 46 | span: Range, 47 | }, 48 | /// This warning gets reported when a label is never used. 49 | /// 50 | /// # Example 51 | /// 52 | /// ```uxntal 53 | /// @label 54 | /// ``` 55 | LabelUnused { 56 | /// Name of the unused label. 57 | name: String, 58 | /// Span of the label definition. 59 | span: Range, 60 | }, 61 | } 62 | 63 | /// Enum representing every error that can be reported from Ruxnasm. 64 | #[derive(Debug, Clone, PartialEq, Eq)] 65 | pub enum Error { 66 | /// This error gets reported when an opening parenthesis is not closed i.e. it has 67 | /// no matching closing parenthesis. 68 | /// 69 | /// # Example 70 | /// 71 | /// ```uxntal 72 | /// ( 73 | /// ``` 74 | NoMatchingClosingParenthesis { 75 | /// Span of the opening parenthesis with no matching closing parenthesis. 76 | span: Range, 77 | }, 78 | /// This error gets reported when a closing parenthesis has no matching opening 79 | /// parenthesis. 80 | /// 81 | /// # Example 82 | /// 83 | /// ```uxntal 84 | /// ) 85 | /// ``` 86 | NoMatchingOpeningParenthesis { 87 | /// Span of the closing parenthesis with no matching opening parenthesis. 88 | span: Range, 89 | }, 90 | /// This error gets reported when there is no macro name after a macro definition 91 | /// rune. 92 | /// 93 | /// # Example 94 | /// 95 | /// ```uxntal 96 | /// % 97 | /// ``` 98 | MacroNameExpected { 99 | /// Span of the macro definition rune. 100 | span: Range, 101 | }, 102 | /// This error gets reported when there is no label name after a label definition 103 | /// rune. 104 | /// 105 | /// # Example 106 | /// 107 | /// ```uxntal 108 | /// @ 109 | /// ``` 110 | LabelExpected { 111 | /// Span of the label definition rune. 112 | span: Range, 113 | }, 114 | /// This error gets reported when there is no sublabel name after a sublabel 115 | /// definition rune. 116 | /// 117 | /// # Example 118 | /// 119 | /// ```uxntal 120 | /// & 121 | /// ``` 122 | SublabelExpected { 123 | /// Span of the sublabel definition rune. 124 | span: Range, 125 | }, 126 | /// This error gets reported when a label or a sublabel name contains a slash 127 | /// character. 128 | /// 129 | /// # Example 130 | /// 131 | /// ```uxntal 132 | /// @label/name 133 | /// ``` 134 | SlashInLabelOrSublabel { 135 | /// Span of the slash in the label of sublabel. 136 | span: Range, 137 | }, 138 | /// This error gets reported when an identifier contains more than one slash 139 | /// character. 140 | /// 141 | /// # Example 142 | /// 143 | /// ```uxntal 144 | /// .label-name/sublabel/name 145 | /// ``` 146 | MoreThanOneSlashInIdentifier { 147 | /// Span of the slash in the identifier. 148 | span: Range, 149 | }, 150 | /// This error gets reported when a label name in a label definition has an 151 | /// ampersand as the first character. 152 | /// 153 | /// # Example 154 | /// 155 | /// ```uxntal 156 | /// @&label-name 157 | /// ``` 158 | AmpersandAtTheStartOfLabel { 159 | /// Span of the ampersand at the start of the label. 160 | span: Range, 161 | }, 162 | /// This error gets reported when there is no identifier after an address rune 163 | /// (literal zero-page address rune, literal relative address rune, literal 164 | /// absolute address runem, or raw address rune). 165 | /// 166 | /// # Example 167 | /// 168 | /// ```uxntal 169 | /// . 170 | /// ``` 171 | IdentifierExpected { 172 | /// Span of the address rune. 173 | span: Range, 174 | }, 175 | /// This error gets reported when there is no hexadecimal number after an 176 | /// absolute or relative pad rune. 177 | /// 178 | /// # Example 179 | /// 180 | /// ```uxntal 181 | /// | 182 | /// ``` 183 | HexNumberExpected { 184 | /// Span of the abolute or relative pad rune. 185 | span: Range, 186 | }, 187 | /// This error gets reported when there is no character or hexadecimal number 188 | /// after a literal hex rune. 189 | /// 190 | /// # Example 191 | /// 192 | /// ```uxntal 193 | /// # 194 | /// ``` 195 | HexNumberOrCharacterExpected { 196 | /// Span of the literal hex rune. 197 | span: Range, 198 | }, 199 | /// This error gets reported when there is no character after a character rune. 200 | /// 201 | /// # Example 202 | /// 203 | /// ```uxntal 204 | /// ' 205 | /// ``` 206 | CharacterExpected { 207 | /// Span of the character rune. 208 | span: Range, 209 | }, 210 | /// This error gets reported when there is more than one byte after a character 211 | /// rune. 212 | /// 213 | /// # Example 214 | /// 215 | /// ```uxntal 216 | /// 'characters 217 | /// ``` 218 | MoreThanOneByteFound { 219 | /// Sequence of bytes after the character rune. 220 | bytes: Vec, 221 | /// Span of the characters after the character rune. 222 | span: Range, 223 | }, 224 | /// This error gets reported when a hexadecimal number contains an invalid 225 | /// digit. 226 | /// 227 | /// # Example 228 | /// 229 | /// ```uxntal 230 | /// #00g0 231 | /// ``` 232 | HexDigitInvalid { 233 | /// The invalid digit. 234 | digit: char, 235 | /// The whole hexadecimal number with the invalid digit. 236 | number: String, 237 | /// Span of the hexadecimal number. 238 | span: Range, 239 | }, 240 | /// This error gets reported when a hexadecimal number after a literal hex 241 | /// rune has a length of 3, i.e. it is made out of exactly 3 hexadecimal digits. 242 | /// 243 | /// # Example 244 | /// 245 | /// ```uxntal 246 | /// #000 247 | /// ``` 248 | HexNumberUnevenLength { 249 | /// Length of the hexadecimal number. 250 | length: usize, 251 | /// The hexadecimal number. 252 | number: String, 253 | /// Span of the hexadecimal number. 254 | span: Range, 255 | }, 256 | /// This error gets reported when the hexadecimal number after a literal hex 257 | /// rune is longer than 4 hexadecimal digits. 258 | /// 259 | /// # Example 260 | /// 261 | /// ```uxntal 262 | /// #fffff 263 | /// ``` 264 | HexNumberTooLong { 265 | /// Length of the hexadecimal number. 266 | length: usize, 267 | /// The hexadecimal number. 268 | number: String, 269 | /// Span of the hexadecimal number. 270 | span: Range, 271 | }, 272 | /// This error gets reported when the macro name after a macro definition 273 | /// rune is a valid hexadecimal number i.e. it contains exactly 2 or 4 valid 274 | /// hexadecimal digits. 275 | /// 276 | /// # Example 277 | /// 278 | /// ```uxntal 279 | /// %01 280 | /// ``` 281 | MacroCannotBeAHexNumber { 282 | /// The hexadecimal number that was meant to be a macro name. 283 | number: String, 284 | /// Span of the hexadecimal number that was meant to be a macro name. 285 | span: Range, 286 | }, 287 | /// This error gets reported when the macro name after a macro definition 288 | /// rune is a valid instruction. 289 | /// 290 | /// # Example 291 | /// 292 | /// ```uxntal 293 | /// %ADD 294 | /// ``` 295 | MacroCannotBeAnInstruction { 296 | /// The instruction that was meant to be a macro name. 297 | instruction: String, 298 | /// Span of the instruction that was meant to be a macro name. 299 | span: Range, 300 | }, 301 | /// This error gets reported during an attempt to expand a macro that has not 302 | /// been previously defined. 303 | /// 304 | /// # Example 305 | /// 306 | /// ```uxntal 307 | /// macro 308 | /// ``` 309 | MacroUndefined { 310 | /// Name of the macro. 311 | name: String, 312 | /// Span of the macro invocation. 313 | span: Range, 314 | }, 315 | /// This error gets reported when a macro with the same name is defined 316 | /// multiple times. 317 | /// 318 | /// # Example 319 | /// 320 | /// ```uxntal 321 | /// %macro { ADD } 322 | /// %macro { ADD } 323 | /// ``` 324 | MacroDefinedMoreThanOnce { 325 | /// Name of the macro. 326 | name: String, 327 | /// Span of the current macro definition. 328 | span: Range, 329 | /// Span of the previous macro definition. 330 | other_span: Range, 331 | }, 332 | /// This error gets reported when a label with the same name is defined 333 | /// multiple times. 334 | /// 335 | /// # Example 336 | /// 337 | /// ```uxntal 338 | /// @label 339 | /// @label 340 | /// ``` 341 | LabelDefinedMoreThanOnce { 342 | /// Name of the label. 343 | name: String, 344 | /// Span of the current label definition. 345 | span: Range, 346 | /// Span of the previous label definition. 347 | other_span: Range, 348 | }, 349 | /// This error gets reported when an opening brace character is not directly 350 | /// preceded by a macro definition. 351 | /// 352 | /// # Example 353 | /// 354 | /// ```uxntal 355 | /// { ADD } 356 | /// ``` 357 | OpeningBraceNotAfterMacroDefinition { 358 | /// Span of the opening brace. 359 | span: Range, 360 | }, 361 | /// This error gets reported when a closing brace has no matching opening 362 | /// brace. 363 | /// 364 | /// # Example 365 | /// 366 | /// ```uxntal 367 | /// } 368 | /// ``` 369 | NoMatchingOpeningBrace { 370 | /// Span of the closing brace with no matching opening brace. 371 | span: Range, 372 | }, 373 | /// This error gets reported when an opening brace is not closed i.e. it has 374 | /// no matching closing brace. 375 | /// 376 | /// # Example 377 | /// 378 | /// ```uxntal 379 | /// %macro { 380 | /// ``` 381 | NoMatchingClosingBrace { 382 | /// Span of the opening brace with no matching closing brace. 383 | span: Range, 384 | }, 385 | /// This error gets reported during an attempt to define a sublabel, when 386 | /// no previous label has been defined. 387 | /// 388 | /// # Example 389 | /// 390 | /// ```uxntal 391 | /// &sublabel 392 | /// ``` 393 | SublabelDefinedWithoutScope { 394 | /// Name of the sublabel. 395 | name: String, 396 | /// Span of the sublabel definition. 397 | span: Range, 398 | }, 399 | /// This error gets reported when a closing bracket has no matching opening 400 | /// bracket. 401 | /// 402 | /// # Example 403 | /// 404 | /// ```uxntal 405 | /// ] 406 | /// ``` 407 | NoMatchingOpeningBracket { 408 | /// Span of the closing bracket with no matching opening bracket. 409 | span: Range, 410 | }, 411 | /// This error gets reported when an opening bracket is not closed i.e. it has 412 | /// no matching closing bracket. 413 | /// 414 | /// # Example 415 | /// 416 | /// ```uxntal 417 | /// [ 418 | /// ``` 419 | NoMatchingClosingBracket { 420 | /// Span of the opening bracket with no matching closing bracket. 421 | span: Range, 422 | }, 423 | /// This error wraps an error that has been reported from a macro definition. 424 | /// 425 | /// # Example 426 | /// 427 | /// ```uxntal 428 | /// %macro { #001 } 429 | /// macro 430 | /// ``` 431 | MacroError { 432 | /// The error that has been reported from a macro definition. 433 | original_error: Box, 434 | /// Span of the macro invocation. 435 | span: Range, 436 | }, 437 | /// This error gets reported during an attempt to reference a sublabel, when 438 | /// no previous label has been defined. 439 | /// 440 | /// # Example 441 | /// 442 | /// ```uxntal 443 | /// .&sublabel 444 | /// ``` 445 | SublabelReferencedWithoutScope { 446 | /// Name of the sublabel. 447 | name: String, 448 | /// Span of the sublabel reference. 449 | span: Range, 450 | }, 451 | /// This error gets reported during an attempt to reference a label that 452 | /// has not been defined. 453 | /// 454 | /// # Example 455 | /// 456 | /// ```uxntal 457 | /// .label 458 | /// ``` 459 | LabelUndefined { 460 | /// Name of the label. 461 | name: String, 462 | /// Span of the label reference. 463 | span: Range, 464 | }, 465 | /// This error gets reported during an attempt to reference a non-zero-page label 466 | /// after a literal zero-page address rune. 467 | /// 468 | /// # Example 469 | /// 470 | /// ```uxntal 471 | /// |0100 @label 472 | /// .label 473 | /// ``` 474 | AddressNotZeroPage { 475 | /// The actuall address that is not zero-page. 476 | address: u16, 477 | /// Name of the identifier that is referenced by the literal zero-page address. 478 | identifier: String, 479 | /// Span of the literal zero-page address. 480 | span: Range, 481 | }, 482 | /// This error gets reported during an attempt to reference a label that 483 | /// is too far to be a relative address after a literal relative address rune. 484 | /// 485 | /// # Example 486 | /// 487 | /// ```uxntal 488 | /// @label 489 | /// |0100 ,label 490 | /// ``` 491 | AddressTooFar { 492 | /// The distance in bytes from the literal relative address and the label definition. 493 | distance: usize, 494 | /// Name of the identifier that is referenced by the literal relative address. 495 | identifier: String, 496 | /// Span of the literal relative address. 497 | span: Range, 498 | /// Span of the label definition that is referenced by the literal relative address. 499 | other_span: Range, 500 | }, 501 | /// This error gets reported when there are bytes in the zeroth page (first 502 | /// 256 bytes) of the binary. 503 | /// 504 | /// # Example 505 | /// 506 | /// ```uxntal 507 | /// #01 #02 ADD 508 | /// ``` 509 | BytesInZerothPage { 510 | /// Span of the tokens in the zeroth page. 511 | span: Range, 512 | }, 513 | /// This error gets reported during an attempt to do an absolute pad 514 | /// to an address before the current address pointer. 515 | /// 516 | /// # Example 517 | /// 518 | /// ```uxntal 519 | /// #01 #02 ADD 520 | /// |0000 #02 #03 ADD 521 | /// ``` 522 | PaddedBackwards { 523 | /// The address at which the absolute pad is attempted. 524 | previous_pointer: usize, 525 | /// The address to which the absolute pad is attempted. 526 | desired_pointer: usize, 527 | /// Span of the absolute pad. 528 | span: Range, 529 | }, 530 | /// This error gets reported when the program size exceeds 65536 bytes. 531 | /// 532 | /// # Example 533 | /// 534 | /// ```uxntal 535 | /// |ffff #01 #02 ADD 536 | /// ``` 537 | ProgramTooLong { 538 | /// Span of the tokens that exceed the maximum size. 539 | span: Range, 540 | }, 541 | RecursiveMacro { 542 | chain: Vec<(String, Range)>, 543 | span: Range, 544 | }, 545 | } 546 | -------------------------------------------------------------------------------- /src/bin/ruxnasm/argument_parser.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | path::{Path, PathBuf}, 4 | process::exit, 5 | }; 6 | 7 | const HELP_MESSAGE: &'static str = r#"Usage: ruxnasm [OPTIONS] INPUT OUTPUT 8 | 9 | Options: 10 | -h, --help Display this message 11 | -V, --version Print version info and exit 12 | "#; 13 | const VERSION_MESSAGE: &'static str = concat!("ruxnasm ", env!("CARGO_PKG_VERSION")); 14 | 15 | #[derive(Debug)] 16 | pub struct Arguments { 17 | input_file_path: PathBuf, 18 | output_file_path: PathBuf, 19 | } 20 | 21 | impl Arguments { 22 | pub fn input_file_path(&self) -> &Path { 23 | &self.input_file_path 24 | } 25 | 26 | pub fn output_file_path(&self) -> &Path { 27 | &self.output_file_path 28 | } 29 | } 30 | 31 | pub enum Error { 32 | NoInputProvided, 33 | NoOutputProvided, 34 | UnexpectedArgument { argument: String }, 35 | UnrecognizedOption { option: String }, 36 | } 37 | 38 | pub fn parse_arguments() -> Result { 39 | if env::args().len() == 1 { 40 | exit_with_help_message(); 41 | } 42 | 43 | let mut args = env::args(); 44 | args.next(); 45 | let mut input_file_path: Option = None; 46 | let mut output_file_path: Option = None; 47 | 48 | for arg in args { 49 | if arg.starts_with("--") { 50 | match &arg[2..] { 51 | "help" => exit_with_help_message(), 52 | "version" => exit_with_version_message(), 53 | option => { 54 | return Err(Error::UnrecognizedOption { 55 | option: option.to_owned(), 56 | }) 57 | } 58 | } 59 | } else if arg.starts_with("-") { 60 | for ch in arg[1..].chars() { 61 | match ch { 62 | 'h' => exit_with_help_message(), 63 | 'V' => exit_with_version_message(), 64 | option => { 65 | return Err(Error::UnrecognizedOption { 66 | option: option.to_string(), 67 | }) 68 | } 69 | } 70 | } 71 | } else { 72 | if input_file_path.is_none() { 73 | input_file_path = Some(arg.into()); 74 | } else if output_file_path.is_none() { 75 | output_file_path = Some(arg.into()); 76 | } else { 77 | return Err(Error::UnexpectedArgument { 78 | argument: arg.to_owned(), 79 | }); 80 | } 81 | } 82 | } 83 | 84 | match (input_file_path, output_file_path) { 85 | (Some(input_file_path), Some(output_file_path)) => Ok(Arguments { 86 | input_file_path, 87 | output_file_path, 88 | }), 89 | (None, _) => Err(Error::NoInputProvided), 90 | (_, None) => Err(Error::NoOutputProvided), 91 | } 92 | } 93 | 94 | fn exit_with_help_message() { 95 | println!("{}", HELP_MESSAGE); 96 | exit(0); 97 | } 98 | 99 | fn exit_with_version_message() { 100 | println!("{}", VERSION_MESSAGE); 101 | exit(0); 102 | } 103 | -------------------------------------------------------------------------------- /src/bin/ruxnasm/main.rs: -------------------------------------------------------------------------------- 1 | use std::panic::set_hook; 2 | use std::process::exit; 3 | 4 | pub mod argument_parser; 5 | pub mod reader; 6 | pub mod reporter; 7 | pub mod writer; 8 | 9 | struct InternalAssemblerError { 10 | message: String, 11 | } 12 | 13 | fn try_main() -> Result<(), ()> { 14 | match argument_parser::parse_arguments() { 15 | Ok(arguments) => match reader::read(arguments.input_file_path()) { 16 | Ok(input_file_contents) => { 17 | let reporter = reporter::VoidReporter::new() 18 | .promote(arguments.input_file_path(), &input_file_contents); 19 | match ruxnasm::assemble(&input_file_contents) { 20 | Ok((binary, warnings)) => { 21 | for warning in warnings { 22 | reporter.emit(warning.into()); 23 | } 24 | match writer::write(arguments.output_file_path(), &binary) { 25 | Ok(()) => Ok(()), 26 | Err(error) => { 27 | let reporter = reporter.demote(); 28 | reporter.emit(error.into()); 29 | Err(()) 30 | } 31 | } 32 | } 33 | Err((errors, warnings)) => { 34 | for error in errors { 35 | reporter.emit(error.into()); 36 | } 37 | for warning in warnings { 38 | reporter.emit(warning.into()); 39 | } 40 | Err(()) 41 | } 42 | } 43 | } 44 | Err(error) => { 45 | let reporter = reporter::VoidReporter::new(); 46 | reporter.emit(error.into()); 47 | Err(()) 48 | } 49 | }, 50 | Err(error) => { 51 | let reporter = reporter::VoidReporter::new(); 52 | reporter.emit(error.into()); 53 | Err(()) 54 | } 55 | } 56 | } 57 | 58 | fn main() { 59 | set_hook(Box::new(|panic_info| { 60 | let reporter = reporter::VoidReporter::new(); 61 | 62 | let error = InternalAssemblerError { 63 | message: panic_info.to_string(), 64 | }; 65 | 66 | reporter.emit(error.into()); 67 | 68 | exit(1); 69 | })); 70 | 71 | let exit_code = try_main(); 72 | 73 | exit(exit_code.is_err() as i32); 74 | } 75 | -------------------------------------------------------------------------------- /src/bin/ruxnasm/reader.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::{fs, io}; 3 | 4 | pub enum Error { 5 | CouldNotReadFile { 6 | file_path: PathBuf, 7 | io_error: io::Error, 8 | }, 9 | } 10 | 11 | pub fn read(path: &Path) -> Result, Error> { 12 | fs::read(path).map_err(|io_error| Error::CouldNotReadFile { 13 | file_path: path.to_path_buf(), 14 | io_error, 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /src/bin/ruxnasm/reporter/diagnostic.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | macro_rules! impl_severities { 4 | ($builder: ident) => { 5 | pub fn bug() -> $builder { 6 | $builder { 7 | severity: Severity::Bug, 8 | } 9 | } 10 | 11 | pub fn error() -> $builder { 12 | $builder { 13 | severity: Severity::Error, 14 | } 15 | } 16 | 17 | pub fn warning() -> $builder { 18 | $builder { 19 | severity: Severity::Warning, 20 | } 21 | } 22 | }; 23 | } 24 | 25 | pub struct VoidDiagnosticBuilderStage1 { 26 | severity: Severity, 27 | } 28 | 29 | impl VoidDiagnosticBuilderStage1 { 30 | pub fn with_message(self, message: impl Into) -> VoidDiagnostic { 31 | VoidDiagnostic { 32 | severity: self.severity, 33 | message: message.into(), 34 | notes: Vec::new(), 35 | helps: Vec::new(), 36 | } 37 | } 38 | } 39 | 40 | pub struct VoidDiagnostic { 41 | severity: Severity, 42 | message: String, 43 | notes: Vec, 44 | helps: Vec, 45 | } 46 | 47 | impl<'a> VoidDiagnostic { 48 | impl_severities!(VoidDiagnosticBuilderStage1); 49 | 50 | pub fn with_note(mut self, note: impl Into) -> Self { 51 | self.notes.push(note.into()); 52 | self 53 | } 54 | 55 | pub fn with_help(mut self, help: impl Into) -> Self { 56 | self.helps.push(help.into()); 57 | self 58 | } 59 | } 60 | 61 | pub struct FileDiagnosticBuilderStage1 { 62 | severity: Severity, 63 | } 64 | 65 | impl FileDiagnosticBuilderStage1 { 66 | pub fn with_message(self, message: impl Into) -> FileDiagnosticBuilderStage2 { 67 | FileDiagnosticBuilderStage2 { 68 | severity: self.severity, 69 | message: message.into(), 70 | } 71 | } 72 | } 73 | 74 | pub struct FileDiagnosticBuilderStage2 { 75 | severity: Severity, 76 | message: String, 77 | } 78 | 79 | impl FileDiagnosticBuilderStage2 { 80 | pub fn with_label(self, label: Label) -> FileDiagnostic { 81 | FileDiagnostic { 82 | severity: self.severity, 83 | message: self.message, 84 | label, 85 | additional_labels: Vec::new(), 86 | notes: Vec::new(), 87 | helps: Vec::new(), 88 | } 89 | } 90 | } 91 | 92 | pub struct FileDiagnostic { 93 | severity: Severity, 94 | message: String, 95 | label: Label, 96 | additional_labels: Vec