├── .Dockerignore ├── .builds └── ci.yml ├── .github ├── FUNDING.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ └── deploy_docs.yml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── book.toml ├── build_docs.py ├── builtin ├── README.md ├── builtin.c └── builtin.js ├── contrib ├── PKGBUILD └── prerelease_check.sh ├── docs ├── SUMMARY.md ├── concepts │ ├── SUMMARY.md │ ├── comments.md │ ├── control-flow.md │ ├── datatypes.md │ ├── functions.md │ ├── structured-data.md │ └── variables.md ├── developers │ ├── SUMMARY.md │ ├── backends.md │ ├── contributing.md │ ├── debugging.md │ ├── releasing.md │ └── specification.md ├── introduction │ ├── SUMMARY.md │ ├── hello-world.md │ └── installation.md └── modules │ └── SUMMARY.md ├── examples ├── ackermann.sb ├── bubblesort.sb ├── fib.sb ├── greeter.sb ├── hello_world.sb ├── leapyear.sb ├── loops.sb └── sandbox.sb ├── lib ├── array.sb ├── assert.sb ├── io.sb └── os.sb ├── src ├── ast │ ├── README.md │ ├── mod.rs │ └── types.rs ├── builder │ ├── README.md │ └── mod.rs ├── command │ ├── build.rs │ ├── mod.rs │ └── run.rs ├── generator │ ├── c.rs │ ├── js.rs │ ├── llvm.rs │ ├── mod.rs │ ├── qbe.rs │ ├── tests │ │ ├── c_tests.rs │ │ ├── mod.rs │ │ └── qbe_tests.rs │ └── x86.rs ├── lexer │ ├── cursor.rs │ ├── display.rs │ ├── mod.rs │ └── tests.rs ├── main.rs ├── parser │ ├── infer.rs │ ├── mod.rs │ ├── parser.rs │ ├── rules.rs │ └── tests.rs ├── tests │ ├── mod.rs │ └── test_examples.rs └── util │ ├── mod.rs │ └── string_util.rs └── tests ├── arrays.sb ├── conditionals.sb ├── functions.sb ├── importable_module ├── foo │ ├── bar.sb │ └── baz │ │ └── module.sb └── module.sb ├── imports.sb ├── logger └── module.sb ├── main.sb ├── numbers.sb ├── structs.sb ├── types.sb └── unicode.sb /.Dockerignore: -------------------------------------------------------------------------------- 1 | *_out/ 2 | .builds/ 3 | .github/ 4 | .vscode/ 5 | TODO 6 | book/ 7 | docs/ 8 | examples/ 9 | target/ 10 | tests/ -------------------------------------------------------------------------------- /.builds/ci.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - rust 4 | - nodejs 5 | sources: 6 | - https://git.sr.ht/~garritfra/antimony 7 | tasks: 8 | - build: | 9 | cd antimony 10 | cargo build 11 | cargo test 12 | cargo clippy --all-targets -- -D warnings 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [garritfra] 2 | buy_me_a_coffee: garrit -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes #XXX 2 | 3 | ### Description 4 | 5 | ... 6 | 7 | ### Changes proposed in this pull request 8 | 9 | - ... 10 | - ... 11 | 12 | ### ToDo 13 | 14 | - [ ] Proposed feature/fix is sufficiently tested 15 | - [ ] Proposed feature/fix is sufficiently documented 16 | - [ ] The "Unreleased" section in the changelog has been updated, if applicable 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install LLVM and Clang 12 | uses: KyleMayes/install-llvm-action@v1 13 | with: 14 | version: "10" 15 | directory: ${{ runner.temp }}/llvm 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | profile: minimal 19 | toolchain: stable 20 | override: true 21 | - uses: actions-rs/cargo@v1 22 | with: 23 | command: check 24 | 25 | test: 26 | name: Test Suite 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Install LLVM and Clang 31 | uses: KyleMayes/install-llvm-action@v1 32 | with: 33 | version: "10" 34 | directory: ${{ runner.temp }}/llvm 35 | - uses: actions-rs/toolchain@v1 36 | with: 37 | profile: minimal 38 | toolchain: stable 39 | override: true 40 | - uses: actions-rs/cargo@v1 41 | with: 42 | command: test 43 | 44 | fmt: 45 | name: Rustfmt 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v2 49 | - name: Install LLVM and Clang 50 | uses: KyleMayes/install-llvm-action@v1 51 | with: 52 | version: "10" 53 | directory: ${{ runner.temp }}/llvm 54 | - uses: actions-rs/toolchain@v1 55 | with: 56 | profile: minimal 57 | toolchain: stable 58 | override: true 59 | - run: rustup component add rustfmt 60 | - uses: actions-rs/cargo@v1 61 | with: 62 | command: fmt 63 | args: --all -- --check 64 | 65 | clippy: 66 | name: Clippy 67 | runs-on: ubuntu-latest 68 | steps: 69 | - uses: actions/checkout@v2 70 | - name: Install LLVM and Clang 71 | uses: KyleMayes/install-llvm-action@v1 72 | with: 73 | version: "10" 74 | directory: ${{ runner.temp }}/llvm 75 | - uses: actions-rs/toolchain@v1 76 | with: 77 | profile: minimal 78 | toolchain: stable 79 | override: true 80 | - run: rustup component add clippy 81 | - uses: actions-rs/cargo@v1 82 | with: 83 | command: clippy 84 | args: --all-targets -- -D warnings 85 | -------------------------------------------------------------------------------- /.github/workflows/deploy_docs.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | name: Deploy to Github Pages 6 | jobs: 7 | deploy: 8 | name: deploy 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | 13 | - name: Setup mdBook 14 | uses: peaceiris/actions-mdbook@v1 15 | with: 16 | mdbook-version: "latest" 17 | 18 | - name: setup python 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: 3.8 22 | - run: | 23 | python build_docs.py 24 | 25 | - name: Disable Jekyll 26 | run: | 27 | touch book/.nojekyll 28 | 29 | - name: Deploy 30 | uses: JamesIves/github-pages-deploy-action@4.1.3 31 | with: 32 | branch: gh-pages 33 | folder: book 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .vscode/ 3 | examples_out/ 4 | tests_out/ 5 | book/ 6 | examples/playground.sb -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | xxx 6 | 7 | ## v0.9.0 (2025-04-18) 8 | 9 | **Features** 10 | 11 | - QBE: Added proper memory alignment for struct fields based on field types ([#61](https://github.com/antimony-lang/antimony/pull/61)) 12 | - Improved error reporting with more descriptive messages and helpful hints for common parsing errors ([#118](https://github.com/antimony-lang/antimony/pull/118)) 13 | - Added proper Display trait implementation for lexer tokens to improve error message formatting ([#118](https://github.com/antimony-lang/antimony/pull/118)) 14 | 15 | **Fixes** 16 | 17 | - Fixed parsing of binary operations in inline function expressions ([#109](https://github.com/antimony-lang/antimony/pull/109)) 18 | - Fixed parsing of binary operations after function calls ([#111](https://github.com/antimony-lang/antimony/pull/111)) 19 | - QBE: Fixed struct field access and memory layout for nested structs ([#61](https://github.com/antimony-lang/antimony/pull/61)) 20 | 21 | **Maintenance** 22 | 23 | - Refactored command execution for better error handling and code organization 24 | - Remove regex dependency 25 | - Bump dependency [qbe](https://crates.io/crates/qbe) from `1.0.0` to `2.4.0` 26 | - Bump dependency [rust-embed](https://crates.io/crates/rust-embed) from `5.7.0` to `8.7.0` 27 | 28 | ## v0.8.0 (2024-04-05) 29 | 30 | **Features** 31 | 32 | - Support for shorthand function bodies ([#94](https://github.com/antimony-lang/antimony/pull/94)) 33 | 34 | **Maintenance** 35 | 36 | - Bump dependency [structopt](https://crates.io/crates/structopt) from `0.3.21` to `0.3.26` 37 | - Bump dependency [inkwell](https://crates.io/crates/inkwell) from `0.1.0-beta.2` to `0.4.0` 38 | - Bump dependency [regex](https://crates.io/crates/regex) from `1.5.5` to `1.10.4` 39 | 40 | ## v0.7.0 (2022-06-15) 41 | 42 | **Changes** 43 | 44 | - Arrays now have a fixed capacity 45 | 46 | **Features** 47 | 48 | - Instead of a temporary directory, heap memory is used for compilation 49 | - Support for binary, hexadecimal and octal number notations 50 | - Support for `_` character in integers (E.g. `1_000_000`) 51 | - Parser errors have been improved in consistency and readability 52 | - Compile to stdout by using the `-o -` flag 53 | - Proper support for utf-8 54 | - Initial support for QBE backend 55 | 56 | **Fixes** 57 | 58 | - Allow constructor expressions as function arguments 59 | - Fix `self` keyword inside statement 60 | 61 | ## v0.6.0 (2021-02-28) 62 | 63 | **Changes** 64 | 65 | - Comma separation for struct fields has been removed 66 | 67 | **Features** 68 | 69 | - Struct methods (#19) 70 | - Compile-backend will be determined based on file extension (#20, #21) 71 | 72 | **Fixes** 73 | 74 | - Fixed a bug where strings were terminated using both `"` and `'` 75 | - Fixed circular imports for modules 76 | - Fixed structs not being imported from other modules 77 | 78 | ## v0.5.1 (2021-02-25) 79 | 80 | Sabre is now Antimony! 81 | 82 | **Changes** 83 | 84 | - "sabre" was replaced with "sb" (E.g. run `sb run main.sb` to run a program) 85 | 86 | ## v0.5.0 (2021-02-23) 87 | 88 | **Features** 89 | 90 | - Match statements (#15) 91 | - Modules and imports (#17) 92 | - Support for Docker 93 | - Support for Arch Linux build system (Thanks Alex) 94 | 95 | **Fixes** 96 | 97 | - Fixed a bug with nested expressions and arithmetic operations 98 | 99 | **Documentation** 100 | 101 | - Added some in-repo technical documentation 102 | - Minor fixes 103 | 104 | ## v0.4.0 (2021-02-20) 105 | 106 | This release introduces the concept of structs, alongside many improvements to the documentation. 107 | 108 | **Features** 109 | 110 | - Assignment operators (#10) 111 | - Structs (#12) 112 | 113 | **Fixes** 114 | 115 | None 116 | 117 | **Documentation** 118 | 119 | - Fixed some typose and broken links 120 | - Document boolean values 121 | - Added this changelog! 122 | 123 | ## v0.3.0 (2021-02-12) 124 | 125 | This release adds type inference to Antimony. There are also a lot of improvements in terms of documentation. The docs are now at a state that can be considered "usable". 126 | 127 | **Features** 128 | 129 | - Type inference 130 | - The `any` type 131 | - First attempt of LLVM backend 132 | 133 | **Fixes** 134 | 135 | - Fixed an error when printing numbers 136 | 137 | **Documentation** 138 | 139 | - Added documentation for for loops 140 | - Added documentation for while loops 141 | - Documented LLVM backend 142 | - Documented comments 143 | - Updated contributing guidelines 144 | 145 | ## v0.2.1 (2021-02-06) 146 | 147 | **Fixes** 148 | 149 | - Fixed an issue where nested expressions where not compiled correctly 150 | 151 | ## v0.2.0 (2021-02-06) 152 | 153 | This version introduces a lot of improvements regarding loops and arrays. 154 | 155 | **Features** 156 | 157 | - Support for nested arrays 158 | - `break` and `continue` statements 159 | 160 | **Documentation** 161 | 162 | - Link to our matrix channel in README 163 | - Install Antimony via Cargo 164 | 165 | ## v0.1.1 (2021-02-06) 166 | 167 | Follow-up release that fixes some issues with the published crate. 168 | 169 | ## v0.1.0 (2021-02-06) 170 | 171 | This release is the first to be published to crates.io. The crate is called [antimony-lang](https://crates.io/crates/antimony-lang). 172 | 173 | **Features** 174 | 175 | - Uninitialized variables 176 | - For loops 177 | 178 | **Fixes** 179 | 180 | None 181 | 182 | **Documentation** 183 | 184 | - Functions fully documented 185 | 186 | ## v0.0.4 (2020-12-18) 187 | 188 | This release tries to lay the groundwork of a possible C backend. 189 | 190 | **Features** 191 | 192 | - An unstable, opt-in C backend 193 | - `len()` standard library function 194 | - `rev()` standard library function 195 | - Function return types 196 | 197 | **Fixes** 198 | 199 | - Booleans as function parameters 200 | 201 | **Documentation** 202 | 203 | - A lot of improvements 204 | 205 | ## v0.0.3 (2020-12-10) 206 | 207 | This release adds new vital features to the language. 208 | 209 | **Features** 210 | 211 | - While loops 212 | - Boolean type 213 | - Variable assignments 214 | - Basic standard library 215 | 216 | ## v0.0.2 (2020-12-09) 217 | 218 | Direct follow-up release that didn't add or remove anything 219 | 220 | ## v0.0.1 (2020-12-09) 221 | 222 | Initial release with basic featureset. 223 | 224 | **Full shortlog** 225 | 226 | ``` 227 | Garrit Franke (74): 228 | Initial commit 229 | Add license 230 | Add curly braces 231 | Parse functions 232 | Fix function parsing 233 | Update math example 234 | Refactor TokenType 235 | Implement return statement 236 | Fix keyword recognition 237 | Fix test compilation 238 | Fix tests and comments 239 | Add variable declarations 240 | Implement returning variables 241 | Pretty print AST output 242 | Add strings 243 | Rename flex -> antimony 244 | Fix example filename 245 | Add parser tests 246 | Add multiple functions test 247 | Add token positions 248 | Add token positions 249 | Allow empty returns 250 | Add x86 generator scaffolding 251 | Generate assembly 252 | Add JS generator 253 | Fix warnings 254 | Implement return generation 255 | Refactor x86 generator 256 | Print result of main for js target 257 | Fix infinite loop when parsing strings 258 | Add CI 259 | Add function arguments 260 | Add function arguments 261 | Tokenize Comma 262 | Fix return with expression 263 | Remove uneeded compount statement 264 | Add math operations 265 | Fix parsing of complex compound expressions 266 | Clean up expression parsing 267 | Refactor function call parsing 268 | Change function declaration syntax 269 | Add greeter example 270 | Add fib example 271 | Add basic conditionals; remove semicolons 272 | Allow multiple statements in if conditional 273 | Add TODO file 274 | Add js generator for variable declarations 275 | Add remaining comparison operators 276 | Add Readme 277 | Add backend-state to README 278 | Add CLI TODO 279 | Add error reporting TODO 280 | Fix typo 281 | Add builds.sr.ht badge 282 | Add basic CLI 283 | Revert "Change function declaration syntax" 284 | Fix production build 285 | Fix examples 286 | Fix readme 287 | Fix compound op with identifier first 288 | Fix fib example 289 | Add conditional else if branch 290 | Generalize block into own statement 291 | Add else branch 292 | Add copyright notices 293 | Fix warnings 294 | Add integer arrays 295 | Clean up error handling 296 | Refactor parser module structure 297 | Fix warnings 298 | Add docs 299 | Add placeholder for documentation 300 | docs: add placeholder for CLI 301 | docs: add placeholders for developers 302 | ``` 303 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * garrit@slashdev.space 2 | src/generator/qbe.rs @YerinAlexey 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The easiest way to contribute to the Antimony project is by writing code in the language. The more the language is battle-tested, the more bugs can be found and therefore the language becomes more stable. 4 | 5 | ## Getting in touch 6 | 7 | If you have a question, found a potential bug or want to engage with the community, you can join the [matrix room](https://matrix.to/#/#antimony:matrix.slashdev.space?via=matrix.slashdev.space) of this project. If you prefer to stay away from matrix, you can also send a mail (or a patch) to the [public mailing list](https://lists.sr.ht/~garritfra/antimony). 8 | 9 | ## Fixing things and adding features 10 | 11 | If you want to contribute to the compiler itself, the easiest way to get started is to look at the [open issues](https://github.com/antimony-lang/antimony/issues) of the project. Usually, this is where important todo items are jotted down and bugs are reported. 12 | 13 | You could also run the tests (`cargo test`) and see if any tests are ignored. Usually, if a bug is found in the wild, a failing but ignored test is written, so that it can be further investigated later. 14 | 15 | ## Writing documentation 16 | 17 | As with all software, Antimony needs good documentation. Since Antimony is still in early development, things change constantly. This means that docs will be out of date in a lot of cases, or not written at all. Any help with the documentation is greatly appreciated! 18 | 19 | ## Submitting your code 20 | 21 | If you want to contribute code, please open a pull request on [GitHub](https://github.com/antimony-lang/antimony). There is also a [SourceHut mirror](https://sr.ht/~garritfra/antimony/), if you're trying to avoid GitHub. Feel free to send a patch to the [public mailing list](https://lists.sr.ht/~garritfra/antimony). Check out [this guide](https://slashdev.space/posts/patch-based-git-workflow) to learn about the patch based workflow. 22 | 23 | Before submitting the code, please make sure that it is **sufficiently documented and tested**. 24 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.11.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "antimony-lang" 25 | version = "0.9.0" 26 | dependencies = [ 27 | "inkwell", 28 | "lazy_static", 29 | "qbe", 30 | "rust-embed", 31 | "structopt", 32 | ] 33 | 34 | [[package]] 35 | name = "atty" 36 | version = "0.2.14" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 39 | dependencies = [ 40 | "hermit-abi", 41 | "libc", 42 | "winapi", 43 | ] 44 | 45 | [[package]] 46 | name = "bitflags" 47 | version = "1.2.1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 50 | 51 | [[package]] 52 | name = "block-buffer" 53 | version = "0.10.4" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 56 | dependencies = [ 57 | "generic-array", 58 | ] 59 | 60 | [[package]] 61 | name = "cc" 62 | version = "1.0.66" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" 65 | 66 | [[package]] 67 | name = "cfg-if" 68 | version = "1.0.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 71 | 72 | [[package]] 73 | name = "clap" 74 | version = "2.33.3" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 77 | dependencies = [ 78 | "ansi_term", 79 | "atty", 80 | "bitflags", 81 | "strsim", 82 | "textwrap", 83 | "unicode-width", 84 | "vec_map", 85 | ] 86 | 87 | [[package]] 88 | name = "cpufeatures" 89 | version = "0.2.17" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 92 | dependencies = [ 93 | "libc", 94 | ] 95 | 96 | [[package]] 97 | name = "crypto-common" 98 | version = "0.1.6" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 101 | dependencies = [ 102 | "generic-array", 103 | "typenum", 104 | ] 105 | 106 | [[package]] 107 | name = "digest" 108 | version = "0.10.7" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 111 | dependencies = [ 112 | "block-buffer", 113 | "crypto-common", 114 | ] 115 | 116 | [[package]] 117 | name = "either" 118 | version = "1.6.1" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 121 | 122 | [[package]] 123 | name = "generic-array" 124 | version = "0.14.7" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 127 | dependencies = [ 128 | "typenum", 129 | "version_check", 130 | ] 131 | 132 | [[package]] 133 | name = "heck" 134 | version = "0.3.1" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 137 | dependencies = [ 138 | "unicode-segmentation", 139 | ] 140 | 141 | [[package]] 142 | name = "hermit-abi" 143 | version = "0.1.17" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" 146 | dependencies = [ 147 | "libc", 148 | ] 149 | 150 | [[package]] 151 | name = "inkwell" 152 | version = "0.6.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "e67349bd7578d4afebbe15eaa642a80b884e8623db74b1716611b131feb1deef" 155 | dependencies = [ 156 | "either", 157 | "inkwell_internals", 158 | "libc", 159 | "llvm-sys", 160 | "once_cell", 161 | "thiserror", 162 | ] 163 | 164 | [[package]] 165 | name = "inkwell_internals" 166 | version = "0.11.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "f365c8de536236cfdebd0ba2130de22acefed18b1fb99c32783b3840aec5fb46" 169 | dependencies = [ 170 | "proc-macro2", 171 | "quote", 172 | "syn 2.0.48", 173 | ] 174 | 175 | [[package]] 176 | name = "lazy_static" 177 | version = "1.5.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 180 | 181 | [[package]] 182 | name = "libc" 183 | version = "0.2.172" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 186 | 187 | [[package]] 188 | name = "llvm-sys" 189 | version = "100.2.4" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "72251a917884f75079ba3bd20fe41aa0c89d40392ac3de6a8b1063ee92475b34" 192 | dependencies = [ 193 | "cc", 194 | "lazy_static", 195 | "libc", 196 | "regex", 197 | "semver", 198 | ] 199 | 200 | [[package]] 201 | name = "memchr" 202 | version = "2.6.3" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 205 | 206 | [[package]] 207 | name = "once_cell" 208 | version = "1.17.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 211 | 212 | [[package]] 213 | name = "proc-macro-error" 214 | version = "1.0.4" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 217 | dependencies = [ 218 | "proc-macro-error-attr", 219 | "proc-macro2", 220 | "quote", 221 | "syn 1.0.53", 222 | "version_check", 223 | ] 224 | 225 | [[package]] 226 | name = "proc-macro-error-attr" 227 | version = "1.0.4" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 230 | dependencies = [ 231 | "proc-macro2", 232 | "quote", 233 | "version_check", 234 | ] 235 | 236 | [[package]] 237 | name = "proc-macro2" 238 | version = "1.0.78" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 241 | dependencies = [ 242 | "unicode-ident", 243 | ] 244 | 245 | [[package]] 246 | name = "qbe" 247 | version = "2.5.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "c1ce54124980f3c530866c8145401764df3f0c428698f594ba8d634483af66e5" 250 | 251 | [[package]] 252 | name = "quote" 253 | version = "1.0.35" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 256 | dependencies = [ 257 | "proc-macro2", 258 | ] 259 | 260 | [[package]] 261 | name = "regex" 262 | version = "1.11.1" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 265 | dependencies = [ 266 | "aho-corasick", 267 | "memchr", 268 | "regex-automata", 269 | "regex-syntax", 270 | ] 271 | 272 | [[package]] 273 | name = "regex-automata" 274 | version = "0.4.8" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 277 | dependencies = [ 278 | "aho-corasick", 279 | "memchr", 280 | "regex-syntax", 281 | ] 282 | 283 | [[package]] 284 | name = "regex-syntax" 285 | version = "0.8.5" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 288 | 289 | [[package]] 290 | name = "rust-embed" 291 | version = "8.7.2" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" 294 | dependencies = [ 295 | "rust-embed-impl", 296 | "rust-embed-utils", 297 | "walkdir", 298 | ] 299 | 300 | [[package]] 301 | name = "rust-embed-impl" 302 | version = "8.7.2" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" 305 | dependencies = [ 306 | "proc-macro2", 307 | "quote", 308 | "rust-embed-utils", 309 | "syn 2.0.48", 310 | "walkdir", 311 | ] 312 | 313 | [[package]] 314 | name = "rust-embed-utils" 315 | version = "8.7.2" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" 318 | dependencies = [ 319 | "sha2", 320 | "walkdir", 321 | ] 322 | 323 | [[package]] 324 | name = "same-file" 325 | version = "1.0.6" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 328 | dependencies = [ 329 | "winapi-util", 330 | ] 331 | 332 | [[package]] 333 | name = "semver" 334 | version = "0.9.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 337 | dependencies = [ 338 | "semver-parser", 339 | ] 340 | 341 | [[package]] 342 | name = "semver-parser" 343 | version = "0.7.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 346 | 347 | [[package]] 348 | name = "sha2" 349 | version = "0.10.8" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 352 | dependencies = [ 353 | "cfg-if", 354 | "cpufeatures", 355 | "digest", 356 | ] 357 | 358 | [[package]] 359 | name = "strsim" 360 | version = "0.8.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 363 | 364 | [[package]] 365 | name = "structopt" 366 | version = "0.3.26" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 369 | dependencies = [ 370 | "clap", 371 | "lazy_static", 372 | "structopt-derive", 373 | ] 374 | 375 | [[package]] 376 | name = "structopt-derive" 377 | version = "0.4.18" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 380 | dependencies = [ 381 | "heck", 382 | "proc-macro-error", 383 | "proc-macro2", 384 | "quote", 385 | "syn 1.0.53", 386 | ] 387 | 388 | [[package]] 389 | name = "syn" 390 | version = "1.0.53" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68" 393 | dependencies = [ 394 | "proc-macro2", 395 | "quote", 396 | "unicode-xid", 397 | ] 398 | 399 | [[package]] 400 | name = "syn" 401 | version = "2.0.48" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 404 | dependencies = [ 405 | "proc-macro2", 406 | "quote", 407 | "unicode-ident", 408 | ] 409 | 410 | [[package]] 411 | name = "textwrap" 412 | version = "0.11.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 415 | dependencies = [ 416 | "unicode-width", 417 | ] 418 | 419 | [[package]] 420 | name = "thiserror" 421 | version = "1.0.56" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" 424 | dependencies = [ 425 | "thiserror-impl", 426 | ] 427 | 428 | [[package]] 429 | name = "thiserror-impl" 430 | version = "1.0.56" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" 433 | dependencies = [ 434 | "proc-macro2", 435 | "quote", 436 | "syn 2.0.48", 437 | ] 438 | 439 | [[package]] 440 | name = "typenum" 441 | version = "1.18.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 444 | 445 | [[package]] 446 | name = "unicode-ident" 447 | version = "1.0.9" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 450 | 451 | [[package]] 452 | name = "unicode-segmentation" 453 | version = "1.7.1" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 456 | 457 | [[package]] 458 | name = "unicode-width" 459 | version = "0.1.8" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 462 | 463 | [[package]] 464 | name = "unicode-xid" 465 | version = "0.2.2" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 468 | 469 | [[package]] 470 | name = "vec_map" 471 | version = "0.8.2" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 474 | 475 | [[package]] 476 | name = "version_check" 477 | version = "0.9.2" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 480 | 481 | [[package]] 482 | name = "walkdir" 483 | version = "2.5.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 486 | dependencies = [ 487 | "same-file", 488 | "winapi-util", 489 | ] 490 | 491 | [[package]] 492 | name = "winapi" 493 | version = "0.3.9" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 496 | dependencies = [ 497 | "winapi-i686-pc-windows-gnu", 498 | "winapi-x86_64-pc-windows-gnu", 499 | ] 500 | 501 | [[package]] 502 | name = "winapi-i686-pc-windows-gnu" 503 | version = "0.4.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 506 | 507 | [[package]] 508 | name = "winapi-util" 509 | version = "0.1.5" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 512 | dependencies = [ 513 | "winapi", 514 | ] 515 | 516 | [[package]] 517 | name = "winapi-x86_64-pc-windows-gnu" 518 | version = "0.4.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 521 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "antimony-lang" 3 | version = "0.9.0" 4 | authors = ["Garrit Franke "] 5 | description = "The Antimony programming language" 6 | license = "Apache-2.0" 7 | documentation = "https://antimony-lang.github.io/antimony" 8 | repository = "https://github.com/antimony-lang/antimony" 9 | edition = "2018" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [[bin]] 14 | name = "sb" 15 | path = "src/main.rs" 16 | 17 | [features] 18 | llvm = ["inkwell"] 19 | 20 | [dependencies] 21 | structopt = "0.3.26" 22 | rust-embed = "8.7.2" 23 | inkwell = { version = "0.6.0", features = ["llvm10-0"], optional = true } 24 | qbe = "2.5.0" 25 | lazy_static = "1.5.0" 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.50.0 AS build 2 | WORKDIR /usr/src 3 | 4 | # Download the target for static linking. 5 | RUN rustup target add x86_64-unknown-linux-musl 6 | 7 | # Create a dummy project and build the app's dependencies. 8 | # If the Cargo.toml or Cargo.lock files have not changed, 9 | # we can use the docker build cache and skip these (typically slow) steps. 10 | WORKDIR /usr/src/antimony 11 | COPY Cargo.toml Cargo.lock ./ 12 | 13 | # Copy the source and build the application. 14 | COPY src ./src 15 | COPY lib ./lib 16 | COPY builtin ./builtin 17 | RUN cargo install --target x86_64-unknown-linux-musl --path . 18 | 19 | RUN cargo build --release 20 | 21 | # Copy the statically-linked binary into a scratch container. 22 | FROM alpine:3.13 23 | 24 | LABEL maintainer="Garrit Franke " 25 | 26 | COPY --from=build /usr/local/cargo/bin/antimony /bin 27 | 28 | RUN antimony --version 29 | 30 | ENTRYPOINT ["antimony"] 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Antimony Programming Language 2 | 3 | [![](https://img.shields.io/crates/v/antimony-lang.svg)](https://crates.io/crates/antimony-lang) 4 | ![Continuous integration](https://github.com/antimony-lang/antimony/workflows/Continuous%20integration/badge.svg?branch=master) 5 | [![docs](https://img.shields.io/badge/docs-mdBook-blue.svg)](https://antimony-lang.github.io/antimony/latest) 6 | [![Chat on Matrix](https://img.shields.io/badge/chat-on%20Matrix-green)](https://matrix.to/#/#antimony:matrix.slashdev.space?via=matrix.slashdev.space) 7 | 8 | Antimony is a bullshit-free (©) programming language that gets out of your way. 9 | It is meant to "just work", without adding unnecessary and bloated language features. 10 | 11 | ## Why yet another language? 12 | 13 | The goal of Antimony is to be a simple language that anyone - beginner and expert - can pick up and use. A "bullshit-free programming language" is of course a highly subjective opinion, and this project is my very own attempt at this. There are plenty of great programming languages out there, and Antimony is not meant to replace any of them. Currently, Antimony is just a general-purpose toy language. Its primary goal is to be simple and easy to understand, not to be efficient. 14 | 15 | ## Example 16 | 17 | ```rs 18 | // examples/fib.sb 19 | 20 | fn main() { 21 | let num = 10 22 | println(fib(num)) 23 | } 24 | 25 | fn fib(n: int): int { 26 | if n <= 1 { 27 | return n 28 | } 29 | 30 | return fib(n-1) + fib(n-2) 31 | } 32 | 33 | // -> 55 34 | ``` 35 | 36 | ## State of this project 37 | 38 | **Antimony is a hobby project** created to learn about compiler internals. It is not (yet) intended for production use. 39 | 40 | Most algorithms should run fine, but some features may be unstable. Standard library and documentation are still incomplete. See the [open issues](https://github.com/antimony-lang/antimony/issues) for upcoming todos. 41 | 42 | The Antimony compiler emits JavaScript for the Node.js runtime, and a C backend is currently under development. Backends for WASM and LLVM are planned. 43 | 44 | ## Documentation 45 | 46 | Documentation is hosted [here](https://antimony-lang.github.io/antimony). 47 | 48 | ## Getting started 49 | 50 | See the [installation](https://antimony-lang.github.io/antimony/latest/introduction/installation.html) instructions to get started. 51 | 52 | ## Getting in touch 53 | 54 | [Join our Matrix room](https://matrix.to/#/#antimony:matrix.slashdev.space?via=matrix.slashdev.space)! 55 | 56 | ## License 57 | 58 | This software is licensed under the [Apache-2.0 license](./LICENSE). 59 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Garrit Franke"] 3 | language = "en" 4 | multilingual = false 5 | src = "docs" 6 | title = "The Antimony Programming Language" 7 | -------------------------------------------------------------------------------- /build_docs.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import time 4 | 5 | os.system("mdbook build --dest-dir './book'") 6 | os.system("mdbook build --dest-dir './book/latest'") 7 | tags = subprocess.run(["git", "tag"], stdout=subprocess.PIPE).stdout.decode('utf-8') 8 | for tag in tags.split("\n"): 9 | if tag == "": 10 | continue 11 | print("Building tag", tag) 12 | os.system("git checkout " + tag) 13 | time.sleep(1) 14 | os.system("mdbook build --dest-dir './book/" + tag + "'") 15 | -------------------------------------------------------------------------------- /builtin/README.md: -------------------------------------------------------------------------------- 1 | The following function signatures need to be implemented by each backend: 2 | 3 | `_printf(msg: string)` 4 | `_exit(code: int)` 5 | -------------------------------------------------------------------------------- /builtin/builtin.c: -------------------------------------------------------------------------------- 1 | /* START builtins */ 2 | #include "stdio.h" 3 | #include 4 | 5 | void _printf(char *msg) 6 | { 7 | printf("%s", msg); 8 | } 9 | 10 | void _exit(int code) 11 | { 12 | exit(code); 13 | } 14 | 15 | /* END builtins */ 16 | -------------------------------------------------------------------------------- /builtin/builtin.js: -------------------------------------------------------------------------------- 1 | /* START builtins */ 2 | 3 | function _printf(msg) { 4 | // Message is casted to string to prevent crash 5 | process.stdout.write(msg.toString()); 6 | } 7 | 8 | function _exit(code) { 9 | process.exit(code); 10 | } 11 | 12 | /* END builtins */ 13 | -------------------------------------------------------------------------------- /contrib/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Garrit Franke 2 | # Contributor: Alexey Yerin 3 | pkgname="antimony-git" 4 | pkgver=v0.5.1.r1.fd08c6a 5 | pkgrel=1 6 | pkgdesc="The bullshit-free programming language" 7 | url="https://github.com/antimony-lang/antimony" 8 | license=("Apache") 9 | arch=("x86_64" "pentium4" "i686") 10 | makedepends=("git" "cargo") 11 | optdepends=( 12 | "node: Node.js backend" 13 | "llvm: LLVM backend" 14 | ) 15 | provides=("antimony") 16 | source=( 17 | "antimony::git+https://github.com/antimony-lang/antimony" 18 | ) 19 | sha256sums=("SKIP") 20 | 21 | pkgver() { 22 | cd "${srcdir}/antimony" 23 | 24 | printf "%s" "$(git describe --long | sed 's/\([^-]*-\)g/r\1/;s/-/./g')" 25 | } 26 | 27 | build() { 28 | cd "${srcdir}/antimony" 29 | 30 | cargo build --release 31 | } 32 | 33 | check() { 34 | cd "${srcdir}/antimony" 35 | 36 | cargo test 37 | } 38 | 39 | package() { 40 | cd "${srcdir}/antimony" 41 | 42 | install -Dm755 target/release/sb "${pkgdir}/usr/bin/sb" 43 | } 44 | -------------------------------------------------------------------------------- /contrib/prerelease_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the current branch name 4 | current_branch=$(git branch --show-current) 5 | 6 | echo "Checking current branch: $current_branch" 7 | if [ "$current_branch" != "master" ]; then 8 | echo "Error: Not on master branch. Currently on '$current_branch'." 9 | exit 1 10 | fi 11 | 12 | # Run cargo clippy 13 | echo "Running cargo clippy..." 14 | if ! cargo clippy; then 15 | echo "Error: cargo clippy failed." 16 | exit 1 17 | fi 18 | 19 | # Run cargo fmt 20 | echo "Running cargo fmt..." 21 | if ! cargo fmt -- --check; then 22 | echo "Error: cargo fmt failed." 23 | exit 1 24 | fi 25 | 26 | # Run cargo test 27 | echo "Running cargo test..." 28 | if ! cargo test; then 29 | echo "Error: cargo test failed." 30 | exit 1 31 | fi 32 | 33 | echo "All checks passed successfully." -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | - [Introduction](./introduction/SUMMARY.md) 4 | - [Installation](./introduction/installation.md) 5 | - [Hello World!](./introduction/hello-world.md) 6 | - [Common language concepts](./concepts/SUMMARY.md) 7 | - [Variables](./concepts/variables.md) 8 | - [Data Types](./concepts/datatypes.md) 9 | - [Functions](./concepts/functions.md) 10 | - [Comments](./concepts/comments.md) 11 | - [Control Flow](./concepts/control-flow.md) 12 | - [Structured Data](./concepts/structured-data.md) 13 | - [Modules and Imports](./modules/SUMMARY.md) 14 | - [Developer Resources](./developers/SUMMARY.md) 15 | - [Specification](./developers/specification.md) 16 | - [Compiler Backends](./developers/backends.md) 17 | - [Debugging the compiler](./developers/debugging.md) 18 | - [Release Workflow](./developers/releasing.md) 19 | -------------------------------------------------------------------------------- /docs/concepts/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Common language concepts 2 | 3 | This chapter covers concepts that appear in almost every programming language and how they work in Antimony. Many programming languages have much in common at their core. 4 | 5 | Specifically, you’ll learn about variables, basic types, functions, comments, and control flow. These foundations will be in every Antimony program, and learning them early will give you a strong core to start from. 6 | -------------------------------------------------------------------------------- /docs/concepts/comments.md: -------------------------------------------------------------------------------- 1 | # Comments 2 | 3 | All programmers strive to make their code easy to understand, but sometimes extra explanation is warranted. In these cases, programmers leave notes, or comments, in their source code that the compiler will ignore but people reading the source code may find useful. 4 | 5 | Here’s a simple comment: 6 | 7 | ``` 8 | // I'm a comment! 9 | ``` 10 | 11 | In Antimony, the idiomatic comment style starts a comment with two slashes, and the comment continues until the end of the line. For comments that extend beyond a single line, you’ll need to include `//` on each line, like this: 12 | 13 | ``` 14 | // So we’re doing something complicated here, long enough that we need 15 | // multiple lines of comments to do it! Whew! Hopefully, this comment will 16 | // explain what’s going on. 17 | ``` 18 | 19 | Comments can also be placed at the end of lines containing code: 20 | 21 | ``` 22 | fn main() { 23 | let lucky_number = 7 // I’m feeling lucky today 24 | } 25 | ``` 26 | 27 | But you’ll more often see them used in this format, with the comment on a separate line above the code it’s annotating: 28 | 29 | ``` 30 | fn main() { 31 | // I’m feeling lucky today 32 | let lucky_number = 7 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/concepts/control-flow.md: -------------------------------------------------------------------------------- 1 | # Control Flow 2 | 3 | Deciding whether or not to run some code depending on if a condition is true and deciding to run some code repeatedly while a condition is true are basic building blocks in most programming languages. The most common constructs that let you control the flow of execution of Antimony code are `if` expressions and loops. 4 | 5 | ## `if` Expressions 6 | 7 | An `if` expression allows you to branch your code depending on conditions. You provide a condition and then state, "If this condition is met, run this block of code. If the condition is not met, do not run this block of code." 8 | 9 | Here is a basic example of an `if` expression: 10 | 11 | ``` 12 | fn main() { 13 | let number = 3 14 | 15 | if number < 5 { 16 | println("condition was true") 17 | } else { 18 | println("condition was false") 19 | } 20 | } 21 | ``` 22 | 23 | All `if` Statements start with the keyword `if`, followed by a condition. In this case, the condition checks if the number has a value less than 5. The block of code we want to execute if the condition is true is placed immediately after the condition inside curly braces. 24 | 25 | Optionally, we can also include an `else` expression, which we chose to do here, to give the program an alternative block of code to execute should the condition evaluate to false. If you don’t provide an `else` expression and the condition is false, the program will just skip the `if` block and move on to the next bit of code. 26 | 27 | Try running this code; You should see the following output: 28 | 29 | ``` 30 | $ sb run main.sb 31 | condition was true 32 | ``` 33 | 34 | Let’s try changing the value of `number` to a value that makes the condition false to see what happens: 35 | 36 | ``` 37 | let number = 7 38 | ``` 39 | 40 | Run the program again, and look at the output: 41 | 42 | ``` 43 | $ sb run main.sb 44 | condition was false 45 | ``` 46 | 47 | > **Note**: It's worth noting that the condition in this code must be a bool. At the current state of the project, this is not the case, but it is subject to change at any time. **TODO**: Discuss this behavior. 48 | 49 | ### Handling multiple conditions with `else if` 50 | 51 | You can have multiple conditions by combining `if` and `else` in an `else if` expression. For example: 52 | 53 | ``` 54 | fn main() { 55 | let number = 6 56 | 57 | if number % 4 == 0 { 58 | println("number is divisible by 4") 59 | } else if number % 3 == 0 { 60 | println("number is divisible by 3") 61 | } else if number % 2 == 0 { 62 | println("number is divisible by 2") 63 | } else { 64 | println("number is not divisible by 4, 3, or 2") 65 | } 66 | } 67 | ``` 68 | 69 | This program has four possible paths it can take. After running it, you should see the following output: 70 | 71 | ``` 72 | $ sb run main.sb 73 | number is divisible by 3 74 | ``` 75 | 76 | When this program executes, it checks each `if` expression in turn and executes the first body for which the condition holds true. Note that even though 6 is divisible by 2, we don’t see the output `number is divisible by 2`, nor do we see the `number is not divisible by 4, 3, or 2` text from the else block. That’s because Antimony only executes the block for the first true condition, and once it finds one, it doesn’t even check the rest. 77 | 78 | ### Value matching 79 | 80 | Working with `if` statements with multiple `else` branches can become tedious. `match` statements provide a cleaner syntax for this case. You can compare `match` statements to `switch` in many other languages. Let's look at a very simple match statement. 81 | 82 | ``` 83 | let x = 42 84 | 85 | match x { 86 | 1 => println("x is 1") 87 | 2 => println("x is 2") 88 | 42 => println("The answer to the universe and everything!") 89 | else => println("This will not be called") 90 | } 91 | ``` 92 | 93 | In this example, we check the value of `x`, and execute some code based on the value. Instead of having to type `x == 1`, `x == 2` and so on, we instead provide the value only once, and decide what to do for each case. We can optionally provide a `else` case, which will be executed if no other case was triggered. 94 | 95 | You can execute multiple statements inside a single case. A common case would be to log some debug output and then return a value. 96 | 97 | ``` 98 | fn invert(x: bool): bool { 99 | match x { 100 | true => { 101 | println("The value is true") 102 | return false 103 | } 104 | false => { 105 | println("The value is false") 106 | return true 107 | } 108 | } 109 | } 110 | ``` 111 | 112 | Keep in mind that excessive use of this could hurt the readability of your code. Instead, you could try to outsource those statements into a function and call that instead. 113 | 114 | ## Loops 115 | 116 | It's often useful to execute a block of code more than once. For this task, Antimony provides different kind of _loops_. A loop runs through the code inside the its body to the end and then starts immediately back at the beginning. 117 | 118 | Antimony has two types of loops: `while` and `for`. Let's go through each of them. 119 | 120 | ### Conditional Loops with `while` 121 | 122 | It’s often useful for a program to evaluate a condition within a loop. While the condition is true, the loop runs. When the condition ceases to be true, the program calls `break`, stopping the loop. 123 | 124 | The example below loops three times, counting down each time, and then, after the loop, it prints another message and exits. 125 | 126 | ``` 127 | fn main() { 128 | let number = 3 129 | 130 | while number != 0 { 131 | println(number) 132 | 133 | number = number - 1 134 | } 135 | 136 | println("LIFTOFF!!!") 137 | } 138 | ``` 139 | 140 | ### Looping Through a Collection with `for` 141 | 142 | You could use the `while` construct to loop over the elements of a collection, such as an array. For example: 143 | 144 | ``` 145 | fn main() { 146 | let a = [10, 20, 30, 40, 50] 147 | let index = 0 148 | 149 | while index < 5 { 150 | println("the value is: " + a[index]) 151 | 152 | index += 1 153 | } 154 | } 155 | ``` 156 | 157 | Here, the code counts up through the elements in the array. It starts at index `0`, and then loops until it reaches the final index in the array (that is, when `index < 5` is no longer true). Running this code will print every element in the array: 158 | 159 | ``` 160 | $ sb run main.sb 161 | the value is: 10 162 | the value is: 20 163 | the value is: 30 164 | the value is: 40 165 | the value is: 50 166 | ``` 167 | 168 | All five array values appear in the terminal, as expected. Even though index will reach a value of 5 at some point, the loop stops executing before trying to fetch a sixth value from the array. 169 | 170 | But this approach is error prone; we could cause the program to crash if the index length is incorrect. It's also slow, because the compiler adds runtime code to perform the conditional check on every element on every iteration through the loop. 171 | 172 | As a more concise alternative, you can use a `for` loop and execute some code for each item in a collection. A `for` loop looks like the following: 173 | 174 | ``` 175 | fn main() { 176 | let a = [10, 20, 30, 40, 50] 177 | 178 | for element in a { 179 | println("the value is: " + element) 180 | } 181 | } 182 | ``` 183 | 184 | When we run this code, we’ll see the same output as in the previous example. More importantly, the code is faster and less prone to errors. 185 | 186 | For example, in the code in the previous example, if you changed the definition of the a array to have four elements but forgot to update the condition to `while index < 4`, the program would crash. Using the `for` loop, you wouldn’t need to remember to change any other code if you changed the number of values in the array. 187 | -------------------------------------------------------------------------------- /docs/concepts/datatypes.md: -------------------------------------------------------------------------------- 1 | # Datatypes 2 | 3 | Antimony comes with some generic data types. 4 | 5 | ## The Boolean type 6 | 7 | As in most other programming languages, a Boolean type in Antimony has two possible values: `true` and `false`. Booleans are one byte in size. The Boolean type in Antimony is specified using `bool`. For example: 8 | 9 | ``` 10 | fn main() { 11 | let t = true 12 | let f: bool = false // with explicit type annotation 13 | } 14 | ``` 15 | 16 | The main way to use Boolean values is through conditionals, such as an `if` expression. We’ll cover how `if` expressions work in the ["Control Flow"](introduction/control-flow.md) section. 17 | 18 | ## The Integer type 19 | 20 | The `integer` datatype represents a 4 byte decimal number. 21 | 22 | ``` 23 | fn main() { 24 | let sum: int = 1 + 2 25 | println("1 + 2 is ", sum) 26 | } 27 | ``` 28 | 29 | ``` 30 | $ sb run main.sb 31 | 1 + 2 is 3 32 | ``` 33 | 34 | Decimal, binary, hexadecimal and octal number systems are supported. The number `255` can be written in these formats: 35 | 36 | ``` 37 | let binary = 0b11111111 38 | let octal = 0o37 39 | let decimal = 255 40 | let hexadecimal = 0xFF 41 | ``` 42 | 43 | To make large numbers more readable, you can insert `_` characters at arbitrary places. These characters will be ignored by the compiler. 44 | 45 | ``` 46 | let one_billion = 1_000_000_000 47 | ``` 48 | 49 | ## The String type 50 | 51 | A string is a sequence of characters. 52 | 53 | ``` 54 | fn main() { 55 | let name: string = "Jon" 56 | println("Hello " + name) 57 | } 58 | ``` 59 | 60 | ``` 61 | $ sb run main.sb 62 | Hello Jon 63 | ``` 64 | 65 | ## The Array type 66 | 67 | Arrays represent a sequence of values. They can hold any number of values of a specific type. 68 | 69 | ``` 70 | fn main() { 71 | let fruits: string[] = ["Banana", "Apple", "Pineapple"] 72 | 73 | for fruit in fruits { 74 | println(fruit) 75 | } 76 | } 77 | ``` 78 | 79 | ``` 80 | $ sb run main.sb 81 | Banana 82 | Apple 83 | Pineapple 84 | ``` 85 | 86 | Arrays have a fixed capacity. In most cases, the capacity of an array can be infered. In the example above, the compiler knows that three elements are in the array, so it can be inferred. If the capacity can't be inferred by the compiler, it is necessary to mark it explicitely. This is the case for uninitialized arrays: 87 | 88 | ``` 89 | let arr: int[3] 90 | arr[0] = 1 91 | arr[1] = 2 92 | arr[2] = 3 93 | 94 | for element in arr { 95 | println(element) 96 | } 97 | ``` 98 | 99 | ## The Any type 100 | 101 | `any` can be used to specify that any type can be used in this place. This should be used with caution, as it might cause undefined behavior. 102 | 103 | ``` 104 | fn main() { 105 | 106 | print_anything(5) 107 | print_anything("Hello") 108 | } 109 | 110 | fn print_anything(x: any) { 111 | println(x) 112 | } 113 | ``` 114 | 115 | ``` 116 | $ sb run main.sb 117 | 5 118 | Hello 119 | ``` 120 | 121 | `any` can also be used in conjunction with the array notation to allow a mixture of types within an array. 122 | 123 | ``` 124 | fn main() { 125 | 126 | let arr = [1, "Two", 3] 127 | 128 | for x in arr { 129 | println(x) 130 | } 131 | } 132 | ``` 133 | 134 | ``` 135 | $ sb run main.sb 136 | 1 137 | Two 138 | 3 139 | ``` 140 | -------------------------------------------------------------------------------- /docs/concepts/functions.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | Functions are pervasive in Antimony code. You’ve already seen one of the most important functions in the language: the `main` function, which is the entry point of many programs. You've also seen the `fn` keyword, which allows you to declare new functions. 4 | 5 | Antimony code uses snake_case as the conventional style for function and variable names. In snake case, all letters are lowercase and underscores separate words. Here’s a program that contains an example function definition: 6 | 7 | ``` 8 | fn main() { 9 | println("Hello, world!") 10 | another_function() 11 | } 12 | 13 | fn another_function() { 14 | println("Another function.") 15 | } 16 | ``` 17 | 18 | We can call any function we’ve defined by entering its name followed by a set of parentheses. Because `another_function` is defined in the program, it can be called from inside the main function. Note that we defined `another_function` after the `main` function in the source code; we could have defined it before as well. Antimony doesn’t care where you define your functions, only that they’re defined somewhere. 19 | 20 | ## Function parameters 21 | 22 | Functions can also be defined to have parameters, which are special variables that are part of a function’s signature. When a function has parameters, you can provide it with concrete values for those parameters. Technically, the concrete values are called arguments, but in casual conversation, people tend to use the words parameter and argument interchangeably for either the variables in a function’s definition or the concrete values passed in when you call a function. 23 | 24 | The following rewritten version of `another_function` shows what parameters look like in Antimony: 25 | 26 | ``` 27 | fn main() { 28 | another_function(5) 29 | } 30 | 31 | fn another_function(x: int) { 32 | println(x) 33 | } 34 | ``` 35 | 36 | ## Return types 37 | 38 | Functions can optionally return a value. To specify the return type, it is added to the function signature, similar to how variables and parameters do. Here's a simple example of a function that returns an integer: 39 | 40 | ``` 41 | fn add_one(x: int): int {} 42 | ``` 43 | 44 | Note that this function won't compile, since it doesn't actually return anything. Let's fix that by adding a `return` statement with an expression: 45 | 46 | ``` 47 | fn add_one(x: int): int { 48 | return x + 1 49 | } 50 | ``` 51 | 52 | Now, if you call the function with `1` as its argument and read its value, you will see the computed result: 53 | 54 | ``` 55 | fn main() { 56 | let result = add_one(1) 57 | println(result) 58 | } 59 | 60 | fn add_one(x: int): int { 61 | return x + 1 62 | } 63 | ``` 64 | 65 | ``` 66 | $ sb run main.sb 67 | 2 68 | ``` 69 | 70 | # Simplified Function Syntax for Single Statements 71 | 72 | Antimony supports a more concise syntax for functions that perform a single operation. This syntax is particularly useful for simple tasks, such as arithmetic operations, printing to the console, or returning a single expression. Instead of wrapping the function's body in curly braces, you can define the function using an equals sign (`=`) followed by the expression that constitutes the function's body. 73 | 74 | ## Syntax 75 | 76 | The syntax for this simplified function declaration is as follows: 77 | 78 | ``` 79 | fn function_name(parameters): return_type = expression 80 | ``` 81 | 82 | This syntax removes the need for curly braces and the `return` keyword for single-statement functions, making the code cleaner and more readable. 83 | 84 | ## Examples 85 | 86 | Below are examples demonstrating how to use this syntax: 87 | 88 | **Defining a function that adds two numbers**: 89 | 90 | ``` 91 | fn add(x: int, y: int): int = x + y 92 | ``` 93 | 94 | **Defining a function that concatenates two strings**: 95 | 96 | ``` 97 | fn concat(a: string, b: string): string = a + b 98 | ``` 99 | -------------------------------------------------------------------------------- /docs/concepts/structured-data.md: -------------------------------------------------------------------------------- 1 | # Structured data 2 | 3 | When working with data, you often find yourself needing to group information together. This is where a `struct` could come into play. A _struct_, or _structure_, is a custom data type that lets you name and package together multiple related values that make up a meaningful group. If you’re familiar with an object-oriented language, a struct is like an object’s data attributes. 4 | 5 | ## Defining structs 6 | 7 | To define a struct, we enter the keyword `struct` and name the entire struct. A struct’s name should describe the significance of the pieces of data being grouped together. Then, inside curly brackets, we define the names and types of the pieces of data, which we call fields. The following example shows a struct that stores information about a user account. 8 | 9 | ``` 10 | struct User { 11 | username: string 12 | email: string 13 | sign_in_count: int 14 | active: bool 15 | } 16 | ``` 17 | 18 | Structs can be nested as a type inside other structs. For example, we could assign each user an address, which itself is a struct. 19 | 20 | ``` 21 | struct Address { 22 | street: string 23 | number: int 24 | postal_code: string 25 | city: string 26 | } 27 | 28 | struct User { 29 | username: string 30 | email: string 31 | address: Address 32 | } 33 | ``` 34 | 35 | ## Instantiating structs 36 | 37 | To use a struct after we’ve defined it, we create an _instance_ of that struct by specifying concrete values for each of the fields. We create an instance by stating the name of the struct and then add curly brackets containing `key: value` pairs, where the keys are the names of the fields and the values are the data we want to store in those fields. We don’t have to specify the fields in the same order in which we declared them in the struct. In other words, the struct definition is like a general template for the type, and instances fill in that template with particular data to create values of the type. Let's use our `User` struct from a previous example and create an user called `alice`. 38 | 39 | ``` 40 | struct User { 41 | username: string 42 | email: string 43 | sign_in_count: int 44 | active: bool 45 | } 46 | 47 | let alice = new User { 48 | email: "alice@example.com" 49 | username: "alice" 50 | sign_in_count: 1 51 | active: true 52 | } 53 | ``` 54 | 55 | To get a specific value from a struct, we can use dot notation. If we wanted just alice's email address, we could use `alice.email` wherever we wanted to use this value. Fields of structs can also be reassigned using the dot notation: 56 | 57 | ``` 58 | let alice = new User { 59 | email: "alice@example.com" 60 | username: "alice" 61 | sign_in_count: 1 62 | active: true 63 | } 64 | 65 | alice.sign_in_count = 2 66 | ``` 67 | 68 | ## Struct methods 69 | 70 | Antimony supports the concept of methods. A method can be described as a function on a struct. Let's take a look at a struct implementing a method. 71 | 72 | ``` 73 | struct User { 74 | first_name: string 75 | last_name: string 76 | 77 | fn full_name(): string { 78 | return self.first_name + " " + self.last_name 79 | } 80 | } 81 | ``` 82 | 83 | Every instance of the `User` struct can now call `full_name()`. Note the usage of the word `self`. `self` is a special keyword referencing the struct instance the method was called on. Say we had the following instance of `User`, and called the `full_name()` method on it. 84 | 85 | ``` 86 | let alice = new User { 87 | first_name: "Jon" 88 | last_name: "Doe" 89 | } 90 | 91 | println(alice.full_name()) 92 | ``` 93 | 94 | The `full_name` method described above will return the first name, a space and the last name of the user. If we run this code, we should see the expected output: 95 | 96 | ``` 97 | $ sb run main.sb 98 | Jon Doe 99 | ``` 100 | 101 | Methods behave just like functions. They can return a value and take parameters. The only difference is the `self` keyword, which allows you to execute it on a specific instance of a struct. 102 | -------------------------------------------------------------------------------- /docs/concepts/variables.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | If you are familiar with some other programming language, the way Antimony handles variables won't surprise you. 4 | 5 | To declare a variable, the `let` keyword is used. The type of the variable is infered, but can be specified explicitly. 6 | 7 | > **Note**: Type inference currently only works when using the node-backend. For most other backends, the types need to be specified, until proper type inference is implemented. 8 | 9 | ``` 10 | // variables.sb 11 | fn main() { 12 | let x = 10 13 | let y: int = 5 14 | println(x + y) 15 | } 16 | ``` 17 | 18 | Run this code using the antimony CLI: 19 | 20 | ``` 21 | $ sb run variables.sb 22 | 15 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/developers/SUMMARY.md: -------------------------------------------------------------------------------- 1 | This chapter includes resources that might be helpful for developers hacking on the Antimony compiler. 2 | -------------------------------------------------------------------------------- /docs/developers/backends.md: -------------------------------------------------------------------------------- 1 | # Backends 2 | 3 | Antimony currently implements a JavaScript backend, but C and QBE backends are in development. WASM, ARM and x86 are planned. 4 | 5 | Backend can be specified when running on building with `--target` (`-t`) option, default is `js`: 6 | 7 | ```sh 8 | sb -t c build in.sb --out-file out 9 | ``` 10 | 11 | ## Available Backends 12 | 13 | | Target Language | Identifier | Stability notice | 14 | | :-------------- | :------------- | :--------------- | 15 | | Node.js | `js` | mostly stable | 16 | | [QBE] | `qbe` | work in progess | 17 | | LLVM | `llvm` | unstable | 18 | | C | `c` | unstable | 19 | 20 | [QBE]: https://c9x.me/compile 21 | 22 | LLVM also requires to enable `llvm` feature when building: 23 | 24 | ```sh 25 | cargo build --features llvm 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/developers/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The easiest way to contribute to the Antimony project is by writing code in the language. The more the language is battle-tested, the more bugs can be found and therefore the language becomes more stable. 4 | 5 | ## Getting in touch 6 | 7 | If you have a question, found a potential bug or want to engage with the community, you can join the [matrix room](https://matrix.to/#/#antimony:matrix.slashdev.space?via=matrix.slashdev.space) of this project. If you prefer to stay away from matrix, you can also send a mail (or a patch) to the [public mailing list](https://lists.sr.ht/~garritfra/antimony). 8 | 9 | ## Fixing things and adding features 10 | 11 | If you want to contribute to the compiler itself, the easiest way to get started is to look at the [open issues](https://github.com/antimony-lang/antimony/issues) of the project. Usually, this is where important todo items are jotted down and bugs are reported. 12 | 13 | You could also run the tests (`cargo test`) and see if any tests are ignored. Usually, if a bug is found in the wild, a failing but ignored test is written, so that it can be further investigated later. 14 | 15 | ## Writing documentation 16 | 17 | As with all software, Antimony needs good documentation. Since Antimony is still in early development, things change constantly. This means that docs will be out of date in a lot of cases, or not written at all. Any help with the documentation is greatly appreciated! 18 | 19 | ## Submitting your code 20 | 21 | If you want to contribute code, please open a pull request on [GitHub](https://github.com/antimony-lang/antimony). There is also a [SourceHut mirror](https://sr.ht/~garritfra/antimony/), if you're trying to avoid GitHub. Feel free to send a patch to the [public mailing list](https://lists.sr.ht/~garritfra/antimony). Check out [this guide](https://slashdev.space/posts/patch-based-git-workflow) to learn about the patch based workflow. 22 | 23 | Before submitting the code, please make sure that it is **sufficiently documented and tested**. 24 | -------------------------------------------------------------------------------- /docs/developers/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | > **NOTE**: Currently, debugging is still nearly impossible in Antimony. 4 | 5 | This document will give you some hints on debugging the Antimony compiler. 6 | 7 | ## Viewing the generated source code 8 | 9 | Programs can be compiled to stdout. Use the `-o -` flag in combination with the 10 | target backend: 11 | 12 | ```sh 13 | cargo run -- -t js build -o - examples/fib.sb 14 | ``` 15 | 16 | Or, if Antimony is installed in your path: 17 | 18 | ```sh 19 | sb -t js build -o - examples/fib.sb 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /docs/developers/releasing.md: -------------------------------------------------------------------------------- 1 | # Release Workflow 2 | 3 | 1. Run prerelease checks: `sh contrib/prerelease_check.sh` 4 | 1. Update version in `Cargo.toml` 5 | 1. Add entry in `CHANGELOG.md` 6 | 1. Commit change with semantic version number (`v0.1.1`) 7 | 1. Tag commit using `git tag -a -m "$(git shortlog ..HEAD)"` 8 | 1. Push the tag using `git push --tags` 9 | 1. Publish package using `cargo publish` 10 | -------------------------------------------------------------------------------- /docs/developers/specification.md: -------------------------------------------------------------------------------- 1 | # Specification 2 | 3 | > **Note**: This specification is a work in progress. 4 | 5 | ## Introduction 6 | 7 | This is a reference manual for the Antimony programming language. 8 | 9 | Antimony is a general-purpose language designed with simplicity in mind. It is 10 | strongly typed and supports multiple compile-targets. Programs are constructed 11 | from modules, whose properties allow efficient management of dependencies. 12 | 13 | ## Notation 14 | 15 | The syntax is specified using altered Extended Backus-Naur Form (EBNF): 16 | 17 | ``` 18 | Production = production_name "=" [ Expression ] "." . 19 | Expression = Alternative { "|" Alternative } . 20 | Alternative = Term { Term } . 21 | Term = production_name | token [ "..." token ] | Group | Option | Repetition . 22 | Group = "(" Expression ")" . 23 | Option = "[" Expression "]" . 24 | Repetition = "{" Expression "}" . 25 | ``` 26 | 27 | Productions are expressions constructed from terms and the following operators, 28 | in increasing precedence: 29 | 30 | ``` 31 | | alternation 32 | () grouping 33 | [] option (0 or 1 times) 34 | {} repetition (0 to n times) 35 | ``` 36 | 37 | Lower-case production names are used to identify lexical tokens. Non-terminals 38 | are in CamelCase. Lexical tokens are enclosed in double quotes `""` or single 39 | quotes `''`. 40 | 41 | The form `a ... b` represents the set of characters from `a` through `b` as 42 | alternatives. The horizontal ellipsis `...` is also used elsewhere in the spec 43 | to informally denote various enumerations or code snippets that are not further 44 | specified. The character `…` (as opposed to the three characters `...`) is not a 45 | token of the Antimony language. 46 | 47 | ## Source Code Representation 48 | 49 | Source code is Unicode text encoded in 50 | [UTF-8](https://en.wikipedia.org/wiki/UTF-8). The text is not canonicalized, so 51 | a single accented code point is distinct from the same character constructed 52 | from combining an accent and a letter; those are treated as two code points. For 53 | simplicity, this document will use the unqualified term _character_ to refer to 54 | a Unicode code point in the source text. 55 | 56 | Each code point is distinct; for instance, upper and lower case letters are 57 | different characters. 58 | 59 | Implementation restriction: For compatibility with other tools, a compiler may 60 | disallow the NUL character (U+0000) in the source text. 61 | 62 | ### Characters 63 | 64 | The following terms are used to denote specific Unicode character classes: 65 | 66 | ``` 67 | newline = /* the Unicode code point U+000A */ . 68 | unicode_char = /* an arbitrary Unicode code point except newline */ . 69 | unicode_letter = /* a Unicode code point classified as "Letter" */ . 70 | unicode_digit = /* a Unicode code point classified as "Number, decimal digit" */ . 71 | ``` 72 | 73 | ### Letters and digits 74 | 75 | The underscore character `_` (U+005F) is considered a letter. 76 | 77 | ``` 78 | letter = unicode_letter | "_" . 79 | decimal_digit = "0" ... "9" . 80 | binary_digit = "0" | "1" . 81 | octal_digit = "0" ... "7" . 82 | hex_digit = "0" ... "9" | "A" ... "F" | "a" ... "f" . 83 | ``` 84 | 85 | ## Lexical elements 86 | 87 | ### Comments 88 | 89 | Comments serve as program documentation. A comment starts with the character 90 | sequence // and stop at the end of the line. 91 | 92 | A comment cannot start inside a string literal, or inside a comment. 93 | 94 | ### Tokens 95 | 96 | Tokens form the vocabulary of the Antimony programming language. There are four 97 | classes: _identifiers_, _keywords_, _operators and punctuation_, and _literals_. 98 | _White space_, formed from spaces (U+0020), horizontal tabs (U+0009), carriage 99 | returns (U+000D), and newlines (U+000A), is ignored except as it separates 100 | tokens that would otherwise combine into a single token. 101 | 102 | ### Identifiers 103 | 104 | Identifiers name program entities such as variables and types. An identifier is 105 | a sequence of one or more letters and digits. The first character in an 106 | identifier must be a letter. 107 | 108 | ``` 109 | identifier = letter { letter | unicode_digit } . 110 | ``` 111 | 112 | ``` 113 | a 114 | _x9 115 | This_is_aValidIdentifier 116 | αβ 117 | ``` 118 | 119 | ### Keywords 120 | 121 | The following keywords are reserved and may not be used as identifiers. 122 | 123 | ``` 124 | break 125 | continue 126 | else 127 | false 128 | fn 129 | for 130 | if 131 | import 132 | in 133 | let 134 | match 135 | new 136 | return 137 | self 138 | struct 139 | true 140 | while 141 | ``` 142 | 143 | ### Operators and Punctuation 144 | 145 | The following character sequences represent operators (including assignment operators) and punctuation: 146 | 147 | ``` 148 | + 149 | += 150 | && 151 | == 152 | != 153 | ( 154 | ) 155 | - 156 | -= 157 | || 158 | < 159 | <= 160 | [ 161 | ] 162 | * 163 | *= 164 | > 165 | >= 166 | { 167 | } 168 | / 169 | /= 170 | ++ 171 | = 172 | , 173 | ; 174 | % 175 | -- 176 | ! 177 | . 178 | : 179 | ``` 180 | 181 | ### Integer Literals 182 | 183 | An integer literal is a sequence of digits representing an integer constant. An 184 | optional prefix sets a non-decimal base: `0b` or `0B` for binary, `0`, `0o`, or 185 | `0O` for octal, and `0x` or `0X` for hexadecimal. A single `0` is considered a 186 | decimal zero. In hexadecimal literals, letters `a` through `f` and `A` through 187 | `F` represent values `10` through `15`. 188 | 189 | For readability, an underscore character `_` may appear after a base prefix or 190 | between successive digits; such underscores do not change the literal's value. 191 | 192 | ``` 193 | int_lit = decimal_lit | binary_lit | octal_lit | hex_lit . 194 | decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] . 195 | binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits . 196 | octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits . 197 | hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits . 198 | 199 | decimal_digits = decimal_digit { [ "_" ] decimal_digit } . 200 | binary_digits = binary_digit { [ "_" ] binary_digit } . 201 | octal_digits = octal_digit { [ "_" ] octal_digit } . 202 | hex_digits = hex_digit { [ "_" ] hex_digit } . 203 | 204 | 42 205 | 4_2 206 | 0600 207 | 0_600 208 | 0o600 209 | 0O600 // second character is capital letter 'O' 210 | 0xBadFace 211 | 0xBad_Face 212 | 0x_67_7a_2f_cc_40_c6 213 | 170141183460469231731687303715884105727 214 | 170_141183_460469_231731_687303_715884_105727 215 | 216 | _42 // an identifier, not an integer literal 217 | 42_ // invalid: _ must separate successive digits 218 | 4__2 // invalid: only one _ at a time 219 | 0_xBadFace // invalid: _ must separate successive digits 220 | ``` 221 | 222 | ### Floating-point literals 223 | 224 | TO BE IMPLEMENTED 225 | 226 | ### Rune literals 227 | 228 | TO BE IMPLEMENTED 229 | 230 | ### String literals 231 | 232 | A string literal represents a string constant obtained from concatenating a 233 | sequence of characters. String literals are character sequences between double 234 | quotes, as in "bar". Within the quotes, any character may appear except newline 235 | and unescaped double quote. 236 | 237 | If `\` character appears in the string, the character(s) following it *must* be 238 | interpreted specially: 239 | 240 | 1. `\` and `"` are included unchanged (e.g. `"C:\\Users"` -> `C:\Users`) 241 | 2. `n` emits the newline control chracter (U+000A) 242 | 3. `r` emits the carriage return control chracter (U+000D) 243 | 4. `b` emits the backspace control character (U+000C) 244 | 5. `t` emits a horizontal tab (U+0009) 245 | 6. `f` emits a form feed (U+000C) 246 | 7. Unknown escape sequences *must* raise a compile error 247 | 248 | TODO: byte values 249 | 250 | TODO: Currently, `"` and `'` are valid string characters. Remove `'` and only 251 | use them for runes. 252 | 253 | ``` 254 | string_escape = 255 | "\n" | # Newline (U+000A) 256 | "\r" | # Carriage return (U+000D) 257 | "\t" | # Horizontal tab (U+0009) 258 | "\f" | # Form feed (U+000C) 259 | "\b" | # Backspace (U+0008) 260 | `\"` | "\\" 261 | any = /* Any Unicode code point except newline (U+000A) and double quote (U+0022) */ . 262 | string_lit = `"` { any | string_escape } `"` . 263 | 264 | "abc" 265 | "Hello, world!" 266 | "Hello\nworld" 267 | "C:\\Users" # Should emit C:\Users 268 | "日本語" 269 | ``` -------------------------------------------------------------------------------- /docs/introduction/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Antimony is a bullshit-free (©) programming language that gets out of your way. 4 | It is meant to "just work", without adding unnecessary and bloated language features. 5 | 6 | To get started with Antimony, continue with the [installation](introduction/installation.md). 7 | 8 | > **Note**: Some parts of this documentation have been copied from the [Rust book](https://doc.rust-lang.org/book/). 9 | 10 | > **Note**: I consider this documentation _usable_, but it may be incomplete in some places. If you feel like that a feature or behavior should be documented, feel free to contact the maintainers. You can search for the word _TODO_, if you want to help improving this documentation but don't know where to start. Any help is always welcome! 11 | -------------------------------------------------------------------------------- /docs/introduction/hello-world.md: -------------------------------------------------------------------------------- 1 | # The command line interface 2 | 3 | Now that you have installed Antimony, it is time to write our first program. This is a program that will simply print a string to the screen. 4 | 5 | # Creating a project directory 6 | 7 | Let's begin by setting up our development workspace. Antimony really doesn't care where you store the code, so feel free to choose a different directory, than the one in this example. 8 | 9 | ``` 10 | mkdir ~/sources 11 | cd ~/sources 12 | mkdir hello_world 13 | cd hello_world 14 | ``` 15 | 16 | # Writing and running a program 17 | 18 | Next, make a new source file and call it `main.sb`. Antimony files should always end with `.sb` by convention. 19 | 20 | Now open the main.sb file you just created and enter the following code: 21 | 22 | ``` 23 | fn main() { 24 | println("Hello, world!") 25 | } 26 | ``` 27 | 28 | Save the file and go back to your terminal window. Now, run the following command to compile and run your program: 29 | 30 | ``` 31 | $ sb run main.sb 32 | ``` 33 | 34 | You should see the string `Hello World!` on the screen. Congrats! You have officially written a Antimony Program! 35 | -------------------------------------------------------------------------------- /docs/introduction/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | > **Note:** An installation of the Rust programming language is needed to compile Antimony. 4 | 5 | ## Cargo 6 | 7 | The fastest way to get up and running is to install the [latest published version](https://crates.io/crates/antimony-lang) via cargo: 8 | 9 | ```sh 10 | cargo install antimony-lang 11 | ``` 12 | 13 | ## Git 14 | 15 | To get the current development version, you can clone the Git [repository](https://github.com/antimony-lang/antimony) and run the following command: 16 | 17 | ```sh 18 | cargo install --path . 19 | ``` 20 | 21 | ## Docker 22 | 23 | Antimony provides a [Docker image](https://hub.docker.com/r/antimony-lang/antimony). It currently only supports the x64 architecture. Please reach out if you need a ARM variant (needed for Raspberry Pi). If you don't want to wait, you can build the image yourself by running this command in the root of the project: 24 | 25 | ``` 26 | docker build . -t antimony 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/modules/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Modules and Imports 2 | 3 | Projects naturally grow over time, and digging through 10.000 lines of code in a single file can be cumbersome. By grouping related functionality and separating code with distinct features, you’ll clarify where to find code that implements a particular feature and where to go to change how a feature works. 4 | 5 | The programs we've written so far have been in one file. As a project grows, you can organize code by splitting it into multiple modules with a clear name. 6 | 7 | In Antimony, every file is also a module. Let's take a look at a project structure and identify its modules. 8 | 9 | ``` 10 | . 11 | ├── foo 12 | │   ├── bar.sb 13 | │   └── baz 14 | │   └── module.sb 15 | ├── main.sb 16 | └── some_logic.sb 17 | ``` 18 | 19 | As per convention, the entrypoint for this project is the `main.sb` file in the root directory. 20 | 21 | There is a child-module called `some_logic` at the same directory-level. 22 | 23 | Below it, there is a directory called `foo`, containing the submodule `bar`. To address the `bar` module from our entrypoint, we'd import the following: 24 | 25 | ``` 26 | import "foo/bar" 27 | ``` 28 | 29 | > **Note**: File extensions in imports are optional. Importing `foo/bar.sb` would yield the same result as importing `foo/bar`. 30 | 31 | ## Module entrypoints 32 | 33 | In the `foo` directory, there is another directory called `baz`, containing a single file named `module.sb`. This file is treated as a special file, since it serves as the entrypoint for that module. So, instead of importing the file explicitely: 34 | 35 | ``` 36 | // main.sb 37 | import "foo/baz/module" 38 | ``` 39 | 40 | we can simply import the module containing this file, and Antimony will import the contained `module.sb` instead. 41 | 42 | ``` 43 | // main.sb 44 | import "foo/baz" 45 | ``` 46 | 47 | ## Using imported modules 48 | 49 | To use code defined in a separate module, we first need to import it. This is usually done at the top of the file, but it technically doesn't make a difference where in the document the import is defined. Once the module is imported, we can use the code inside it, as if it were in the current file. 50 | 51 | Let's say we have a module named `math.sb` in the same directory as out `main.sb`, and it defines the function `add(x: int, y: int): int`. To call it in our `main.sb`, we'd do the following: 52 | 53 | ``` 54 | import "math" 55 | 56 | fn main() { 57 | println(add(1, 2)) 58 | } 59 | ``` 60 | 61 | If we run `main.sb`, we should see the expected output. Antimony has imported the `add` function from the `math` module. 62 | 63 | ``` 64 | $ sb run main.sb 65 | 3 66 | ``` 67 | -------------------------------------------------------------------------------- /examples/ackermann.sb: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let m = 3 3 | let n = 3 4 | println(ackermann(m, n)) 5 | } 6 | 7 | fn ackermann(m: int, n: int): int { 8 | if m == 0 { 9 | return n + 1 10 | } else if n == 0 { 11 | return ackermann(m - 1, 1) 12 | } else { 13 | return ackermann(m - 1, ackermann(m, n - 1)) 14 | } 15 | } -------------------------------------------------------------------------------- /examples/bubblesort.sb: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let arr = [2, 5, 3, 1, 4] 3 | let n = len(arr) 4 | 5 | let c = 0 6 | while c < n { 7 | let d = 0 8 | while d < n - c - 1 { 9 | let current = arr[d] 10 | let next = arr[d+1] 11 | if current > next { 12 | let swap = arr[d] 13 | arr[d] = arr[d+1] 14 | arr[d+1] = swap 15 | } 16 | 17 | d += 1 18 | } 19 | 20 | c += 1 21 | } 22 | 23 | println(arr) 24 | } 25 | -------------------------------------------------------------------------------- /examples/fib.sb: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let num: int = 10 3 | println(fib(num)) 4 | } 5 | 6 | fn fib(n: int): int { 7 | if 1 >= n { 8 | return n 9 | } 10 | return fib(n-1) + fib(n-2) 11 | } -------------------------------------------------------------------------------- /examples/greeter.sb: -------------------------------------------------------------------------------- 1 | fn greet(name: string) = "Hello " + name 2 | 3 | fn main() { 4 | println(greet("World")) 5 | } 6 | -------------------------------------------------------------------------------- /examples/hello_world.sb: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println("Hello World") 3 | } 4 | -------------------------------------------------------------------------------- /examples/leapyear.sb: -------------------------------------------------------------------------------- 1 | // There are no nested expressions yet, so we have to hack a little bit 2 | 3 | fn main() { 4 | let year = 2020 5 | 6 | let divisibleBy4 = year % 4 == 0 7 | let divisibleBy100 = year % 100 != 0 8 | let divisibleBy400 = year % 400 == 0 9 | 10 | let ly = divisibleBy4 && divisibleBy100 11 | 12 | if ly || divisibleBy400 { 13 | println("Leap year") 14 | } else { 15 | println("Not a leap year") 16 | } 17 | } -------------------------------------------------------------------------------- /examples/loops.sb: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let arr = ["One", "Two", "Three"] 3 | for x in arr { 4 | println(x) 5 | } 6 | 7 | for fruit in ["Apple", "Strawberry", "Orange"] { 8 | println(fruit) 9 | } 10 | } -------------------------------------------------------------------------------- /examples/sandbox.sb: -------------------------------------------------------------------------------- 1 | struct Point { 2 | x: int 3 | y: int 4 | } 5 | 6 | struct Rectangle { 7 | origin: Point 8 | width: int 9 | height: int 10 | } 11 | 12 | fn main(): int { 13 | let rect = new Rectangle { 14 | origin: new Point { 15 | x: 10 16 | y: 20 17 | } 18 | width: 100 19 | height: 50 20 | } 21 | rect.origin.x == 10 22 | rect.origin.x += 5 23 | rect.origin.x == 15 24 | 25 | return 0 26 | } 27 | -------------------------------------------------------------------------------- /lib/array.sb: -------------------------------------------------------------------------------- 1 | // Prints the size of an array 2 | fn len(arr: int[]): int { 3 | let c: int = 0 4 | while arr[c] { 5 | c += 1 6 | } 7 | 8 | return c 9 | } 10 | 11 | // Reverses an array 12 | // TODO: fix me! 13 | fn rev(arr: int[]): int[] { 14 | 15 | let l: int = len(arr) 16 | let new_arr: int[] = [] 17 | 18 | let i: int = 0 19 | let j: int = l 20 | while i < l { 21 | new_arr[i] = arr[j] 22 | i = i - 1 23 | j = j - 1 24 | } 25 | 26 | return new_arr 27 | } -------------------------------------------------------------------------------- /lib/assert.sb: -------------------------------------------------------------------------------- 1 | fn assert(condition: bool) { 2 | if condition == false { 3 | println("Assertion failed") 4 | exit(1) 5 | } 6 | } -------------------------------------------------------------------------------- /lib/io.sb: -------------------------------------------------------------------------------- 1 | // Raw wrapper around _printf builtin function. 2 | // Writes the given content to stdout 3 | fn print(arg: string) { 4 | _printf(arg) 5 | } 6 | 7 | // Like print(), but with an extra newline ('\n') character 8 | fn println(msg: string) { 9 | print(msg + "\n") 10 | } 11 | -------------------------------------------------------------------------------- /lib/os.sb: -------------------------------------------------------------------------------- 1 | // Exit the program immediately 2 | fn exit(code: int) { 3 | _exit(code) 4 | } -------------------------------------------------------------------------------- /src/ast/README.md: -------------------------------------------------------------------------------- 1 | # AST 2 | 3 | This module contains the node types of the Antimony AST. 4 | 5 | The most important node is `Module`, which is the abstract representation of a Antimony file. 6 | -------------------------------------------------------------------------------- /src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::*; 2 | use core::convert::TryFrom; 3 | use std::collections::HashMap; 4 | /** 5 | * Copyright 2021 Garrit Franke 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * https://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | use std::collections::HashSet; 20 | 21 | pub mod types; 22 | use types::Type; 23 | 24 | /// Table that contains all symbol and its types 25 | pub type SymbolTable = HashMap>; 26 | 27 | #[derive(Debug, Clone)] 28 | pub struct Module { 29 | pub imports: HashSet, 30 | pub func: Vec, 31 | pub structs: Vec, 32 | pub globals: Vec, 33 | } 34 | 35 | impl Module { 36 | pub fn merge_with(&mut self, mut other: Module) { 37 | self.func.append(&mut other.func); 38 | self.structs.append(&mut other.structs); 39 | self.globals.append(&mut other.globals) 40 | } 41 | 42 | pub fn get_symbol_table(&self) -> SymbolTable { 43 | let mut table = SymbolTable::new(); 44 | 45 | for func in self.func.clone() { 46 | table.insert(func.name, func.ret_type); 47 | } 48 | 49 | table 50 | } 51 | } 52 | 53 | #[derive(Debug, Clone)] 54 | pub struct Function { 55 | pub name: String, 56 | pub arguments: Vec, 57 | pub body: Statement, 58 | pub ret_type: Option, 59 | } 60 | 61 | #[derive(Debug, Clone)] 62 | pub struct StructDef { 63 | pub name: String, 64 | pub fields: Vec, 65 | pub methods: Vec, 66 | } 67 | 68 | #[derive(Debug, Eq, PartialEq, Clone)] 69 | pub struct Variable { 70 | pub name: String, 71 | pub ty: Option, 72 | } 73 | 74 | impl AsRef for Variable { 75 | fn as_ref(&self) -> &Self { 76 | self 77 | } 78 | } 79 | 80 | #[derive(Debug, Eq, PartialEq, Clone)] 81 | pub enum Statement { 82 | /// (Statements, Scoped variables) 83 | Block { 84 | statements: Vec, 85 | scope: Vec, 86 | }, 87 | Declare { 88 | variable: Variable, 89 | value: Option, 90 | }, 91 | Assign { 92 | lhs: Box, 93 | rhs: Box, 94 | }, 95 | Return(Option), 96 | If { 97 | condition: Expression, 98 | body: Box, 99 | else_branch: Option>, 100 | }, 101 | While { 102 | condition: Expression, 103 | body: Box, 104 | }, 105 | For { 106 | ident: Variable, 107 | expr: Expression, 108 | body: Box, 109 | }, 110 | Match { 111 | subject: Expression, 112 | arms: Vec, 113 | }, 114 | Break, 115 | Continue, 116 | Exp(Expression), 117 | } 118 | 119 | #[derive(Debug, Eq, PartialEq, Clone)] 120 | pub enum Expression { 121 | Int(usize), 122 | Str(String), 123 | Bool(bool), 124 | /// Represents "self" keyword 125 | Selff, 126 | Array { 127 | capacity: usize, 128 | elements: Vec, 129 | }, 130 | FunctionCall { 131 | fn_name: String, 132 | args: Vec, 133 | }, 134 | Variable(String), 135 | ArrayAccess { 136 | name: String, 137 | index: Box, 138 | }, 139 | BinOp { 140 | lhs: Box, 141 | op: BinOp, 142 | rhs: Box, 143 | }, 144 | StructInitialization { 145 | name: String, 146 | fields: HashMap>, 147 | }, 148 | FieldAccess { 149 | expr: Box, 150 | field: Box, 151 | }, 152 | } 153 | 154 | impl TryFrom for Expression { 155 | type Error = String; 156 | 157 | fn try_from(token: Token) -> std::result::Result { 158 | let kind = token.kind; 159 | match kind { 160 | TokenKind::Identifier(val) => Ok(Expression::Variable(val)), 161 | TokenKind::Literal(Value::Int) => Ok(Expression::Int( 162 | token 163 | .raw 164 | .parse() 165 | .map_err(|_| "Int value could not be parsed")?, 166 | )), 167 | TokenKind::Keyword(Keyword::Boolean) => match token.raw.as_ref() { 168 | "true" => Ok(Expression::Bool(true)), 169 | "false" => Ok(Expression::Bool(false)), 170 | _ => Err("Boolean value could not be parsed".into()), 171 | }, 172 | TokenKind::Literal(Value::Str(string)) => Ok(Expression::Str(string)), 173 | _ => Err("Value could not be parsed".into()), 174 | } 175 | } 176 | } 177 | 178 | #[derive(Debug, Eq, PartialEq, Clone)] 179 | pub enum MatchArm { 180 | Case(Expression, Statement), 181 | Else(Statement), 182 | } 183 | 184 | #[derive(Debug, Eq, PartialEq, Clone)] 185 | pub enum BinOp { 186 | Addition, 187 | Subtraction, 188 | Multiplication, 189 | Division, 190 | Modulus, 191 | LessThan, 192 | LessThanOrEqual, 193 | GreaterThan, 194 | GreaterThanOrEqual, 195 | Equal, 196 | NotEqual, 197 | And, 198 | Or, 199 | AddAssign, 200 | SubtractAssign, 201 | MultiplyAssign, 202 | DivideAssign, 203 | } 204 | 205 | impl TryFrom for BinOp { 206 | type Error = String; 207 | fn try_from(token: TokenKind) -> Result { 208 | match token { 209 | TokenKind::Star => Ok(BinOp::Multiplication), 210 | TokenKind::Slash => Ok(BinOp::Division), 211 | TokenKind::Plus => Ok(BinOp::Addition), 212 | TokenKind::Minus => Ok(BinOp::Subtraction), 213 | TokenKind::Percent => Ok(BinOp::Modulus), 214 | TokenKind::LessThan => Ok(BinOp::LessThan), 215 | TokenKind::GreaterThan => Ok(BinOp::GreaterThan), 216 | TokenKind::Equals => Ok(BinOp::Equal), 217 | TokenKind::LessThanOrEqual => Ok(BinOp::LessThanOrEqual), 218 | TokenKind::GreaterThanOrEqual => Ok(BinOp::GreaterThanOrEqual), 219 | TokenKind::NotEqual => Ok(BinOp::NotEqual), 220 | TokenKind::And => Ok(BinOp::And), 221 | TokenKind::Or => Ok(BinOp::Or), 222 | TokenKind::PlusEqual => Ok(BinOp::AddAssign), 223 | TokenKind::MinusEqual => Ok(BinOp::SubtractAssign), 224 | TokenKind::StarEqual => Ok(BinOp::MultiplyAssign), 225 | TokenKind::SlashEqual => Ok(BinOp::DivideAssign), 226 | other => Err(format!( 227 | "Token {:?} cannot be converted into a BinOp", 228 | other 229 | )), 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/ast/types.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use std::convert::TryFrom; 17 | 18 | #[derive(Debug, Eq, PartialEq, Clone)] 19 | pub enum Type { 20 | Any, 21 | Int, 22 | Str, 23 | Bool, 24 | Array(Box, Option), 25 | Struct(String), 26 | } 27 | 28 | impl TryFrom for Type { 29 | type Error = String; 30 | fn try_from(s: String) -> Result { 31 | match s.as_ref() { 32 | "int" => Ok(Self::Int), 33 | "string" => Ok(Self::Str), 34 | "any" => Ok(Self::Any), 35 | "bool" => Ok(Self::Bool), 36 | name => Ok(Self::Struct(name.to_string())), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/builder/README.md: -------------------------------------------------------------------------------- 1 | # Builder 2 | 3 | The builder module provides a simple interface to compile a Antimony program. 4 | 5 | The `Builder` structure takes in a path to a Antimony program. Calling `.build()` will construct the internal representation of the program, and `.generate()` outputs the program to a target file. 6 | -------------------------------------------------------------------------------- /src/builder/mod.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::ast::Module; 17 | use crate::generator::{self, Generator, Target}; 18 | use crate::lexer; 19 | use crate::parser; 20 | use crate::Lib; 21 | use crate::PathBuf; 22 | use std::env; 23 | use std::fs::File; 24 | use std::io::Read; 25 | use std::io::Write; 26 | 27 | pub struct Builder { 28 | in_file: PathBuf, 29 | modules: Vec, 30 | } 31 | 32 | impl Builder { 33 | pub fn new(entrypoint: PathBuf) -> Self { 34 | Self { 35 | in_file: entrypoint, 36 | modules: Vec::new(), 37 | } 38 | } 39 | 40 | fn get_base_path(&self) -> Result { 41 | Ok(self 42 | .in_file 43 | .parent() 44 | .ok_or("File does not have a parent")? 45 | .to_path_buf()) 46 | } 47 | 48 | pub fn build(&mut self, target: &Target) -> Result<(), String> { 49 | let in_file = self.in_file.clone(); 50 | // Resolve path deltas between working directory and entrypoint 51 | let base_directory = self.get_base_path()?; 52 | 53 | // During building, we change the environment directory. 54 | // After we're done, we have to set it back to the initial directory. 55 | let initial_directory = env::current_dir().expect("Current directory does not exist"); 56 | if let Ok(resolved_delta) = in_file.strip_prefix(&base_directory) { 57 | // TODO: This error could probably be handled better 58 | let _ = env::set_current_dir(base_directory); 59 | self.in_file = resolved_delta.to_path_buf(); 60 | } 61 | self.build_module(self.in_file.clone(), &mut Vec::new())?; 62 | 63 | // Append standard library 64 | if matches!(target, Target::JS) { 65 | self.build_stdlib()?; 66 | } 67 | 68 | // Change back to the initial directory 69 | env::set_current_dir(initial_directory).expect("Could not set current directory"); 70 | Ok(()) 71 | } 72 | 73 | fn build_module( 74 | &mut self, 75 | file_path: PathBuf, 76 | seen: &mut Vec, 77 | ) -> Result { 78 | // TODO: This method can probably cleaned up quite a bit 79 | 80 | // In case the module is a directory, we have to append the filename of the entrypoint 81 | let resolved_file_path = if file_path.is_dir() { 82 | file_path.join("module.sb") 83 | } else { 84 | file_path 85 | }; 86 | let mut file = File::open(&resolved_file_path) 87 | .map_err(|_| format!("Could not open file: {}", resolved_file_path.display()))?; 88 | let mut contents = String::new(); 89 | 90 | file.read_to_string(&mut contents) 91 | .expect("Could not read file"); 92 | let tokens = lexer::tokenize(&contents)?; 93 | let module = parser::parse(tokens, Some(contents))?; 94 | for import in &module.imports { 95 | // Prevent circular imports 96 | if seen.contains(import) { 97 | continue; 98 | } else { 99 | seen.push(import.to_string()); 100 | } 101 | // Build module relative to the current file 102 | let mut import_path = resolved_file_path 103 | .parent() 104 | .unwrap() 105 | .join(PathBuf::from(import)); 106 | 107 | if import_path.is_dir() { 108 | import_path = import_path.join("module.sb"); 109 | } else if !import_path.ends_with(".sb") { 110 | import_path.set_extension("sb"); 111 | } 112 | 113 | self.build_module(import_path, seen)?; 114 | } 115 | self.modules.push(module.clone()); 116 | Ok(module) 117 | } 118 | 119 | pub(crate) fn generate( 120 | &mut self, 121 | target: &Target, 122 | buffer: &mut Box, 123 | ) -> Result<(), String> { 124 | let mut mod_iter = self.modules.iter(); 125 | 126 | // TODO: We shouldn't clone here 127 | let mut condensed = mod_iter.next().ok_or("No module specified")?.clone(); 128 | for module in mod_iter { 129 | condensed.merge_with(module.clone()); 130 | } 131 | 132 | let output = match target { 133 | Target::JS => generator::js::JsGenerator::generate(condensed)?, 134 | Target::C => generator::c::CGenerator::generate(condensed)?, 135 | Target::Llvm => { 136 | #[cfg(not(feature = "llvm"))] 137 | panic!("'llvm' feature should be enabled to use LLVM target"); 138 | 139 | #[cfg(feature = "llvm")] 140 | generator::llvm::LLVMGenerator::generate(condensed)? 141 | } 142 | Target::Qbe => generator::qbe::QbeGenerator::generate(condensed)?, 143 | Target::X86 => generator::x86::X86Generator::generate(condensed)?, 144 | }; 145 | 146 | buffer.write_all(output.as_bytes()).expect("write failed"); 147 | buffer.flush().map_err(|_| "Could not flush file".into()) 148 | } 149 | 150 | fn build_stdlib(&mut self) -> Result<(), String> { 151 | let assets = Lib::iter(); 152 | 153 | for file in assets { 154 | let stdlib_raw = Lib::get(&file) 155 | .expect("Standard library not found. This should not occur.") 156 | .data; 157 | let stblib_str = 158 | std::str::from_utf8(&stdlib_raw).expect("Could not interpret standard library."); 159 | let stdlib_tokens = lexer::tokenize(stblib_str)?; 160 | let module = parser::parse(stdlib_tokens, Some(stblib_str.into())) 161 | .expect("Could not parse stdlib"); 162 | self.modules.push(module); 163 | } 164 | 165 | Ok(()) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/command/build.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::builder; 17 | use crate::generator::Target; 18 | use std::fs::File; 19 | use std::io::stdout; 20 | use std::io::Write; 21 | use std::path::Path; 22 | 23 | pub fn build(target: &Target, in_file: &Path, out_file: &Path) -> Result<(), String> { 24 | let mut buf = Box::>::default(); 25 | build_to_buffer(target, in_file, &mut buf)?; 26 | 27 | if out_file.to_str() == Some("-") { 28 | stdout() 29 | .write_all(&buf) 30 | .map_err(|e| format!("Could not write to stdout: {}", e)) 31 | } else { 32 | File::create(out_file) 33 | .map_err(|e| format!("Could not create output file: {}", e))? 34 | .write_all(&buf) 35 | .map_err(|e| format!("Could not write to file: {}", e)) 36 | } 37 | } 38 | 39 | pub fn build_to_buffer( 40 | target: &Target, 41 | in_file: &Path, 42 | buf: &mut Box, 43 | ) -> Result<(), String> { 44 | let mut b = builder::Builder::new(in_file.to_path_buf()); 45 | b.build(target)?; 46 | b.generate(target, buf) 47 | } 48 | -------------------------------------------------------------------------------- /src/command/mod.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pub mod build; 17 | pub mod run; 18 | -------------------------------------------------------------------------------- /src/command/run.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::command::build; 17 | use crate::generator::Target; 18 | use std::fs::OpenOptions; 19 | use std::io::{Read, Write}; 20 | use std::path::{Path, PathBuf}; 21 | use std::process::{Command, Stdio}; 22 | 23 | type Result = std::result::Result; 24 | 25 | fn run_command(cmd: &mut Command) -> Result<()> { 26 | cmd.spawn() 27 | .map_err(|e| format!("Failed to spawn process: {}", e))? 28 | .wait() 29 | .map_err(|e| format!("Failed to wait for process: {}", e)) 30 | .map(|_| ()) 31 | } 32 | 33 | fn run_node(buf: &[u8]) -> Result<()> { 34 | let process = Command::new("node") 35 | .stdin(Stdio::piped()) 36 | .stdout(Stdio::piped()) 37 | .stderr(Stdio::piped()) 38 | .spawn() 39 | .map_err(|e| format!("Could not spawn Node.js process: {}", e))?; 40 | 41 | // Write to stdin 42 | process 43 | .stdin 44 | .ok_or("Failed to open stdin")? 45 | .write_all(buf) 46 | .map_err(|e| format!("Could not write to Node.js process: {}", e))?; 47 | 48 | // Read from stdout 49 | let mut output = Vec::new(); 50 | process 51 | .stdout 52 | .ok_or("Failed to open stdout")? 53 | .read_to_end(&mut output) 54 | .map_err(|e| format!("Could not read from child process: {}", e))?; 55 | 56 | // Write to stdout 57 | std::io::stdout() 58 | .write_all(&output) 59 | .map_err(|e| format!("Could not write to stdout: {}", e)) 60 | } 61 | 62 | fn run_qbe(buf: Vec, in_file: &Path) -> Result<()> { 63 | let dir_path = "./"; // TODO: Use this for changing build directory 64 | let filename = in_file 65 | .file_stem() 66 | .and_then(|s| s.to_str()) 67 | .ok_or("Invalid filename")?; 68 | 69 | // Create paths without array destructuring 70 | let ssa_path = format!("{dir_path}{}.ssa", filename); 71 | let asm_path = format!("{dir_path}{}.s", filename); 72 | let exe_path = format!("{dir_path}{}.exe", filename); 73 | 74 | // Write SSA file 75 | OpenOptions::new() 76 | .read(true) 77 | .write(true) 78 | .create(true) 79 | .truncate(true) 80 | .open(&ssa_path) 81 | .map_err(|e| format!("Failed to open SSA file: {}", e))? 82 | .write_all(&buf) 83 | .map_err(|e| format!("Failed to write SSA file: {}", e))?; 84 | 85 | // Compile and run 86 | run_command(Command::new("qbe").arg(&ssa_path).arg("-o").arg(&asm_path))?; 87 | run_command(Command::new("gcc").arg(&asm_path).arg("-o").arg(&exe_path))?; 88 | run_command(&mut Command::new(&exe_path)) 89 | } 90 | 91 | pub fn run(target: Target, in_file: PathBuf) -> Result<()> { 92 | let mut buf = Box::>::default(); 93 | build::build_to_buffer(&target, &in_file, &mut buf)?; 94 | 95 | match target { 96 | Target::JS => run_node(&buf), 97 | Target::Qbe => run_qbe(*buf, &in_file), 98 | _ => Err("Unsupported target".to_string()), 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/generator/c.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::*; 2 | use crate::generator::{Generator, GeneratorResult}; 3 | use std::collections::HashMap; 4 | use types::Type; 5 | 6 | pub struct CGenerator; 7 | 8 | impl Generator for CGenerator { 9 | fn generate(prog: Module) -> GeneratorResult { 10 | let mut code = String::new(); 11 | 12 | // Add standard C headers 13 | code += "#include \n"; 14 | code += "#include \n"; 15 | code += "#include \n"; 16 | code += "#include \n\n"; 17 | 18 | // Add builtin functions 19 | let raw_builtins = crate::Builtins::get("builtin.c") 20 | .expect("Could not locate builtin functions") 21 | .data; 22 | code += std::str::from_utf8(&raw_builtins).expect("Unable to interpret builtin functions"); 23 | 24 | // Generate struct definitions first 25 | let structs: String = prog 26 | .structs 27 | .clone() 28 | .into_iter() 29 | .map(generate_struct_definition) 30 | .collect(); 31 | 32 | code += &structs; 33 | 34 | // Generate function prototypes 35 | let prototypes: String = prog.func.iter().map(generate_function_prototype).collect(); 36 | 37 | code += &prototypes; 38 | code += "\n"; 39 | 40 | // Generate function implementations 41 | let funcs: String = prog.func.into_iter().map(generate_function).collect(); 42 | 43 | code += &funcs; 44 | 45 | Ok(code) 46 | } 47 | } 48 | 49 | pub(super) fn generate_arguments(args: Vec) -> String { 50 | if args.is_empty() { 51 | return "void".to_string(); 52 | } 53 | 54 | args.into_iter() 55 | .map(|var| format!("{} {}", type_to_c_type(&var.ty), var.name)) 56 | .collect::>() 57 | .join(", ") 58 | } 59 | 60 | fn type_to_c_type(ty: &Option) -> String { 61 | match ty { 62 | Some(Type::Int) => "int".to_string(), 63 | Some(Type::Bool) => "bool".to_string(), 64 | Some(Type::Str) => "char*".to_string(), 65 | Some(Type::Array(inner, _)) => format!("{}*", type_to_c_type(&Some(*inner.clone()))), 66 | Some(Type::Struct(name)) => name.clone(), 67 | Some(Type::Any) => "void*".to_string(), 68 | None => "void".to_string(), 69 | } 70 | } 71 | 72 | pub(super) fn generate_function_prototype(func: &Function) -> String { 73 | let return_type = match &func.ret_type { 74 | Some(ty) => type_to_c_type(&Some(ty.clone())), 75 | None => "void".to_string(), 76 | }; 77 | 78 | format!( 79 | "{} {}({});\n", 80 | return_type, 81 | func.name, 82 | generate_arguments(func.arguments.clone()) 83 | ) 84 | } 85 | 86 | pub(super) fn generate_function(func: Function) -> String { 87 | let return_type = match &func.ret_type { 88 | Some(ty) => type_to_c_type(&Some(ty.clone())), 89 | None => "void".to_string(), 90 | }; 91 | 92 | let arguments = generate_arguments(func.arguments); 93 | let mut raw = format!("{} {}({}) ", return_type, func.name, arguments); 94 | 95 | raw += &generate_block(func.body, None); 96 | raw += "\n"; 97 | raw 98 | } 99 | 100 | pub(super) fn generate_struct_definition(struct_def: StructDef) -> String { 101 | let mut buf = format!("typedef struct {} {{\n", &struct_def.name); 102 | 103 | // Generate struct fields 104 | for field in &struct_def.fields { 105 | buf += &format!(" {} {};\n", type_to_c_type(&field.ty), field.name); 106 | } 107 | buf += &format!("}} {};\n\n", &struct_def.name); 108 | 109 | // Generate method prototypes 110 | for method in &struct_def.methods { 111 | let mut method_copy = method.clone(); 112 | // Add self parameter as first argument 113 | let self_var = Variable { 114 | name: "self".to_string(), 115 | ty: Some(Type::Struct(struct_def.name.clone())), 116 | }; 117 | method_copy.arguments.insert(0, self_var); 118 | buf += &generate_function_prototype(&method_copy); 119 | } 120 | 121 | // Generate method implementations 122 | for method in &struct_def.methods { 123 | let mut method_copy = method.clone(); 124 | // Add self parameter as first argument 125 | let self_var = Variable { 126 | name: "self".to_string(), 127 | ty: Some(Type::Struct(struct_def.name.clone())), 128 | }; 129 | method_copy.arguments.insert(0, self_var); 130 | buf += &generate_function(method_copy); 131 | } 132 | 133 | buf 134 | } 135 | 136 | pub(super) fn generate_block(block: Statement, prepend: Option) -> String { 137 | let mut generated = String::from("{\n"); 138 | 139 | if let Some(pre) = prepend { 140 | generated += ⪯ 141 | } 142 | 143 | let statements = match block { 144 | Statement::Block { 145 | statements, 146 | scope: _, 147 | } => statements, 148 | _ => panic!("Block body should be of type Statement::Block"), 149 | }; 150 | 151 | for statement in statements { 152 | generated += &generate_statement(statement); 153 | } 154 | 155 | generated += "}\n"; 156 | generated 157 | } 158 | 159 | pub(super) fn generate_statement(statement: Statement) -> String { 160 | let state = match statement { 161 | Statement::Return(ret) => generate_return(ret), 162 | Statement::Declare { variable, value } => generate_declare(variable, value), 163 | Statement::Exp(val) => generate_expression(val), 164 | Statement::If { 165 | condition, 166 | body, 167 | else_branch, 168 | } => generate_conditional(condition, *body, else_branch.map(|x| *x)), 169 | Statement::Assign { lhs, rhs } => generate_assign(*lhs, *rhs), 170 | Statement::Block { 171 | statements: _, 172 | scope: _, 173 | } => return generate_block(statement, None), 174 | Statement::While { condition, body } => generate_while_loop(condition, *body), 175 | Statement::For { ident, expr, body } => generate_for_loop(ident, expr, *body), 176 | Statement::Continue => "continue".to_string(), 177 | Statement::Break => "break".to_string(), 178 | Statement::Match { subject, arms } => generate_match(subject, arms), 179 | }; 180 | 181 | format!(" {};\n", state) 182 | } 183 | 184 | pub(super) fn generate_expression(expr: Expression) -> String { 185 | match expr { 186 | Expression::Int(val) => val.to_string(), 187 | Expression::Selff => "self".to_string(), 188 | Expression::Str(val) => format!("\"{}\"", val.replace("\"", "\\\"")), 189 | Expression::Variable(val) => val, 190 | Expression::Bool(b) => if b { "true" } else { "false" }.to_string(), 191 | Expression::FunctionCall { fn_name, args } => generate_function_call(fn_name, args), 192 | Expression::Array { 193 | capacity: _, 194 | elements, 195 | } => generate_array(elements), 196 | Expression::ArrayAccess { name, index } => generate_array_access(name, *index), 197 | Expression::BinOp { lhs, op, rhs } => generate_bin_op(*lhs, op, *rhs), 198 | Expression::StructInitialization { name, fields } => { 199 | generate_struct_initialization(name, fields) 200 | } 201 | Expression::FieldAccess { expr, field } => generate_field_access(*expr, *field), 202 | } 203 | } 204 | 205 | pub(super) fn generate_while_loop(expr: Expression, body: Statement) -> String { 206 | format!( 207 | "while ({}) {}", 208 | generate_expression(expr), 209 | generate_block(body, None) 210 | ) 211 | } 212 | 213 | pub(super) fn generate_for_loop(ident: Variable, expr: Expression, body: Statement) -> String { 214 | // C-style for loop with array indexing 215 | let mut out_str = format!( 216 | "for(int i = 0; i < sizeof({}) / sizeof({}[0]); i++)", 217 | generate_expression(expr.clone()), 218 | generate_expression(expr.clone()) 219 | ); 220 | 221 | // Add the loop variable declaration to the prepended block 222 | out_str += &generate_block( 223 | body, 224 | Some(format!( 225 | " {} {} = {}[i];\n", 226 | type_to_c_type(&ident.ty), 227 | ident.name, 228 | generate_expression(expr) 229 | )), 230 | ); 231 | out_str 232 | } 233 | 234 | pub(super) fn generate_match(subject: Expression, arms: Vec) -> String { 235 | let mut out_str = format!("switch ({}) {{\n", generate_expression(subject)); 236 | 237 | for arm in arms { 238 | match arm { 239 | MatchArm::Case(expr, statement) => { 240 | out_str += &format!(" case {}:\n", generate_expression(expr)); 241 | out_str += &generate_statement(statement); 242 | out_str += " break;\n"; 243 | } 244 | MatchArm::Else(statement) => { 245 | out_str += " default:\n"; 246 | out_str += &generate_statement(statement); 247 | } 248 | } 249 | } 250 | 251 | out_str += " }\n"; 252 | out_str 253 | } 254 | 255 | pub(super) fn generate_array(elements: Vec) -> String { 256 | let mut out_str = String::from("{"); 257 | 258 | out_str += &elements 259 | .iter() 260 | .map(|el| generate_expression(el.clone())) 261 | .collect::>() 262 | .join(", "); 263 | 264 | out_str += "}"; 265 | out_str 266 | } 267 | 268 | pub(super) fn generate_array_access(name: String, expr: Expression) -> String { 269 | format!("{}[{}]", name, generate_expression(expr)) 270 | } 271 | 272 | pub(super) fn generate_conditional( 273 | expr: Expression, 274 | if_state: Statement, 275 | else_state: Option, 276 | ) -> String { 277 | let mut outcome = format!("if ({}) ", generate_expression(expr)); 278 | outcome += &generate_block(if_state, None); 279 | 280 | if let Some(else_state) = else_state { 281 | outcome += "else "; 282 | outcome += &generate_block(else_state, None); 283 | } 284 | 285 | outcome 286 | } 287 | 288 | pub(super) fn generate_declare>( 289 | identifier: V, 290 | val: Option, 291 | ) -> String { 292 | let ident = identifier.as_ref(); 293 | let type_str = type_to_c_type(&ident.ty); 294 | 295 | match val { 296 | Some(expr) => format!( 297 | "{} {} = {}", 298 | type_str, 299 | ident.name, 300 | generate_expression(expr) 301 | ), 302 | None => match &ident.ty { 303 | Some(Type::Array(_, _)) => { 304 | format!("{} {}[]", type_str, ident.name) 305 | } 306 | _ => format!("{} {}", type_str, ident.name), 307 | }, 308 | } 309 | } 310 | 311 | pub(super) fn generate_function_call(func: String, args: Vec) -> String { 312 | let formatted_args = args 313 | .into_iter() 314 | .map(generate_expression) 315 | .collect::>() 316 | .join(", "); 317 | 318 | format!("{}({})", func, formatted_args) 319 | } 320 | 321 | pub(super) fn generate_return(ret: Option) -> String { 322 | match ret { 323 | Some(expr) => format!("return {}", generate_expression(expr)), 324 | None => "return".to_string(), 325 | } 326 | } 327 | 328 | pub(super) fn generate_bin_op(left: Expression, op: BinOp, right: Expression) -> String { 329 | let op_str = match op { 330 | BinOp::Addition => "+", 331 | BinOp::And => "&&", 332 | BinOp::Division => "/", 333 | BinOp::Equal => "==", 334 | BinOp::GreaterThan => ">", 335 | BinOp::GreaterThanOrEqual => ">=", 336 | BinOp::LessThan => "<", 337 | BinOp::LessThanOrEqual => "<=", 338 | BinOp::Modulus => "%", 339 | BinOp::Multiplication => "*", 340 | BinOp::NotEqual => "!=", 341 | BinOp::Or => "||", 342 | BinOp::Subtraction => "-", 343 | BinOp::AddAssign => "+=", 344 | BinOp::SubtractAssign => "-=", 345 | BinOp::MultiplyAssign => "*=", 346 | BinOp::DivideAssign => "/=", 347 | }; 348 | 349 | format!( 350 | "{} {} {}", 351 | generate_expression(left), 352 | op_str, 353 | generate_expression(right) 354 | ) 355 | } 356 | 357 | pub(super) fn generate_struct_initialization( 358 | name: String, 359 | fields: HashMap>, 360 | ) -> String { 361 | let mut out_str = format!("({}) {{", name); 362 | 363 | let field_inits: Vec = fields 364 | .into_iter() 365 | .map(|(key, value)| format!(".{} = {}", key, generate_expression(*value))) 366 | .collect(); 367 | 368 | out_str += &field_inits.join(", "); 369 | out_str += "}"; 370 | 371 | out_str 372 | } 373 | 374 | pub(super) fn generate_field_access(expr: Expression, field: Expression) -> String { 375 | // In C, we use -> for pointer access and . for direct access 376 | // For simplicity, we'll use . here, but in a real implementation 377 | // you'd need to check if expr is a pointer 378 | format!( 379 | "{}.{}", 380 | generate_expression(expr), 381 | generate_expression(field) 382 | ) 383 | } 384 | 385 | pub(super) fn generate_assign(name: Expression, expr: Expression) -> String { 386 | format!( 387 | "{} = {}", 388 | generate_expression(name), 389 | generate_expression(expr) 390 | ) 391 | } 392 | -------------------------------------------------------------------------------- /src/generator/js.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::ast::*; 17 | use crate::generator::{Generator, GeneratorResult}; 18 | use std::collections::HashMap; 19 | use types::Type; 20 | 21 | pub struct JsGenerator; 22 | 23 | impl Generator for JsGenerator { 24 | fn generate(prog: Module) -> GeneratorResult { 25 | let mut code = String::new(); 26 | 27 | let raw_builtins = crate::Builtins::get("builtin.js") 28 | .expect("Could not locate builtin functions") 29 | .data; 30 | code += std::str::from_utf8(&raw_builtins).expect("Unable to interpret builtin functions"); 31 | 32 | let structs: String = prog 33 | .structs 34 | .clone() 35 | .into_iter() 36 | .map(generate_struct_definition) 37 | .collect(); 38 | 39 | code += &structs; 40 | 41 | let funcs: String = prog.func.into_iter().map(generate_function).collect(); 42 | 43 | code += &funcs; 44 | 45 | code += "main();"; 46 | 47 | Ok(code) 48 | } 49 | } 50 | 51 | fn generate_arguments(args: Vec) -> String { 52 | args.into_iter() 53 | .map(|var| var.name) 54 | .collect::>() 55 | .join(", ") 56 | } 57 | 58 | fn generate_function(func: Function) -> String { 59 | let arguments: String = generate_arguments(func.arguments); 60 | 61 | let mut raw = format!("function {N}({A})", N = func.name, A = arguments); 62 | 63 | raw += &generate_block(func.body, None); 64 | raw += "\n"; 65 | raw 66 | } 67 | 68 | fn generate_method(subject: String, func: Function) -> String { 69 | let mut buf = format!( 70 | "{}.prototype.{} = function({})", 71 | subject, 72 | func.name, 73 | generate_arguments(func.arguments) 74 | ); 75 | 76 | buf += &generate_block(func.body, None); 77 | buf += "\n"; 78 | 79 | buf 80 | } 81 | 82 | fn generate_struct_definition(struct_def: StructDef) -> String { 83 | // JS doesn't care about field declaration 84 | 85 | // Constructor signature 86 | let mut buf = format!("function {}(args) {{\n", &struct_def.name); 87 | 88 | // Field constructor fields 89 | for field in &struct_def.fields { 90 | buf += &format!("this.{N} = args.{N};\n", N = field.name); 91 | } 92 | // Constructor end 93 | buf += "}\n"; 94 | 95 | // Methods 96 | for method in &struct_def.methods { 97 | buf += &generate_method(struct_def.name.clone(), method.to_owned()); 98 | } 99 | 100 | buf 101 | } 102 | 103 | /// prepend is used to pass optional statements, that will be put in front of the regular block 104 | /// Currently used in for statements, to declare local variables 105 | fn generate_block(block: Statement, prepend: Option) -> String { 106 | let mut generated = String::from("{\n"); 107 | 108 | if let Some(pre) = prepend { 109 | generated += ⪯ 110 | } 111 | 112 | // TODO: Prepend statements 113 | let statements = match block { 114 | Statement::Block { 115 | statements, 116 | scope: _, 117 | } => statements, 118 | _ => panic!("Block body should be of type Statement::Block"), 119 | }; 120 | 121 | for statement in statements { 122 | generated += &generate_statement(statement); 123 | } 124 | 125 | generated += "}\n"; 126 | 127 | generated 128 | } 129 | 130 | fn generate_statement(statement: Statement) -> String { 131 | let state = match statement { 132 | Statement::Return(ret) => generate_return(ret), 133 | Statement::Declare { variable, value } => generate_declare(variable, value), 134 | Statement::Exp(val) => generate_expression(val), 135 | Statement::If { 136 | condition, 137 | body, 138 | else_branch, 139 | } => generate_conditional(condition, *body, else_branch.map(|x| *x)), 140 | Statement::Assign { lhs, rhs } => generate_assign(*lhs, *rhs), 141 | Statement::Block { 142 | statements: _, 143 | scope: _, 144 | } => generate_block(statement, None), 145 | Statement::While { condition, body } => generate_while_loop(condition, *body), 146 | Statement::For { ident, expr, body } => generate_for_loop(ident, expr, *body), 147 | Statement::Continue => generate_continue(), 148 | Statement::Break => generate_break(), 149 | Statement::Match { subject, arms } => generate_match(subject, arms), 150 | }; 151 | 152 | format!("{};\n", state) 153 | } 154 | 155 | fn generate_expression(expr: Expression) -> String { 156 | match expr { 157 | Expression::Int(val) => val.to_string(), 158 | Expression::Selff => "this".to_string(), 159 | Expression::Str(val) => super::string_syntax(val), 160 | Expression::Variable(val) => val, 161 | Expression::Bool(b) => b.to_string(), 162 | Expression::FunctionCall { fn_name, args } => generate_function_call(fn_name, args), 163 | Expression::Array { 164 | capacity: _, 165 | elements, 166 | } => generate_array(elements), 167 | Expression::ArrayAccess { name, index } => generate_array_access(name, *index), 168 | Expression::BinOp { lhs, op, rhs } => generate_bin_op(*lhs, op, *rhs), 169 | Expression::StructInitialization { name, fields } => { 170 | generate_struct_initialization(name, fields) 171 | } 172 | Expression::FieldAccess { expr, field } => generate_field_access(*expr, *field), 173 | } 174 | } 175 | 176 | fn generate_while_loop(expr: Expression, body: Statement) -> String { 177 | let mut out_str = String::from("while ("); 178 | 179 | out_str += &generate_expression(expr); 180 | out_str += ") "; 181 | out_str += &generate_block(body, None); 182 | out_str 183 | } 184 | 185 | fn generate_for_loop(ident: Variable, expr: Expression, body: Statement) -> String { 186 | // Assign expression to variable to access it from within the loop 187 | let mut expr_ident = ident.clone(); 188 | expr_ident.name = format!("loop_orig_{}", ident.name); 189 | let mut out_str = format!("{};\n", generate_declare(&expr_ident, Some(expr))); 190 | 191 | // Loop signature 192 | out_str += &format!( 193 | "for (let iter_{I} = 0; iter_{I} < {E}.length; iter_{I}++)", 194 | I = ident.name, 195 | E = expr_ident.name 196 | ); 197 | 198 | // Block with prepended declaration of the actual variable 199 | out_str += &generate_block( 200 | body, 201 | Some(format!( 202 | "let {I} = {E}[iter_{I}];\n", 203 | I = ident.name, 204 | E = expr_ident.name, 205 | )), 206 | ); 207 | out_str 208 | } 209 | 210 | fn generate_break() -> String { 211 | "break;\n".into() 212 | } 213 | 214 | fn generate_continue() -> String { 215 | "continue;\n".into() 216 | } 217 | 218 | fn generate_match(subject: Expression, arms: Vec) -> String { 219 | let mut out_str = format!("switch ({E}) {{\n", E = generate_expression(subject)); 220 | for arm in arms { 221 | match arm { 222 | MatchArm::Case(expr, statement) => { 223 | out_str += &format!("case {}:\n", generate_expression(expr)); 224 | out_str += &format!("{}\n", &generate_statement(statement)); 225 | out_str += "break;"; 226 | } 227 | MatchArm::Else(statement) => { 228 | out_str += "default:\n"; 229 | out_str += &format!("{}\n", &generate_statement(statement)); 230 | } 231 | } 232 | } 233 | 234 | out_str += "}"; 235 | 236 | out_str 237 | } 238 | 239 | fn generate_array(elements: Vec) -> String { 240 | let mut out_str = String::from("["); 241 | 242 | out_str += &elements 243 | .iter() 244 | .map(|el| generate_expression(el.clone())) 245 | .collect::>() 246 | .join(", "); 247 | 248 | out_str += "]"; 249 | out_str 250 | } 251 | 252 | fn generate_array_access(name: String, expr: Expression) -> String { 253 | format!("{n}[{e}]", n = name, e = generate_expression(expr)) 254 | } 255 | 256 | fn generate_conditional( 257 | expr: Expression, 258 | if_state: Statement, 259 | else_state: Option, 260 | ) -> String { 261 | let expr_str = generate_expression(expr); 262 | 263 | let body = match if_state { 264 | Statement::Block { 265 | statements, 266 | scope: _, 267 | } => statements, 268 | _ => panic!("Conditional body should be of type block"), 269 | }; 270 | 271 | let mut outcome = format!("if ({})", expr_str); 272 | 273 | outcome += "{\n"; 274 | for statement in body { 275 | outcome += &generate_statement(statement); 276 | } 277 | outcome += "}"; 278 | 279 | if let Some(else_state) = else_state { 280 | outcome += "else "; 281 | outcome += &generate_statement(else_state); 282 | } 283 | outcome 284 | } 285 | 286 | fn generate_declare>(identifier: V, val: Option) -> String { 287 | // AsRef prevents unnecessary cloning here 288 | let ident = identifier.as_ref(); 289 | // var is used here to not collide with scopes. 290 | // TODO: Can let be used instead? 291 | match val { 292 | Some(expr) => format!("var {} = {}", ident.name, generate_expression(expr)), 293 | None => match ident.ty { 294 | // Accessing an array that has not been initialized will throw an error, 295 | // So we have to initialize it as an empty array. 296 | // 297 | // This crashes: 298 | // var x; 299 | // x[0] = 1; 300 | // 301 | // But this works: 302 | // var x = []; 303 | // x[0] = 1; 304 | Some(Type::Array(_, _)) => format!("var {} = []", ident.name), 305 | _ => format!("var {}", ident.name), 306 | }, 307 | } 308 | } 309 | 310 | fn generate_function_call(func: String, args: Vec) -> String { 311 | let formatted_args = args 312 | .into_iter() 313 | .map(|arg| match arg { 314 | Expression::Int(i) => i.to_string(), 315 | Expression::Bool(v) => v.to_string(), 316 | Expression::Selff => "this".to_string(), 317 | Expression::ArrayAccess { name, index } => generate_array_access(name, *index), 318 | Expression::FunctionCall { fn_name, args } => generate_function_call(fn_name, args), 319 | Expression::Str(s) => super::string_syntax(s), 320 | Expression::Variable(s) => s, 321 | Expression::Array { 322 | capacity: _, 323 | elements, 324 | } => generate_array(elements), 325 | Expression::BinOp { lhs, op, rhs } => generate_bin_op(*lhs, op, *rhs), 326 | Expression::StructInitialization { name, fields } => { 327 | generate_struct_initialization(name, fields) 328 | } 329 | Expression::FieldAccess { expr, field } => generate_field_access(*expr, *field), 330 | }) 331 | .collect::>() 332 | .join(","); 333 | format!("{N}({A})", N = func, A = formatted_args) 334 | } 335 | 336 | fn generate_return(ret: Option) -> String { 337 | match ret { 338 | Some(expr) => format!("return {}", generate_expression(expr)), 339 | None => "return".to_string(), 340 | } 341 | } 342 | 343 | fn generate_bin_op(left: Expression, op: BinOp, right: Expression) -> String { 344 | let op_str = match op { 345 | BinOp::Addition => "+", 346 | BinOp::And => "&&", 347 | BinOp::Division => "/", 348 | BinOp::Equal => "===", 349 | BinOp::GreaterThan => ">", 350 | BinOp::GreaterThanOrEqual => ">=", 351 | BinOp::LessThan => "<", 352 | BinOp::LessThanOrEqual => "<=", 353 | BinOp::Modulus => "%", 354 | BinOp::Multiplication => "*", 355 | BinOp::NotEqual => "!==", 356 | BinOp::Or => "||", 357 | BinOp::Subtraction => "-", 358 | BinOp::AddAssign => "+=", 359 | BinOp::SubtractAssign => "-=", 360 | BinOp::MultiplyAssign => "*=", 361 | BinOp::DivideAssign => "/=", 362 | }; 363 | format!( 364 | "{l} {op} {r}", 365 | l = generate_expression(left), 366 | op = op_str, 367 | r = generate_expression(right) 368 | ) 369 | } 370 | 371 | fn generate_struct_initialization( 372 | name: String, 373 | fields: HashMap>, 374 | ) -> String { 375 | let mut out_str = format!("new {}({{", name); 376 | for (key, value) in fields { 377 | out_str += &format!("{}: {},", key, generate_expression(*value)); 378 | } 379 | 380 | out_str += "})"; 381 | 382 | out_str 383 | } 384 | 385 | fn generate_field_access(expr: Expression, field: Expression) -> String { 386 | format!( 387 | "{}.{}", 388 | generate_expression(expr), 389 | generate_expression(field) 390 | ) 391 | } 392 | 393 | fn generate_assign(name: Expression, expr: Expression) -> String { 394 | format!( 395 | "{} = {}", 396 | generate_expression(name), 397 | generate_expression(expr) 398 | ) 399 | } 400 | -------------------------------------------------------------------------------- /src/generator/llvm.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::types::Type; 2 | use crate::ast::*; 3 | /** 4 | * Copyright 2020 Garrit Franke 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | use crate::generator::Generator; 19 | use inkwell::context::Context; 20 | use inkwell::module; 21 | use inkwell::types::*; 22 | 23 | pub struct LLVMGenerator<'ctx> { 24 | ctx: &'ctx Context, 25 | module: module::Module<'ctx>, 26 | } 27 | 28 | impl<'ctx> Generator for LLVMGenerator<'ctx> { 29 | fn generate(prog: Module) -> String { 30 | let ctx = Context::create(); 31 | let module = ctx.create_module("main"); 32 | let mut generator = LLVMGenerator { ctx: &ctx, module }; 33 | for func in prog.func { 34 | generator.generate_function(func); 35 | } 36 | generator.module.print_to_string().to_string() 37 | } 38 | } 39 | 40 | impl<'ctx> LLVMGenerator<'ctx> { 41 | fn convert_to_llvm_args(&mut self, args: Vec) -> Vec> { 42 | let arg_types: Vec = args 43 | .iter() 44 | .map(|arg| match arg.ty { 45 | Some(Type::Int) => self.ctx.i32_type().as_basic_type_enum(), 46 | Some(Type::Bool) => self.ctx.bool_type().as_basic_type_enum(), 47 | Some(Type::Any) => todo!(), 48 | Some(Type::Str) => todo!(), 49 | Some(Type::Array(_)) => todo!(), 50 | Some(Type::Struct(_)) => todo!(), 51 | None => panic!("Function argument has no type"), 52 | }) 53 | .collect(); 54 | arg_types 55 | } 56 | 57 | fn generate_function(&mut self, func: Function) { 58 | let arg_types: Vec = self.convert_to_llvm_args(func.arguments); 59 | 60 | let func_type = match func.ret_type { 61 | Some(Type::Int) => self.ctx.i32_type().fn_type(&arg_types, false), 62 | Some(Type::Bool) => self.ctx.bool_type().fn_type(&arg_types, false), 63 | None => self.ctx.void_type().fn_type(&arg_types, false), 64 | _ => todo!(), 65 | }; 66 | let function = self.module.add_function(&func.name, func_type, None); 67 | let _basic_block = self.ctx.append_basic_block(function, "entry"); 68 | self.generate_statement(func.body); 69 | } 70 | 71 | fn generate_statement(&mut self, statement: Statement) { 72 | match statement { 73 | Statement::Block(statements, _) => { 74 | for s in statements { 75 | self.generate_statement(s); 76 | } 77 | } 78 | Statement::Exp(expression) => self.generate_expression(expression), 79 | _ => todo!(), 80 | }; 81 | } 82 | 83 | fn generate_expression(&mut self, _expr: Expression) { 84 | todo!() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/generator/mod.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::ast::*; 17 | use std::path; 18 | use std::str::FromStr; 19 | 20 | pub mod c; 21 | pub mod js; 22 | #[cfg(feature = "llvm")] 23 | pub mod llvm; 24 | pub mod qbe; 25 | #[cfg(test)] 26 | mod tests; 27 | pub mod x86; 28 | 29 | #[derive(Debug)] 30 | pub enum Target { 31 | C, 32 | JS, 33 | Llvm, 34 | Qbe, 35 | X86, 36 | } 37 | 38 | impl Target { 39 | /// Constructs target based on provided output filename, returns 40 | /// None if target can't be detected 41 | pub fn from_extension(file: &path::Path) -> Option { 42 | let ext = file.extension()?; 43 | 44 | match &*ext.to_string_lossy() { 45 | "c" => Some(Self::C), 46 | "js" => Some(Self::JS), 47 | "ssa" => Some(Self::Qbe), 48 | "s" => Some(Self::X86), 49 | _ => None, 50 | } 51 | } 52 | } 53 | 54 | impl FromStr for Target { 55 | type Err = String; 56 | 57 | fn from_str(s: &str) -> Result { 58 | let s = s.to_lowercase(); 59 | 60 | match s.as_str() { 61 | "c" => Ok(Target::C), 62 | "js" => Ok(Target::JS), 63 | "llvm" => Ok(Target::Llvm), 64 | "qbe" => Ok(Target::Qbe), 65 | "x86" => Ok(Target::X86), 66 | _ => Err(format!("no target {} found", s)), 67 | } 68 | } 69 | } 70 | 71 | pub type GeneratorResult = Result; 72 | 73 | pub trait Generator { 74 | fn generate(prog: Module) -> GeneratorResult; 75 | } 76 | 77 | /// Returns C syntax representation of a raw string 78 | pub fn string_syntax(raw: String) -> String { 79 | format!( 80 | "\"{}\"", 81 | raw.chars() 82 | .map(|c| match c { 83 | '\n' => "\\n".to_string(), 84 | '\r' => "\\r".to_string(), 85 | '\t' => "\\t".to_string(), 86 | '\u{000C}' => "\\f".to_string(), 87 | '\u{0008}' => "\\b".to_string(), 88 | '\\' => "\\\\".to_string(), 89 | '"' => "\"".to_string(), 90 | other => other.to_string(), 91 | }) 92 | .collect::(), 93 | ) 94 | } 95 | -------------------------------------------------------------------------------- /src/generator/tests/c_tests.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::types::Type; 2 | use crate::ast::BinOp::*; 3 | use crate::ast::Expression::*; 4 | use crate::ast::Statement::*; 5 | use crate::ast::{Function, MatchArm, StructDef, Variable}; 6 | use crate::generator::c::*; 7 | use std::collections::HashMap; 8 | 9 | #[test] 10 | fn test_generate_block_empty() { 11 | let t = generate_block( 12 | Block { 13 | statements: vec![], 14 | scope: vec![], 15 | }, 16 | None, 17 | ); 18 | assert_eq!(t, "{\n}\n") 19 | } 20 | 21 | #[test] 22 | fn test_generate_block_with_statement() { 23 | let t = generate_block( 24 | Block { 25 | statements: vec![Return(Some(Int(42)))], 26 | scope: vec![], 27 | }, 28 | None, 29 | ); 30 | assert_eq!(t, "{\n return 42;\n}\n") 31 | } 32 | 33 | #[test] 34 | fn test_generate_function() { 35 | let func = Function { 36 | name: "test_func".to_string(), 37 | arguments: vec![], 38 | ret_type: Some(Type::Int), 39 | body: Block { 40 | statements: vec![Return(Some(Int(0)))], 41 | scope: vec![], 42 | }, 43 | }; 44 | let result = generate_function(func); 45 | assert_eq!(result, "int test_func(void) {\n return 0;\n}\n\n") 46 | } 47 | 48 | #[test] 49 | fn test_generate_arguments_empty() { 50 | let args = vec![]; 51 | assert_eq!(generate_arguments(args), "void") 52 | } 53 | 54 | #[test] 55 | fn test_generate_arguments_with_params() { 56 | let args = vec![ 57 | Variable { 58 | name: "x".to_string(), 59 | ty: Some(Type::Int), 60 | }, 61 | Variable { 62 | name: "y".to_string(), 63 | ty: Some(Type::Bool), 64 | }, 65 | ]; 66 | assert_eq!(generate_arguments(args), "int x, bool y") 67 | } 68 | 69 | #[test] 70 | fn test_generate_expression_int() { 71 | assert_eq!(generate_expression(Int(42)), "42") 72 | } 73 | 74 | #[test] 75 | fn test_generate_expression_string() { 76 | assert_eq!(generate_expression(Str("hello".to_string())), "\"hello\"") 77 | } 78 | 79 | #[test] 80 | fn test_generate_expression_bool() { 81 | assert_eq!(generate_expression(Bool(true)), "true") 82 | } 83 | 84 | #[test] 85 | fn test_generate_binary_operation() { 86 | let expr = BinOp { 87 | lhs: Box::new(Int(1)), 88 | op: Addition, 89 | rhs: Box::new(Int(2)), 90 | }; 91 | assert_eq!(generate_expression(expr), "1 + 2") 92 | } 93 | 94 | #[test] 95 | fn test_generate_function_call() { 96 | let call = FunctionCall { 97 | fn_name: "test".to_string(), 98 | args: vec![Int(1), Int(2)], 99 | }; 100 | assert_eq!(generate_expression(call), "test(1, 2)") 101 | } 102 | 103 | #[test] 104 | fn test_generate_array() { 105 | let arr = Array { 106 | capacity: 3, 107 | elements: vec![Int(1), Int(2), Int(3)], 108 | }; 109 | assert_eq!(generate_expression(arr), "{1, 2, 3}") 110 | } 111 | 112 | #[test] 113 | fn test_generate_array_access() { 114 | let access = ArrayAccess { 115 | name: "arr".to_string(), 116 | index: Box::new(Int(0)), 117 | }; 118 | assert_eq!(generate_expression(access), "arr[0]") 119 | } 120 | 121 | #[test] 122 | fn test_generate_struct_definition() { 123 | let struct_def = StructDef { 124 | name: "TestStruct".to_string(), 125 | fields: vec![Variable { 126 | name: "field1".to_string(), 127 | ty: Some(Type::Int), 128 | }], 129 | methods: vec![], 130 | }; 131 | let result = generate_struct_definition(struct_def); 132 | assert_eq!( 133 | result, 134 | "typedef struct TestStruct {\n int field1;\n} TestStruct;\n\n" 135 | ) 136 | } 137 | 138 | #[test] 139 | fn test_generate_conditional() { 140 | let if_stmt = If { 141 | condition: Bool(true), 142 | body: Box::new(Block { 143 | statements: vec![Return(Some(Int(1)))], 144 | scope: vec![], 145 | }), 146 | else_branch: None, 147 | }; 148 | let result = generate_statement(if_stmt); 149 | assert_eq!(result, " if (true) {\n return 1;\n}\n;\n") 150 | } 151 | 152 | #[test] 153 | fn test_generate_while_loop() { 154 | let while_stmt = While { 155 | condition: Bool(true), 156 | body: Box::new(Block { 157 | statements: vec![Break], 158 | scope: vec![], 159 | }), 160 | }; 161 | let result = generate_statement(while_stmt); 162 | assert_eq!(result, " while (true) {\n break;\n}\n;\n") 163 | } 164 | 165 | #[test] 166 | fn test_generate_match() { 167 | let match_stmt = Match { 168 | subject: Int(1), 169 | arms: vec![ 170 | MatchArm::Case(Int(1), Return(Some(Int(1)))), 171 | MatchArm::Else(Return(Some(Int(0)))), 172 | ], 173 | }; 174 | let result = generate_statement(match_stmt); 175 | assert!(result.contains("switch (1)")); 176 | assert!(result.contains("case 1:")); 177 | assert!(result.contains("default:")); 178 | } 179 | 180 | #[test] 181 | fn test_generate_struct_initialization() { 182 | let mut fields = HashMap::new(); 183 | fields.insert("x".to_string(), Box::new(Int(1))); 184 | let init = StructInitialization { 185 | name: "Point".to_string(), 186 | fields, 187 | }; 188 | assert_eq!(generate_expression(init), "(Point) {.x = 1}") 189 | } 190 | 191 | #[test] 192 | fn test_generate_field_access() { 193 | let access = FieldAccess { 194 | expr: Box::new(Variable("point".to_string())), 195 | field: Box::new(Variable("x".to_string())), 196 | }; 197 | assert_eq!(generate_expression(access), "point.x") 198 | } 199 | 200 | #[test] 201 | fn test_generate_for_loop() { 202 | let for_stmt = For { 203 | ident: Variable { 204 | name: "i".to_string(), 205 | ty: Some(Type::Int), 206 | }, 207 | expr: Array { 208 | capacity: 2, 209 | elements: vec![Int(1), Int(2)], 210 | }, 211 | body: Box::new(Block { 212 | statements: vec![], 213 | scope: vec![], 214 | }), 215 | }; 216 | let result = generate_statement(for_stmt); 217 | assert!(result.contains("for(int i = 0;")); 218 | } 219 | 220 | #[test] 221 | fn test_generate_declare() { 222 | let var = Variable { 223 | name: "x".to_string(), 224 | ty: Some(Type::Int), 225 | }; 226 | let decl = generate_declare(var, Some(Int(42))); 227 | assert_eq!(decl, "int x = 42") 228 | } 229 | 230 | #[test] 231 | fn test_generate_return() { 232 | assert_eq!(generate_return(Some(Int(42))), "return 42"); 233 | assert_eq!(generate_return(None), "return"); 234 | } 235 | -------------------------------------------------------------------------------- /src/generator/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod c_tests; 2 | mod qbe_tests; 3 | -------------------------------------------------------------------------------- /src/generator/x86.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::ast::{Function, Module, Statement}; 17 | use crate::generator::{Generator, GeneratorResult}; 18 | 19 | struct Assembly { 20 | asm: Vec, 21 | } 22 | 23 | impl From for String { 24 | fn from(asm: Assembly) -> Self { 25 | asm.build() 26 | } 27 | } 28 | 29 | impl Assembly { 30 | fn new() -> Assembly { 31 | Assembly { asm: vec![] } 32 | } 33 | 34 | fn add>(&mut self, string: S) { 35 | self.asm.push(string.into()) 36 | } 37 | 38 | fn build(&self) -> String { 39 | self.asm.join("\n") 40 | } 41 | } 42 | 43 | pub struct X86Generator; 44 | 45 | impl Generator for X86Generator { 46 | fn generate(prog: Module) -> GeneratorResult { 47 | Ok(Self::new().gen_program(prog).build()) 48 | } 49 | } 50 | 51 | impl X86Generator { 52 | fn new() -> Self { 53 | X86Generator {} 54 | } 55 | 56 | fn gen_program(&mut self, prog: Module) -> Assembly { 57 | let mut asm = Assembly::new(); 58 | let Module { 59 | func, 60 | globals, 61 | structs: _, 62 | imports: _, 63 | } = prog; 64 | 65 | asm.add(".intel_syntax noprefix"); 66 | asm.add(".text"); 67 | 68 | for f in func { 69 | asm.add(self.gen_function(f)); 70 | } 71 | asm.add(".data"); 72 | for g in globals { 73 | asm.add(format!("_{0}: .word 0", g)); 74 | } 75 | 76 | asm 77 | } 78 | 79 | fn gen_function(&mut self, func: Function) -> Assembly { 80 | let mut asm = Assembly::new(); 81 | 82 | let has_return: bool = match &func.body { 83 | Statement::Block { 84 | statements, 85 | scope: _, 86 | } => statements 87 | .iter() 88 | .any(|s| matches!(*s, Statement::Return(_))), 89 | _ => panic!("Function body should be of type Block"), 90 | }; 91 | 92 | asm.add(format!(".globl _{}", func.name)); 93 | asm.add(format!("_{}:", func.name)); 94 | asm.add("push rbp"); 95 | asm.add("mov rbp, rsp"); 96 | 97 | if !has_return { 98 | asm.add("mov rsp, rbp"); 99 | asm.add("pop rbp"); 100 | asm.add("ret\n"); 101 | } 102 | 103 | asm 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/lexer/cursor.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::lexer::Position; 17 | use std::str::Chars; 18 | 19 | /// Peekable iterator over a char sequence. 20 | /// 21 | /// Next characters can be peeked via `nth_char` method, 22 | /// and position can be shifted forward via `bump` method. 23 | pub(crate) struct Cursor<'a> { 24 | pos: &'a mut Position, 25 | len: usize, 26 | chars: Chars<'a>, 27 | #[cfg(debug_assertions)] 28 | prev: char, 29 | } 30 | 31 | pub(crate) const EOF_CHAR: char = '\0'; 32 | 33 | impl<'a> Cursor<'a> { 34 | pub(crate) fn new(input: &'a str, position: &'a mut Position) -> Cursor<'a> { 35 | Cursor { 36 | len: input.len(), 37 | chars: input.chars(), 38 | #[cfg(debug_assertions)] 39 | prev: EOF_CHAR, 40 | pos: position, 41 | } 42 | } 43 | 44 | /// For debug assertions only 45 | /// Returns the last eaten symbol (or '\0' in release builds). 46 | pub(crate) fn prev(&self) -> char { 47 | #[cfg(debug_assertions)] 48 | { 49 | self.prev 50 | } 51 | 52 | #[cfg(not(debug_assertions))] 53 | { 54 | '\0' 55 | } 56 | } 57 | 58 | /// Returns nth character relative to the current cursor position. 59 | /// If requested position doesn't exist, `EOF_CHAR` is returned. 60 | /// However, getting `EOF_CHAR` doesn't always mean actual end of file, 61 | /// it should be checked with `is_eof` method. 62 | fn nth_char(&self, n: usize) -> char { 63 | self.chars().nth(n).unwrap_or(EOF_CHAR) 64 | } 65 | 66 | /// Peeks the next symbol from the input stream without consuming it. 67 | pub(crate) fn first(&self) -> char { 68 | self.nth_char(0) 69 | } 70 | 71 | /// Checks if there is nothing more to consume. 72 | pub(crate) fn is_eof(&self) -> bool { 73 | self.chars.as_str().is_empty() 74 | } 75 | 76 | /// Returns amount of already consumed symbols. 77 | pub(crate) fn len_consumed(&self) -> usize { 78 | self.len - self.chars.as_str().len() 79 | } 80 | 81 | /// Returns a `Chars` iterator over the remaining characters. 82 | pub(crate) fn chars(&self) -> Chars<'a> { 83 | self.chars.clone() 84 | } 85 | 86 | pub(crate) fn pos(&self) -> Position { 87 | *self.pos 88 | } 89 | 90 | /// Moves to the next character. 91 | pub(crate) fn bump(&mut self) -> Option { 92 | let c = self.chars.next()?; 93 | // If first token, the position should be set to 0 94 | match self.pos.raw { 95 | usize::MAX => self.pos.raw = 0, 96 | _ => { 97 | self.pos.raw += 1; 98 | self.pos.offset += 1; 99 | } 100 | } 101 | 102 | if c == '\n' { 103 | self.pos.line += 1; 104 | self.pos.offset = 0; 105 | } 106 | 107 | #[cfg(debug_assertions)] 108 | { 109 | self.prev = c; 110 | } 111 | 112 | Some(c) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/lexer/display.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::{Keyword, TokenKind, Value}; 2 | 3 | impl std::fmt::Display for Keyword { 4 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 5 | match self { 6 | Keyword::Let => write!(f, "let"), 7 | Keyword::If => write!(f, "if"), 8 | Keyword::Else => write!(f, "else"), 9 | Keyword::Return => write!(f, "return"), 10 | Keyword::While => write!(f, "while"), 11 | Keyword::For => write!(f, "for"), 12 | Keyword::In => write!(f, "in"), 13 | Keyword::Break => write!(f, "break"), 14 | Keyword::Continue => write!(f, "continue"), 15 | Keyword::Function => write!(f, "fn"), 16 | Keyword::Boolean => write!(f, "boolean"), 17 | Keyword::Struct => write!(f, "struct"), 18 | Keyword::New => write!(f, "new"), 19 | Keyword::Match => write!(f, "match"), 20 | Keyword::Import => write!(f, "import"), 21 | Keyword::Selff => write!(f, "self"), // "self" 22 | Keyword::Unknown => write!(f, "unknown"), 23 | } 24 | } 25 | } 26 | 27 | impl std::fmt::Display for TokenKind { 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 | match self { 30 | TokenKind::Whitespace => write!(f, "whitespace"), 31 | TokenKind::CarriageReturn => write!(f, "\\n"), 32 | TokenKind::Identifier(id) => write!(f, "{id}"), 33 | TokenKind::Literal(value) => write!(f, "{value}"), 34 | TokenKind::Keyword(keyword) => write!(f, "{keyword}"), 35 | TokenKind::Comment => write!(f, "comment"), 36 | TokenKind::Plus => write!(f, "+"), 37 | TokenKind::Minus => write!(f, "-"), 38 | TokenKind::Star => write!(f, "*"), 39 | TokenKind::Slash => write!(f, "/"), 40 | TokenKind::Percent => write!(f, "%"), 41 | TokenKind::Colon => write!(f, ":"), 42 | TokenKind::SemiColon => write!(f, ";"), 43 | TokenKind::Dot => write!(f, "."), 44 | TokenKind::Exclamation => write!(f, "!"), 45 | TokenKind::Comma => write!(f, ","), 46 | TokenKind::Assign => writeln!(f, "="), 47 | TokenKind::Equals => write!(f, "=="), 48 | TokenKind::LessThan => write!(f, "<"), 49 | TokenKind::LessThanOrEqual => write!(f, "<="), 50 | TokenKind::GreaterThan => write!(f, ">"), 51 | TokenKind::GreaterThanOrEqual => write!(f, ">="), 52 | TokenKind::NotEqual => write!(f, "!="), 53 | TokenKind::And => write!(f, "&&"), 54 | TokenKind::Or => write!(f, "||"), 55 | TokenKind::PlusEqual => write!(f, "+="), 56 | TokenKind::MinusEqual => write!(f, "-="), 57 | TokenKind::StarEqual => write!(f, "*="), 58 | TokenKind::SlashEqual => write!(f, "/="), 59 | TokenKind::ArrowRight => write!(f, "=>"), 60 | TokenKind::BraceOpen => write!(f, "("), 61 | TokenKind::BraceClose => write!(f, ")"), 62 | TokenKind::SquareBraceOpen => write!(f, "["), 63 | TokenKind::SquareBraceClose => write!(f, "]"), 64 | TokenKind::CurlyBracesOpen => write!(f, "{{"), 65 | TokenKind::CurlyBracesClose => write!(f, "}}"), 66 | TokenKind::Tab => write!(f, "tab"), 67 | TokenKind::Unknown => write!(f, "unknown"), 68 | } 69 | } 70 | } 71 | 72 | impl std::fmt::Display for Value { 73 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 74 | match self { 75 | Value::Int => write!(f, "int literal"), 76 | Value::Str(v) => write!(f, "string literal ({v})"), 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/lexer/mod.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pub(crate) mod cursor; 17 | 18 | use self::TokenKind::*; 19 | use cursor::Cursor; 20 | 21 | #[cfg(test)] 22 | mod tests; 23 | 24 | mod display; 25 | 26 | #[derive(Debug, PartialEq, Eq, Clone)] 27 | pub struct Token { 28 | pub kind: TokenKind, 29 | pub len: usize, 30 | pub raw: String, 31 | pub pos: Position, 32 | } 33 | 34 | impl Token { 35 | fn new(kind: TokenKind, len: usize, raw: String, pos: Position) -> Token { 36 | Token { 37 | kind, 38 | len, 39 | raw, 40 | pos, 41 | } 42 | } 43 | } 44 | 45 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 46 | pub struct Position { 47 | pub line: usize, 48 | pub offset: usize, 49 | pub raw: usize, 50 | } 51 | 52 | /// Enum representing common lexeme types. 53 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 54 | pub enum TokenKind { 55 | /// Any whitespace characters sequence. 56 | Whitespace, 57 | Identifier(String), 58 | Literal(Value), 59 | /// Keywords such as 'if' or 'else' 60 | Keyword(Keyword), 61 | /// // Lorem Ipsum 62 | Comment, 63 | /// "+" 64 | Plus, 65 | /// "-" 66 | Minus, 67 | /// "*" 68 | Star, 69 | /// "/" 70 | Slash, 71 | /// "%" 72 | Percent, 73 | /// ":" 74 | Colon, 75 | /// ";" 76 | SemiColon, 77 | /// "." 78 | Dot, 79 | /// "!" 80 | Exclamation, 81 | /// "," 82 | Comma, 83 | /// "=" 84 | Assign, 85 | /// "==" 86 | Equals, 87 | /// "<" 88 | LessThan, 89 | /// "<=" 90 | LessThanOrEqual, 91 | /// ">" 92 | GreaterThan, 93 | /// ">=" 94 | GreaterThanOrEqual, 95 | /// "!=" 96 | NotEqual, 97 | /// && 98 | And, 99 | /// "||" 100 | Or, 101 | /// "+=" 102 | PlusEqual, 103 | /// "-=" 104 | MinusEqual, 105 | /// "*=" 106 | StarEqual, 107 | /// "/=" 108 | SlashEqual, 109 | /// "=>" 110 | ArrowRight, 111 | /// "(" 112 | BraceOpen, 113 | /// ")" 114 | BraceClose, 115 | /// "[" 116 | SquareBraceOpen, 117 | /// "]" 118 | SquareBraceClose, 119 | /// "{" 120 | CurlyBracesOpen, 121 | /// "}" 122 | CurlyBracesClose, 123 | /// "\t" 124 | Tab, 125 | /// "\n" 126 | CarriageReturn, 127 | /// Unknown token, not expected by the lexer, e.g. "№" 128 | Unknown, 129 | } 130 | 131 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 132 | pub enum Value { 133 | Int, 134 | Str(String), 135 | } 136 | 137 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 138 | pub enum Keyword { 139 | Let, 140 | If, 141 | Else, 142 | Return, 143 | While, 144 | For, 145 | In, 146 | Break, 147 | Continue, 148 | Function, 149 | Boolean, 150 | Struct, 151 | New, 152 | Match, 153 | Import, 154 | Selff, // "self" 155 | Unknown, 156 | } 157 | 158 | /// Creates an iterator that produces tokens from the input string. 159 | pub fn tokenize(mut input: &str) -> Result, String> { 160 | let mut pos = Position { 161 | raw: usize::MAX, 162 | line: 1, 163 | offset: 0, 164 | }; 165 | 166 | let mut tokens: Vec = Vec::new(); 167 | while !input.is_empty() { 168 | let token = first_token(input, &mut pos)?; 169 | input = &input[token.len..]; 170 | tokens.push(token); 171 | } 172 | 173 | Ok(tokens) 174 | } 175 | 176 | /// Parses the first token from the provided input string. 177 | pub fn first_token(input: &str, pos: &mut Position) -> Result { 178 | debug_assert!(!input.is_empty()); 179 | Cursor::new(input, pos).advance_token() 180 | } 181 | 182 | pub fn is_whitespace(c: char) -> bool { 183 | // https://doc.rust-lang.org/reference/whitespace.html 184 | matches!( 185 | c, 186 | // Usual ASCII suspects 187 | '\u{0009}' // \t 188 | | '\u{000A}' // \n 189 | | '\u{000B}' // vertical tab 190 | | '\u{000C}' // form feed 191 | | '\u{000D}' // \r 192 | | '\u{0020}' // space 193 | 194 | // NEXT LINE from latin1 195 | | '\u{0085}' 196 | 197 | // Bidi markers 198 | | '\u{200E}' // LEFT-TO-RIGHT MARK 199 | | '\u{200F}' // RIGHT-TO-LEFT MARK 200 | 201 | // Dedicated whitespace characters from Unicode 202 | | '\u{2028}' // LINE SEPARATOR 203 | | '\u{2029}' // PARAGRAPH SEPARATOR 204 | ) 205 | } 206 | 207 | /// True if `c` is a valid first character of an identifier 208 | /// See [Antimony specification](https://antimony-lang.github.io/antimony/developers/specification.html#identifiers) for 209 | /// a formal definition of valid identifier name. 210 | pub fn is_id_start(c: char) -> bool { 211 | // Valid identifier start is either an underscore or any Unicode letter 212 | c == '_' || c.is_alphabetic() 213 | } 214 | 215 | /// True if `c` is a valid continuation of an identifier 216 | /// See [Antimony specification](https://antimony-lang.github.io/antimony/developers/specification.html#identifiers) for 217 | /// a formal definition of valid identifier name. 218 | pub fn is_id_continue(c: char) -> bool { 219 | // Valid identifier continuation is underscore, letter, or number 220 | c == '_' || c.is_alphabetic() || c.is_numeric() 221 | } 222 | 223 | impl Cursor<'_> { 224 | /// Parses a token from the input string. 225 | fn advance_token(&mut self) -> Result { 226 | // Original chars used to identify the token later on 227 | let original_chars = self.chars(); 228 | // FIXME: Identical value, since it will be used twice and is not clonable later 229 | let original_chars2 = self.chars(); 230 | let first_char = self.bump().unwrap(); 231 | let token_kind = match first_char { 232 | c if is_whitespace(c) => self.whitespace(), 233 | '0'..='9' => self.number(), 234 | '"' | '\'' => self.string(first_char)?, 235 | '.' => Dot, 236 | '+' => match self.first() { 237 | '=' => { 238 | self.bump(); 239 | PlusEqual 240 | } 241 | _ => Plus, 242 | }, 243 | '-' => match self.first() { 244 | '=' => { 245 | self.bump(); 246 | MinusEqual 247 | } 248 | _ => Minus, 249 | }, 250 | '*' => match self.first() { 251 | '=' => { 252 | self.bump(); 253 | StarEqual 254 | } 255 | _ => Star, 256 | }, 257 | '%' => Percent, 258 | '/' => match self.first() { 259 | '/' => { 260 | self.bump(); 261 | self.comment() 262 | } 263 | '=' => { 264 | self.bump(); 265 | SlashEqual 266 | } 267 | _ => Slash, 268 | }, 269 | '=' => match self.first() { 270 | '=' => { 271 | self.bump(); 272 | Equals 273 | } 274 | '>' => { 275 | self.bump(); 276 | ArrowRight 277 | } 278 | _ => Assign, 279 | }, 280 | ':' => Colon, 281 | ';' => SemiColon, 282 | ',' => Comma, 283 | '<' => match self.first() { 284 | '=' => { 285 | self.bump(); 286 | LessThanOrEqual 287 | } 288 | _ => LessThan, 289 | }, 290 | '>' => match self.first() { 291 | '=' => { 292 | self.bump(); 293 | GreaterThanOrEqual 294 | } 295 | _ => GreaterThan, 296 | }, 297 | '&' => match self.first() { 298 | '&' => { 299 | self.bump(); 300 | And 301 | } 302 | _ => Unknown, 303 | }, 304 | '|' => match self.first() { 305 | '|' => { 306 | self.bump(); 307 | Or 308 | } 309 | _ => Unknown, 310 | }, 311 | '!' => match self.first() { 312 | '=' => { 313 | self.bump(); 314 | NotEqual 315 | } 316 | _ => Exclamation, 317 | }, 318 | '(' => BraceOpen, 319 | ')' => BraceClose, 320 | '[' => SquareBraceOpen, 321 | ']' => SquareBraceClose, 322 | '{' => CurlyBracesOpen, 323 | '}' => CurlyBracesClose, 324 | c if is_id_start(c) => { 325 | let kind = self.identifier(c); 326 | if kind == Keyword::Unknown { 327 | let mut ch: String = original_chars.collect(); 328 | ch.truncate(self.len_consumed()); 329 | TokenKind::Identifier(ch) 330 | } else { 331 | TokenKind::Keyword(kind) 332 | } 333 | } 334 | '\n' => CarriageReturn, 335 | '\t' => Tab, 336 | _ => Unknown, 337 | }; 338 | 339 | let len = self.len_consumed(); 340 | let mut raw = original_chars2.collect::(); 341 | // Cut the original tokens to the length of the token 342 | raw.truncate(len); 343 | let position = self.pos(); 344 | 345 | Ok(Token::new(token_kind, len, raw, position)) 346 | } 347 | 348 | /// Eats symbols while predicate returns true or until the end of file is reached. 349 | /// Returns amount of eaten symbols. 350 | fn eat_while(&mut self, mut predicate: F) -> usize 351 | where 352 | F: FnMut(char) -> bool, 353 | { 354 | let mut eaten: usize = 0; 355 | while predicate(self.first()) && !self.is_eof() { 356 | eaten += self.first().len_utf8(); 357 | self.bump(); 358 | } 359 | 360 | eaten 361 | } 362 | 363 | fn whitespace(&mut self) -> TokenKind { 364 | debug_assert!(is_whitespace(self.prev())); 365 | self.eat_while(is_whitespace); 366 | Whitespace 367 | } 368 | 369 | fn number(&mut self) -> TokenKind { 370 | match self.first() { 371 | 'b' => { 372 | self.bump(); 373 | self.eat_binary_digits(); 374 | } 375 | 'o' => { 376 | self.bump(); 377 | self.eat_octal_digits(); 378 | } 379 | 'x' => { 380 | self.bump(); 381 | self.eat_hex_digits(); 382 | } 383 | _ => { 384 | self.eat_digits(); 385 | } 386 | }; 387 | TokenKind::Literal(Value::Int) 388 | } 389 | 390 | fn string(&mut self, end: char) -> Result { 391 | Ok(TokenKind::Literal(Value::Str(self.eat_string(end)?))) 392 | } 393 | 394 | fn identifier(&mut self, first_char: char) -> Keyword { 395 | let mut original: String = self.chars().collect::(); 396 | let len = self.eat_while(is_id_continue); 397 | 398 | // Cut original "rest"-character stream to length of token 399 | // and prepend first character, because it has been eaten beforehand 400 | original.truncate(len); 401 | original = format!("{}{}", first_char, original); 402 | 403 | match original { 404 | c if c == "if" => Keyword::If, 405 | c if c == "else" => Keyword::Else, 406 | c if c == "fn" => Keyword::Function, 407 | c if c == "true" || c == "false" => Keyword::Boolean, 408 | c if c == "let" => Keyword::Let, 409 | c if c == "return" => Keyword::Return, 410 | c if c == "while" => Keyword::While, 411 | c if c == "for" => Keyword::For, 412 | c if c == "in" => Keyword::In, 413 | c if c == "break" => Keyword::Break, 414 | c if c == "continue" => Keyword::Continue, 415 | c if c == "struct" => Keyword::Struct, 416 | c if c == "new" => Keyword::New, 417 | c if c == "match" => Keyword::Match, 418 | c if c == "import" => Keyword::Import, 419 | c if c == "self" => Keyword::Selff, 420 | _ => Keyword::Unknown, 421 | } 422 | } 423 | 424 | fn comment(&mut self) -> TokenKind { 425 | // FIXME: Might lead to a bug, if End of file is encountered 426 | while self.first() != '\n' { 427 | self.bump(); 428 | } 429 | 430 | TokenKind::Comment 431 | } 432 | 433 | fn eat_digits(&mut self) -> bool { 434 | let mut has_digits = false; 435 | loop { 436 | match self.first() { 437 | '_' => { 438 | self.bump(); 439 | } 440 | '0'..='9' => { 441 | has_digits = true; 442 | self.bump(); 443 | } 444 | _ => break, 445 | } 446 | } 447 | has_digits 448 | } 449 | 450 | fn eat_binary_digits(&mut self) -> bool { 451 | let mut has_digits = false; 452 | loop { 453 | match self.first() { 454 | '_' => { 455 | self.bump(); 456 | } 457 | '0' | '1' => { 458 | has_digits = true; 459 | self.bump(); 460 | } 461 | _ => break, 462 | } 463 | } 464 | has_digits 465 | } 466 | 467 | fn eat_octal_digits(&mut self) -> bool { 468 | let mut has_digits = false; 469 | loop { 470 | match self.first() { 471 | '_' => { 472 | self.bump(); 473 | } 474 | '0'..='7' => { 475 | has_digits = true; 476 | self.bump(); 477 | } 478 | _ => break, 479 | } 480 | } 481 | has_digits 482 | } 483 | 484 | fn eat_hex_digits(&mut self) -> bool { 485 | let mut has_digits = false; 486 | loop { 487 | match self.first() { 488 | '_' => { 489 | self.bump(); 490 | } 491 | '0'..='9' | 'a'..='f' | 'A'..='F' => { 492 | has_digits = true; 493 | self.bump(); 494 | } 495 | _ => break, 496 | } 497 | } 498 | has_digits 499 | } 500 | 501 | fn eat_escape(&mut self) -> Result { 502 | let ch = self.first(); 503 | let ch = match ch { 504 | 'n' => '\n', // Newline 505 | 'r' => '\r', // Carriage Return 506 | 'b' => '\u{0008}', // Backspace 507 | 'f' => '\u{000C}', // Form feed 508 | 't' => '\t', // Horizontal tab 509 | '"' | '\\' => ch, 510 | ch => { 511 | return Err(self.make_error_msg(format!("Unknown escape sequence \\{}", ch))); 512 | } 513 | }; 514 | self.bump(); 515 | 516 | Ok(ch) 517 | } 518 | 519 | fn eat_string(&mut self, end: char) -> Result { 520 | let mut buf = String::new(); 521 | loop { 522 | match self.first() { 523 | '\n' => return Err(self.make_error_msg("String does not end on same line".into())), 524 | '\\' => { 525 | self.bump(); 526 | buf.push(self.eat_escape()?) 527 | } 528 | ch if ch == end => break, 529 | ch => { 530 | buf.push(ch); 531 | self.bump(); 532 | } 533 | }; 534 | } 535 | 536 | // Eat last quote 537 | self.bump(); 538 | 539 | Ok(buf) 540 | } 541 | 542 | fn make_error_msg(&self, msg: String) -> String { 543 | let pos = self.pos(); 544 | format!("{}:{}: {}", pos.line, pos.offset, msg) 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /src/lexer/tests.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::lexer::*; 17 | 18 | #[test] 19 | fn test_basic_tokenizing() { 20 | let raw = tokenize("1 = 2").unwrap(); 21 | let mut tokens = raw.into_iter(); 22 | 23 | assert_eq!( 24 | tokens.next().unwrap(), 25 | Token { 26 | len: 1, 27 | kind: TokenKind::Literal(Value::Int), 28 | raw: "1".to_owned(), 29 | pos: Position { 30 | raw: 0, 31 | line: 1, 32 | offset: 0 33 | } 34 | } 35 | ); 36 | 37 | assert_eq!( 38 | tokens.next().unwrap(), 39 | Token { 40 | len: 1, 41 | kind: TokenKind::Whitespace, 42 | raw: " ".to_owned(), 43 | pos: Position { 44 | raw: 1, 45 | line: 1, 46 | offset: 1 47 | } 48 | } 49 | ); 50 | 51 | assert_eq!( 52 | tokens.next().unwrap(), 53 | Token { 54 | len: 1, 55 | kind: TokenKind::Assign, 56 | raw: "=".to_owned(), 57 | pos: Position { 58 | raw: 2, 59 | line: 1, 60 | offset: 2 61 | } 62 | } 63 | ); 64 | 65 | assert_eq!( 66 | tokens.next().unwrap(), 67 | Token { 68 | len: 1, 69 | kind: TokenKind::Whitespace, 70 | raw: " ".to_owned(), 71 | pos: Position { 72 | raw: 3, 73 | line: 1, 74 | offset: 3 75 | } 76 | } 77 | ); 78 | 79 | assert_eq!( 80 | tokens.next().unwrap(), 81 | Token { 82 | len: 1, 83 | kind: TokenKind::Literal(Value::Int), 84 | raw: "2".to_owned(), 85 | pos: Position { 86 | raw: 4, 87 | line: 1, 88 | offset: 4 89 | } 90 | } 91 | ); 92 | } 93 | 94 | #[test] 95 | fn test_tokenizing_without_whitespace() { 96 | let mut tokens = tokenize("1=2").unwrap().into_iter(); 97 | 98 | assert_eq!( 99 | tokens.next().unwrap(), 100 | Token { 101 | len: 1, 102 | kind: TokenKind::Literal(Value::Int), 103 | raw: "1".to_owned(), 104 | pos: Position { 105 | raw: 0, 106 | line: 1, 107 | offset: 0 108 | } 109 | } 110 | ); 111 | 112 | assert_eq!( 113 | tokens.next().unwrap(), 114 | Token { 115 | len: 1, 116 | kind: TokenKind::Assign, 117 | raw: "=".to_owned(), 118 | pos: Position { 119 | raw: 1, 120 | line: 1, 121 | offset: 1 122 | } 123 | } 124 | ); 125 | 126 | assert_eq!( 127 | tokens.next().unwrap(), 128 | Token { 129 | len: 1, 130 | kind: TokenKind::Literal(Value::Int), 131 | raw: "2".to_owned(), 132 | pos: Position { 133 | raw: 2, 134 | line: 1, 135 | offset: 2 136 | } 137 | } 138 | ); 139 | } 140 | 141 | #[test] 142 | fn test_string() { 143 | let mut tokens = tokenize("'aaa' \"bbb\"").unwrap().into_iter(); 144 | 145 | assert_eq!( 146 | tokens.next().unwrap(), 147 | Token { 148 | len: 5, 149 | kind: TokenKind::Literal(Value::Str("aaa".into())), 150 | raw: "'aaa'".to_owned(), 151 | pos: Position { 152 | raw: 4, 153 | line: 1, 154 | offset: 4 155 | } 156 | } 157 | ); 158 | 159 | assert_eq!( 160 | tokens.nth(1).unwrap(), 161 | Token { 162 | len: 5, 163 | kind: TokenKind::Literal(Value::Str("bbb".into())), 164 | raw: "\"bbb\"".to_owned(), 165 | pos: Position { 166 | raw: 10, 167 | line: 1, 168 | offset: 10 169 | } 170 | } 171 | ); 172 | } 173 | 174 | #[test] 175 | fn test_string_markers_within_string() { 176 | let mut tokens = tokenize("'\"aaa' \"'bbb\"").unwrap().into_iter(); 177 | 178 | assert_eq!( 179 | tokens.next().unwrap(), 180 | Token { 181 | len: 6, 182 | kind: TokenKind::Literal(Value::Str("\"aaa".into())), 183 | raw: "'\"aaa'".to_owned(), 184 | pos: Position { 185 | raw: 5, 186 | line: 1, 187 | offset: 5 188 | } 189 | } 190 | ); 191 | 192 | assert_eq!( 193 | tokens.nth(1).unwrap(), 194 | Token { 195 | len: 6, 196 | kind: TokenKind::Literal(Value::Str("'bbb".into())), 197 | raw: "\"'bbb\"".to_owned(), 198 | pos: Position { 199 | raw: 12, 200 | line: 1, 201 | offset: 12 202 | } 203 | } 204 | ); 205 | } 206 | 207 | #[test] 208 | fn test_numbers() { 209 | let mut tokens = tokenize("42").unwrap().into_iter(); 210 | 211 | assert_eq!( 212 | tokens.next().unwrap(), 213 | Token { 214 | len: 2, 215 | kind: TokenKind::Literal(Value::Int), 216 | raw: "42".to_owned(), 217 | pos: Position { 218 | raw: 1, 219 | line: 1, 220 | offset: 1 221 | } 222 | } 223 | ); 224 | } 225 | 226 | #[test] 227 | fn test_binary_numbers() { 228 | let mut tokens = tokenize("0b101010").unwrap().into_iter(); 229 | 230 | assert_eq!( 231 | tokens.next().unwrap(), 232 | Token { 233 | len: 8, 234 | kind: TokenKind::Literal(Value::Int), 235 | raw: "0b101010".to_owned(), 236 | pos: Position { 237 | raw: 7, 238 | line: 1, 239 | offset: 7 240 | } 241 | } 242 | ); 243 | } 244 | 245 | #[test] 246 | fn test_octal_numbers() { 247 | let mut tokens = tokenize("0o52").unwrap().into_iter(); 248 | 249 | assert_eq!( 250 | tokens.next().unwrap(), 251 | Token { 252 | len: 4, 253 | kind: TokenKind::Literal(Value::Int), 254 | raw: "0o52".to_owned(), 255 | pos: Position { 256 | raw: 3, 257 | line: 1, 258 | offset: 3 259 | } 260 | } 261 | ); 262 | } 263 | 264 | #[test] 265 | fn test_hex_numbers() { 266 | let mut tokens = tokenize("0x2A").unwrap().into_iter(); 267 | 268 | assert_eq!( 269 | tokens.next().unwrap(), 270 | Token { 271 | len: 4, 272 | kind: TokenKind::Literal(Value::Int), 273 | raw: "0x2A".to_owned(), 274 | pos: Position { 275 | raw: 3, 276 | line: 1, 277 | offset: 3 278 | } 279 | } 280 | ); 281 | } 282 | 283 | #[test] 284 | fn test_functions() { 285 | let mut tokens = tokenize("fn fib() {}").unwrap().into_iter(); 286 | 287 | assert_eq!( 288 | tokens.next().unwrap(), 289 | Token { 290 | len: 2, 291 | kind: TokenKind::Keyword(Keyword::Function), 292 | raw: "fn".to_owned(), 293 | pos: Position { 294 | raw: 1, 295 | line: 1, 296 | offset: 1 297 | } 298 | } 299 | ); 300 | } 301 | 302 | #[test] 303 | fn test_comments() { 304 | let mut tokens = tokenize( 305 | "// foo 306 | fn fib() {} 307 | ", 308 | ) 309 | .unwrap() 310 | .into_iter() 311 | .filter(|t| { 312 | t.kind != TokenKind::Whitespace 313 | && t.kind != TokenKind::Tab 314 | && t.kind != TokenKind::CarriageReturn 315 | }); 316 | 317 | assert_eq!( 318 | tokens.next().unwrap(), 319 | Token { 320 | len: 6, 321 | kind: TokenKind::Comment, 322 | raw: "// foo".to_owned(), 323 | pos: Position { 324 | raw: 5, 325 | line: 1, 326 | offset: 5 327 | } 328 | } 329 | ); 330 | 331 | assert_eq!( 332 | tokens.next().unwrap(), 333 | Token { 334 | len: 2, 335 | kind: TokenKind::Keyword(Keyword::Function), 336 | raw: "fn".to_owned(), 337 | pos: Position { 338 | raw: 8, 339 | line: 2, 340 | offset: 2 341 | } 342 | } 343 | ); 344 | } 345 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | extern crate lazy_static; 17 | extern crate qbe; 18 | extern crate rust_embed; 19 | extern crate structopt; 20 | 21 | use generator::Target; 22 | use std::path::PathBuf; 23 | use std::process; 24 | use structopt::StructOpt; 25 | 26 | mod ast; 27 | mod builder; 28 | mod command; 29 | mod generator; 30 | mod lexer; 31 | mod parser; 32 | #[cfg(test)] 33 | mod tests; 34 | mod util; 35 | use rust_embed::RustEmbed; 36 | 37 | #[derive(RustEmbed)] 38 | #[folder = "lib/"] 39 | pub struct Lib; 40 | 41 | #[derive(RustEmbed)] 42 | #[folder = "builtin/"] 43 | pub struct Builtins; 44 | 45 | #[derive(StructOpt, Debug)] 46 | enum Command { 47 | #[structopt()] 48 | Build { 49 | in_file: PathBuf, 50 | /// Write output to a file. Use '-' to print to stdout 51 | #[structopt(short, long)] 52 | out_file: PathBuf, 53 | }, 54 | #[structopt()] 55 | Run { in_file: PathBuf }, 56 | } 57 | 58 | #[derive(StructOpt, Debug)] 59 | struct Opt { 60 | #[structopt(subcommand)] 61 | command: Command, 62 | 63 | /// Target language. Options: c, js, llvm, x86 64 | #[structopt(long, short, parse(try_from_str))] 65 | target: Option, 66 | } 67 | 68 | fn main() { 69 | if let Err(err) = run() { 70 | eprintln!("Error: {}", err); 71 | process::exit(1); 72 | } 73 | } 74 | 75 | fn run() -> Result<(), String> { 76 | let opts = Opt::from_args(); 77 | 78 | match opts.command { 79 | Command::Build { in_file, out_file } => { 80 | let target = match opts.target { 81 | Some(t) => t, 82 | None => Target::from_extension(&out_file).ok_or_else(|| { 83 | format!( 84 | "Cannot detect target from output file {}, use --target option to set it explicitly", 85 | &out_file.to_string_lossy(), 86 | ) 87 | })?, 88 | }; 89 | 90 | command::build::build(&target, &in_file, &out_file)? 91 | } 92 | Command::Run { in_file } => command::run::run(opts.target.unwrap_or(Target::JS), in_file)?, 93 | }; 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /src/parser/infer.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::ast::types::Type; 17 | use crate::ast::{Expression, Module, Statement, SymbolTable}; 18 | 19 | /// Try to infer types of variables 20 | /// 21 | /// TODO: Global symbol table is passed around randomly. 22 | /// This could probably be cleaned up. 23 | pub(super) fn infer(program: &mut Module) { 24 | let table = &program.get_symbol_table(); 25 | // TODO: Fix aweful nesting 26 | for func in &mut program.func { 27 | if let Statement::Block { 28 | statements, 29 | scope: _, 30 | } = &mut func.body 31 | { 32 | for statement in statements { 33 | if let Statement::Declare { variable, value } = statement { 34 | if variable.ty.is_none() { 35 | if let Some(e) = value { 36 | variable.ty = infer_expression(e, table); 37 | #[cfg(debug_assertions)] 38 | if variable.ty.is_none() { 39 | println!( 40 | "Type of {} could not be infered: {:?}", 41 | &variable.name, e 42 | ); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | /// Function table is needed to infer possible function calls 53 | fn infer_expression(expr: &Expression, table: &SymbolTable) -> Option { 54 | match expr { 55 | Expression::Int(_) => Some(Type::Int), 56 | Expression::Bool(_) => Some(Type::Bool), 57 | Expression::Str(_) => Some(Type::Str), 58 | Expression::StructInitialization { name, fields: _ } => { 59 | Some(Type::Struct(name.to_string())) 60 | } 61 | Expression::FunctionCall { fn_name, args: _ } => infer_function_call(fn_name, table), 62 | Expression::Array { 63 | capacity: _, 64 | elements, 65 | } => infer_array(elements, table), 66 | _ => None, 67 | } 68 | } 69 | 70 | fn infer_array(elements: &[Expression], table: &SymbolTable) -> Option { 71 | let types: Vec> = elements 72 | .iter() 73 | .map(|el| infer_expression(el, table)) 74 | .collect(); 75 | 76 | // TODO: This approach only relies on the first element. 77 | // It will not catch that types are possibly inconsistent. 78 | types 79 | .first() 80 | .and_then(|ty| ty.to_owned()) 81 | .map(|ty| Type::Array(Box::new(ty), Some(types.len()))) 82 | } 83 | 84 | fn infer_function_call(name: &str, table: &SymbolTable) -> Option { 85 | match table.get(name) { 86 | Some(t) => t.to_owned(), 87 | None => None, 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | mod infer; 17 | // TODO: Resolve this lint by renaming the module 18 | #[allow(clippy::module_inception)] 19 | mod parser; 20 | mod rules; 21 | use crate::ast::Module; 22 | use crate::lexer::Token; 23 | #[cfg(test)] 24 | mod tests; 25 | 26 | pub fn parse(tokens: Vec, raw: Option) -> Result { 27 | let mut parser = parser::Parser::new(tokens, raw); 28 | parser.parse() 29 | } 30 | -------------------------------------------------------------------------------- /src/parser/parser.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::ast::*; 17 | use crate::lexer::Keyword; 18 | use crate::lexer::Position; 19 | use crate::lexer::{Token, TokenKind}; 20 | use crate::parser::infer::infer; 21 | use crate::util::string_util::highlight_position_in_file; 22 | use std::convert::TryFrom; 23 | use std::iter::Peekable; 24 | use std::vec::IntoIter; 25 | 26 | pub struct Parser { 27 | tokens: Peekable>, 28 | peeked: Vec, 29 | current: Option, 30 | prev: Option, 31 | raw: Option, 32 | } 33 | 34 | impl Parser { 35 | #[allow(clippy::needless_collect)] // TODO 36 | pub fn new(tokens: Vec, raw: Option) -> Parser { 37 | let tokens_without_whitespace: Vec = tokens 38 | .into_iter() 39 | .filter(|token| token.kind != TokenKind::Whitespace && token.kind != TokenKind::Comment) 40 | .collect(); 41 | Parser { 42 | tokens: tokens_without_whitespace.into_iter().peekable(), 43 | peeked: vec![], 44 | current: None, 45 | prev: None, 46 | raw, 47 | } 48 | } 49 | 50 | pub fn parse(&mut self) -> Result { 51 | let mut program = self.parse_module()?; 52 | // infer types 53 | infer(&mut program); 54 | 55 | Ok(program) 56 | } 57 | 58 | pub(super) fn next(&mut self) -> Result { 59 | self.prev = self.current.to_owned(); 60 | let item = if self.peeked.is_empty() { 61 | self.tokens.next() 62 | } else { 63 | self.peeked.pop() 64 | }; 65 | 66 | self.current = item.to_owned(); 67 | item.ok_or_else(|| "Expected token".into()) 68 | } 69 | 70 | pub(super) fn peek(&mut self) -> Result { 71 | let token = self.next()?; 72 | self.push(token.to_owned()); 73 | Ok(token) 74 | } 75 | 76 | pub(super) fn push(&mut self, token: Token) { 77 | self.peeked.push(token); 78 | } 79 | 80 | pub(super) fn has_more(&mut self) -> bool { 81 | !self.peeked.is_empty() || self.tokens.peek().is_some() 82 | } 83 | 84 | pub(super) fn match_token(&mut self, token_kind: TokenKind) -> Result { 85 | match self.next()? { 86 | token if token.kind == token_kind => Ok(token), 87 | other => Err(self.make_error(token_kind, other)), 88 | } 89 | } 90 | 91 | pub(super) fn peek_token(&mut self, token_kind: TokenKind) -> Result { 92 | match self.peek()? { 93 | token if token.kind == token_kind => Ok(token), 94 | other => Err(self.make_error(token_kind, other)), 95 | } 96 | } 97 | 98 | pub(super) fn match_keyword(&mut self, keyword: Keyword) -> Result<(), String> { 99 | let token = self.next()?; 100 | match &token.kind { 101 | TokenKind::Keyword(ref k) if k == &keyword => Ok(()), 102 | _ => { 103 | let mut error = self 104 | .make_error_msg(token.pos, format!("Expected keyword, found {}", token.raw)); 105 | let hint = self.make_hint_msg(format!( 106 | "replace the symbol `{}` with the appropriate keyword. ", 107 | token.raw 108 | )); 109 | error.push_str(&hint); 110 | Err(error) 111 | } 112 | } 113 | } 114 | 115 | pub(super) fn match_operator(&mut self) -> Result { 116 | BinOp::try_from(self.next()?.kind) 117 | } 118 | 119 | pub(super) fn match_identifier(&mut self) -> Result { 120 | let token = self.next()?; 121 | match &token.kind { 122 | TokenKind::Identifier(n) => Ok(n.to_string()), 123 | other => { 124 | let mut error = self 125 | .make_error_msg(token.pos, format!("Expected Identifier, found `{other}`",)); 126 | let hint = self.make_hint_msg(format!( 127 | "replace the symbol `{other}` with an identifier. Example `Foo`" 128 | )); 129 | error.push_str(&hint); 130 | Err(error) 131 | } 132 | } 133 | } 134 | 135 | pub(super) fn make_error(&mut self, token_kind: TokenKind, other: Token) -> String { 136 | let other_kind = &other.kind; 137 | self.make_error_msg( 138 | other.pos, 139 | format!("Token `{token_kind}` not found, found `{other_kind}`"), 140 | ) 141 | } 142 | 143 | pub(super) fn make_error_msg(&mut self, pos: Position, msg: String) -> String { 144 | match &self.raw { 145 | Some(raw_file) => format!( 146 | "{}:{}: {}\n{}", 147 | pos.line, 148 | pos.offset, 149 | msg, 150 | highlight_position_in_file(raw_file.to_string(), pos) 151 | ), 152 | None => format!("{}:{}: {}", pos.line, pos.offset, msg), 153 | } 154 | } 155 | 156 | pub(super) fn make_hint_msg(&mut self, msg: String) -> String { 157 | let new_lines = "\n".repeat(3); 158 | format!("{new_lines}Hint: {}\n", msg) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | mod test_examples; 17 | -------------------------------------------------------------------------------- /src/tests/test_examples.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | /** 3 | * Copyright 2020 Garrit Franke 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | use std::io::Error; 18 | use std::process::Command; 19 | 20 | fn test_directory(dir_in: &str) -> Result<(), Error> { 21 | let dir_out = format!("{}_out", dir_in); 22 | let dir = std::env::current_dir().unwrap(); 23 | 24 | let examples = std::fs::read_dir(dir.join(dir_in))?; 25 | 26 | let _ = fs::create_dir(&dir_out); 27 | 28 | let out_file_suffix = ".js"; 29 | 30 | for ex in examples { 31 | let example = ex?; 32 | let in_file = dir.join(dir_in).join(example.file_name()); 33 | 34 | // We don't want to build submodules, since they don't run without a main function 35 | if in_file.is_dir() { 36 | continue; 37 | } 38 | let out_file = dir.join(&dir_out).join( 39 | example 40 | .file_name() 41 | .into_string() 42 | .unwrap() 43 | .replace(".sb", out_file_suffix), 44 | ); 45 | let success = Command::new("cargo") 46 | .arg("run") 47 | .arg("build") 48 | .arg(&in_file) 49 | .arg("-o") 50 | .arg(&out_file) 51 | .spawn()? 52 | .wait()? 53 | .success(); 54 | assert!(success, "{:?}", &in_file); 55 | 56 | let node_installed = Command::new("node").arg("-v").spawn()?.wait()?.success(); 57 | if node_installed { 58 | let execution = Command::new("node") 59 | .arg(out_file) 60 | .spawn()? 61 | .wait()? 62 | .success(); 63 | assert!(execution, "{:?}", &in_file) 64 | } 65 | } 66 | Ok(()) 67 | } 68 | 69 | #[test] 70 | fn test_examples() -> Result<(), Error> { 71 | test_directory("examples")?; 72 | Ok(()) 73 | } 74 | 75 | #[test] 76 | fn test_testcases() -> Result<(), Error> { 77 | let dir = std::env::current_dir().unwrap(); 78 | 79 | let in_file = dir.join("tests/main.sb"); 80 | let success = Command::new("cargo") 81 | .arg("run") 82 | .arg("run") 83 | .arg(&in_file) 84 | .spawn()? 85 | .wait()? 86 | .success(); 87 | assert!(success, "{:?}", &in_file); 88 | Ok(()) 89 | } 90 | 91 | #[test] 92 | fn test_struct_decl_error() -> Result<(), Error> { 93 | let dir = std::env::current_dir().unwrap(); 94 | 95 | let in_file = dir.join("tests/struct_decl_err.sb"); 96 | let success = Command::new("cargo") 97 | .arg("run") 98 | .arg("run") 99 | .arg(&in_file) 100 | .spawn()? 101 | .wait()? 102 | .success(); 103 | 104 | assert!(!success); 105 | Ok(()) 106 | } 107 | 108 | #[test] 109 | fn test_struct_instance_error() -> Result<(), Error> { 110 | let dir = std::env::current_dir().unwrap(); 111 | 112 | let in_file = dir.join("tests/struct_instance_err.sb"); 113 | let success = Command::new("cargo") 114 | .arg("run") 115 | .arg("run") 116 | .arg(&in_file) 117 | .spawn()? 118 | .wait()? 119 | .success(); 120 | 121 | assert!(!success); 122 | Ok(()) 123 | } 124 | 125 | #[test] 126 | fn test_inline_function_error() -> Result<(), Error> { 127 | let dir = std::env::current_dir().unwrap(); 128 | 129 | let in_file = dir.join("tests/inline_function_err.sb"); 130 | let success = Command::new("cargo") 131 | .arg("run") 132 | .arg("run") 133 | .arg(&in_file) 134 | .spawn()? 135 | .wait()? 136 | .success(); 137 | 138 | assert!(!success); 139 | Ok(()) 140 | } 141 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pub mod string_util; 17 | -------------------------------------------------------------------------------- /src/util/string_util.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Garrit Franke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | use crate::lexer::Position; 17 | 18 | pub fn highlight_position_in_file(input: String, position: Position) -> String { 19 | let mut buf = String::new(); 20 | 21 | let line = input.lines().nth(position.line - 1).unwrap(); 22 | // TODO: do something better, code can be more than 9999 lines 23 | buf.push_str(&format!("{:>4} | {}\n", position.line, line)); 24 | buf.push_str(" | "); 25 | 26 | buf.push_str( 27 | &line 28 | .chars() 29 | .take(position.offset - 1) 30 | .map(|c| if c == '\t' { '\t' } else { ' ' }) 31 | .collect::(), 32 | ); 33 | buf.push('^'); 34 | 35 | buf 36 | } 37 | -------------------------------------------------------------------------------- /tests/arrays.sb: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let foo: int[5] 3 | 4 | foo[0] = 1 5 | foo[1] = 2 6 | foo[2] = 3 7 | foo[3] = 4 8 | foo[4] = 5 9 | 10 | println(foo[0]) 11 | println(foo[4]) 12 | } -------------------------------------------------------------------------------- /tests/conditionals.sb: -------------------------------------------------------------------------------- 1 | fn conditionals_main() { 2 | log_test_stage("Testing conditionals") 3 | test_conditionals_basics() 4 | test_conditionals_multiple_arms() 5 | 6 | test_basic_match() 7 | test_boolean_match() 8 | test_match_with_block_statement() 9 | } 10 | 11 | fn test_conditionals_basics() { 12 | let number = 3 13 | 14 | if number < 5 { 15 | println("condition was true") 16 | } else { 17 | println("condition was false") 18 | // Should not be true 19 | } 20 | } 21 | 22 | fn test_conditionals_multiple_arms() { 23 | let number = 6 24 | 25 | if number % 4 == 0 { 26 | println("number is divisible by 4") 27 | } else if number % 3 == 0 { 28 | println("number is divisible by 3") 29 | } else if number % 2 == 0 { 30 | println("number is divisible by 2") 31 | } else { 32 | println("number is not divisible by 4, 3, or 2") 33 | } 34 | } 35 | 36 | fn test_conditionals_non_boolean_condition() { 37 | let number = 3 38 | 39 | if number { 40 | println("number was three") 41 | } else { 42 | assert(false) 43 | } 44 | } 45 | 46 | fn test_basic_match() { 47 | let x = 1 48 | 49 | match x { 50 | 1 => assert(true) 51 | 2 => assert(false) 52 | } 53 | } 54 | 55 | fn test_boolean_match() { 56 | let x = true 57 | 58 | match x { 59 | true => assert(true) 60 | false => assert(false) 61 | } 62 | } 63 | 64 | fn test_match_with_block_statement() { 65 | let x = 42 66 | 67 | match x { 68 | 1 => println("x is 1") 69 | 2 => { 70 | println("This is a branch with multiple statements.") 71 | println("x is 2, in case you are wondering") 72 | } 73 | 42 => println("The answer to the universe and everything!") 74 | else => println("Default case") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/functions.sb: -------------------------------------------------------------------------------- 1 | fn functions_main() { 2 | log_test_stage("Testing functions") 3 | test_functions_basics() 4 | } 5 | 6 | fn test_functions_basics() { 7 | let x = add_one(2) 8 | assert(x == 3) 9 | println(x) 10 | } 11 | 12 | fn add_one(x: int): int { 13 | return x + 1 14 | } -------------------------------------------------------------------------------- /tests/importable_module/foo/bar.sb: -------------------------------------------------------------------------------- 1 | import "baz" 2 | 3 | fn nested_module() { 4 | println("A deeply nested function was called!") 5 | } -------------------------------------------------------------------------------- /tests/importable_module/foo/baz/module.sb: -------------------------------------------------------------------------------- 1 | fn baz() { 2 | println("Baz was called") 3 | } -------------------------------------------------------------------------------- /tests/importable_module/module.sb: -------------------------------------------------------------------------------- 1 | import "foo/bar" 2 | 3 | fn external_function() { 4 | println("I was called!!") 5 | nested_module() 6 | } -------------------------------------------------------------------------------- /tests/imports.sb: -------------------------------------------------------------------------------- 1 | import "importable_module" 2 | 3 | fn imports_main() { 4 | log_test_stage("Testing imports") 5 | external_function() 6 | } 7 | -------------------------------------------------------------------------------- /tests/logger/module.sb: -------------------------------------------------------------------------------- 1 | fn log_test_stage(msg: string) { 2 | println("") 3 | println("-----------------------------") 4 | println("--- " + msg + " ---") 5 | println("-----------------------------") 6 | } -------------------------------------------------------------------------------- /tests/main.sb: -------------------------------------------------------------------------------- 1 | // 2 | // Main entry point for test program 3 | // 4 | 5 | import "logger" 6 | 7 | import "conditionals" 8 | import "functions" 9 | import "imports" 10 | import "numbers" 11 | import "structs" 12 | import "types" 13 | import "unicode" 14 | 15 | 16 | fn main() { 17 | log_test_stage("Running tests") 18 | 19 | conditionals_main() 20 | functions_main() 21 | imports_main() 22 | numbers_main() 23 | structs_main() 24 | types_main() 25 | unicode_main() 26 | 27 | log_test_stage("Done!") 28 | } 29 | -------------------------------------------------------------------------------- /tests/numbers.sb: -------------------------------------------------------------------------------- 1 | fn numbers_main() { 2 | log_test_stage("Testing numbers") 3 | // TODO: extract to functions 4 | 5 | // Test the "_" character 6 | let one_billion = 1_000_000_000 7 | 8 | // Test number systems 9 | let dec = 255 10 | let hex = 0xFf 11 | let binary = 0b11111111 12 | let octal = 0o377 13 | 14 | assert(dec == 255) 15 | assert(hex == 255) 16 | assert(binary == 255) 17 | assert(octal == 255) 18 | 19 | test_operators() 20 | } 21 | 22 | fn test_operators() { 23 | println("test_operators") 24 | let x = 10 25 | x += 1 26 | x -= 2 27 | x *= 2 28 | x /= 2 29 | assert(x == 9) 30 | } -------------------------------------------------------------------------------- /tests/structs.sb: -------------------------------------------------------------------------------- 1 | fn structs_main() { 2 | log_test_stage("Testing structs") 3 | test_initialization() 4 | test_simple_field_access() 5 | test_field_access_in_function_call() 6 | test_field_access_on_function() 7 | test_nested_structs() 8 | test_method_call() 9 | test_function_call_with_constructor() 10 | test_method_with_self_statement() 11 | test_nested_field_access() 12 | } 13 | 14 | struct User { 15 | username: string 16 | first_name: string 17 | last_name: string 18 | 19 | fn full_name(): string { 20 | return self.first_name + self.last_name 21 | } 22 | } 23 | 24 | // Creates a stub user 25 | fn user_stub() { 26 | let stub = new User { 27 | username: "Foo Bar" 28 | first_name: "Foo" 29 | last_name: "Bar" 30 | } 31 | 32 | assert(stub.first_name) 33 | assert(stub.last_name) 34 | return stub 35 | } 36 | 37 | fn test_initialization() { 38 | println("test_initialization") 39 | let foo = new User { 40 | username: "Foo Bar" 41 | first_name: "Bar" 42 | last_name: "Bar" 43 | } 44 | 45 | assert(foo) 46 | } 47 | 48 | fn test_simple_field_access() { 49 | let user: User = user_stub() 50 | user.username = "Foo Bar" 51 | } 52 | 53 | fn test_field_access_in_function_call() { 54 | let user: User = user_stub() 55 | user.username = "Bar" 56 | assert(user.username == "Bar") 57 | } 58 | 59 | fn test_field_access_on_function() { 60 | assert(user_stub().first_name == "Foo") 61 | } 62 | 63 | struct Foo { 64 | x: int 65 | bar: Bar 66 | } 67 | 68 | struct Bar { 69 | y: string 70 | } 71 | 72 | fn test_nested_structs() { 73 | let foo = new Foo { 74 | x: 5 75 | bar: new Bar { 76 | y: "Nested field" 77 | } 78 | } 79 | 80 | assert(foo.x == 5) 81 | println(foo.bar.y) 82 | assert(foo.bar.y == "Nested field") 83 | } 84 | 85 | fn test_method_call() { 86 | let user: User = user_stub() 87 | let full_name: string = user.full_name() 88 | 89 | assert(full_name, "FooBar") 90 | } 91 | 92 | fn assert_bar_y(bar: Bar) { 93 | assert(bar.y == "ABC") 94 | } 95 | 96 | fn test_function_call_with_constructor() { 97 | assert_bar_y(new Bar { y: "ABC" }) 98 | } 99 | 100 | struct Self_test_struct { 101 | a: int 102 | 103 | fn bar() { 104 | self.a += 1 // <-- This caused problems (See #34) 105 | assert(true) 106 | } 107 | } 108 | 109 | fn test_method_with_self_statement() { 110 | let foo = new Self_test_struct { a: 5 } 111 | foo.bar() 112 | } 113 | 114 | struct Point { 115 | x: int 116 | y: int 117 | } 118 | 119 | struct Rectangle { 120 | origin: Point 121 | width: int 122 | height: int 123 | } 124 | 125 | fn test_nested_field_access() { 126 | let rect = new Rectangle { 127 | origin: new Point { 128 | x: 10 129 | y: 20 130 | } 131 | width: 100 132 | height: 50 133 | } 134 | assert(rect.origin.x == 10) 135 | rect.origin.x += 5 136 | assert(rect.origin.x == 15) 137 | } -------------------------------------------------------------------------------- /tests/types.sb: -------------------------------------------------------------------------------- 1 | fn types_main() { 2 | log_test_stage("Testing types") 3 | print_any(5) 4 | print_any("Test") 5 | } 6 | 7 | fn print_any(x: any) { 8 | println(x) 9 | } -------------------------------------------------------------------------------- /tests/unicode.sb: -------------------------------------------------------------------------------- 1 | fn unicode_main() { 2 | log_test_stage("Testing unicode") 3 | test_unicode_strings() 4 | test_unicode_identifiers() 5 | } 6 | 7 | fn test_unicode_strings() { 8 | println("Test unicode strings") 9 | let alpha_omega = "αβ" 10 | 11 | println(alpha_omega) 12 | } 13 | 14 | fn test_unicode_identifiers() { 15 | println("Test unicode identifiers") 16 | let αβ = "αβ" 17 | let 世界 = "世界" 18 | 19 | println(世界) 20 | } 21 | 22 | --------------------------------------------------------------------------------