├── .github ├── ISSUE_TEMPLATE │ ├── a--compilation-error-or-crash.md │ ├── b--incorrect-compilation-issue.md │ ├── c--feature-request.md │ └── other-issue.md └── workflows │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── ci ├── README.md └── publish.rs ├── crates ├── ast │ ├── Cargo.toml │ └── src │ │ ├── component.rs │ │ ├── expressions.rs │ │ ├── lib.rs │ │ ├── statements.rs │ │ └── types.rs ├── codegen │ ├── Cargo.toml │ ├── allocator.wat │ ├── build.rs │ └── src │ │ ├── builders │ │ ├── component.rs │ │ ├── mod.rs │ │ └── module.rs │ │ ├── code.rs │ │ ├── expression.rs │ │ ├── function.rs │ │ ├── imports.rs │ │ ├── lib.rs │ │ ├── module.rs │ │ ├── statement.rs │ │ └── types.rs ├── common │ ├── Cargo.toml │ ├── src │ │ ├── diagnostic.rs │ │ ├── lib.rs │ │ └── stack_map.rs │ └── tests │ │ └── miette.rs ├── lib │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── tests │ │ ├── bad-programs │ │ ├── adding-conflicting-types.claw │ │ ├── adding-conflicting-types.error.txt │ │ ├── global-without-annotation.claw │ │ ├── global-without-annotation.error.txt │ │ ├── global-without-initialization.claw │ │ ├── global-without-initialization.error.txt │ │ ├── invalid-token.claw │ │ ├── invalid-token.error.txt │ │ ├── modifying-immutable-global.claw │ │ ├── modifying-immutable-global.error.txt │ │ ├── modifying-immutable-local.claw │ │ ├── modifying-immutable-local.error.txt │ │ ├── param-local-type-mismatch.claw │ │ ├── param-local-type-mismatch.error.txt │ │ ├── using-unbound-name.claw │ │ └── using-unbound-name.error.txt │ │ ├── compile-error.rs │ │ ├── programs │ │ ├── arithmetic.claw │ │ ├── compare.claw │ │ ├── counter.claw │ │ ├── factorial.claw │ │ ├── identity.claw │ │ ├── proxy_call.claw │ │ ├── quadratic.claw │ │ ├── strings.claw │ │ ├── timer-proxy.claw │ │ ├── unary.claw │ │ └── wit │ │ │ ├── claw.wit │ │ │ └── deps │ │ │ ├── clocks │ │ │ └── monotonic-clock.wit │ │ │ └── logging │ │ │ └── logging.wit │ │ └── runtime.rs ├── parser │ ├── Cargo.toml │ └── src │ │ ├── component.rs │ │ ├── expressions.rs │ │ ├── lexer.rs │ │ ├── lib.rs │ │ ├── names.rs │ │ ├── statements.rs │ │ └── types.rs └── resolver │ ├── Cargo.toml │ └── src │ ├── expression.rs │ ├── function.rs │ ├── imports.rs │ ├── lib.rs │ ├── statement.rs │ ├── types.rs │ └── wit.rs └── src └── bin.rs /.github/ISSUE_TEMPLATE/a--compilation-error-or-crash.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: A) Compilation error or crash 3 | about: Report an unexpected error or crash during compilation 4 | title: 'Compilation bug: *describe problem here*' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | **Reproduction case** 14 | The code you tried to compile or a minimal version of it that reproduces the error either inline as a snippet or as a link to a repository. 15 | 16 | **Compiler output** 17 | The full claw compiler error output. 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/b--incorrect-compilation-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: B) Incorrect compilation issue 3 | about: Report an issue with generated code 4 | title: 'Runtime bug: *describe problem here*' 5 | labels: incorrect-compilation 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | **Reproduction case** 14 | The code you compiled or a minimal version of it that reproduces the incorrect behavior you've observed either inline as a snippet or as a link to a repository. 15 | 16 | **To Reproduce** 17 | Code for reproducing the incorrect behavior. 18 | Ideally something similar to the [claw runtime tests](https://github.com/esoterra/claw-lang/blob/main/tests/runtime.rs) using Wasmtime to instantiate the component, perform calls, and assert the expected behavior. 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/c--feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: C) Feature request 3 | about: Suggest an addition to the claw compiler project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Eg. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other issue 3 | about: For questions and issues without a dedicated template (e.g. docs problems) 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Info 21 | run: | 22 | rustc --version 23 | cargo --version 24 | 25 | - name: Build 26 | run: cargo build --workspace 27 | 28 | - name: Format 29 | run: cargo fmt --check 30 | 31 | - name: Lint 32 | run: cargo clippy -- -D warnings 33 | 34 | - name: Run tests 35 | run: cargo test --workspace 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | 4 | # So that files from debugging don't get committed 5 | # Only applies to claw, wat, and wasm files in root 6 | # Not e.g. crates/codegen/allocator.wat 7 | *.wat 8 | *.wasm 9 | 10 | # The compiled publish script 11 | publish 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting Robin Brown at [me@esoterra.dev](mailto:me@esoterra.dev). Please include in your email subject "Claw" and "CoC" or "Code of Conduct". Robin will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. Robin is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | As other maintainers join the project, Robin will divest personal oversight over CoC matters to a separate CoC team and this document will be updated. If this project becomes joins an organization, it will adopt the Code of Conduct process of that organization. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: https://www.contributor-covenant.org 46 | [version]: https://www.contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Claw 2 | 3 | #### Table of Contents 4 | 5 | * [Code of Conduct](#code-of-conduct) 6 | * [Getting Started Contributing](#getting-started-contributing) 7 | 8 | ## Code of Conduct 9 | 10 | This project and everyone participating in it is governed by the [Claw Contributor Covenant Code of Conduct][CoC]. By participating, you are expected to uphold this code. Please report unacceptable behavior to me@esoterra.dev. 11 | 12 | ## Getting Started Contributing 13 | 14 | The best way to start contributing is to become a user. 15 | 16 | As a user, you can then help by 17 | 18 | 1. Noticing problems with Claw or its docs and [filing issues][issues] 19 | * Please check if they've already been filed first!! 20 | * Focus on clear bugs, well-defined problems, obvious gaps, etc. here. 21 | 2. Participating in and starting [discussions] about potential new features 22 | * Let's keep open ended and [bikeshed]-style conversations here! 23 | 24 | Once you're familiar with the project and its code, it's a good time to look for code contributions to make. 25 | 26 | Try to find an issue with maintainer (@esoterra) approval (ideally a [good first issue]) and try to implement it. 27 | 28 | When you're ready, submit a Pull Request or Draft Pull Request and we'll take a look at it! 29 | 30 | [CoC]: ./CODE_OF_CONDUCT.md 31 | [issues]: https://github.com/esoterra/claw-lang/issues 32 | [discussions]: https://github.com/esoterra/claw-lang/discussions 33 | [bikeshed]: https://bikeshed.org/ 34 | [good first issue]: https://github.com/esoterra/claw-lang/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22 35 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claw-cli" 3 | description = "The compiler for the Claw language" 4 | readme = "README.md" 5 | version = { workspace = true } 6 | authors = { workspace = true } 7 | license = { workspace = true } 8 | edition = { workspace = true } 9 | repository = { workspace = true } 10 | 11 | [[bin]] 12 | name = "claw-cli" 13 | path = "src/bin.rs" 14 | 15 | [dependencies] 16 | claw-common = { workspace = true } 17 | claw-ast = { workspace = true } 18 | claw-parser = { workspace = true } 19 | claw-resolver = { workspace = true } 20 | claw-codegen = { workspace = true } 21 | 22 | clap = { workspace = true } 23 | thiserror = { workspace = true } 24 | miette = { workspace = true } 25 | logos = { workspace = true } 26 | wasm-encoder ={ workspace = true } 27 | cranelift-entity = { workspace = true } 28 | wat = { workspace = true } 29 | wit-parser = { workspace = true } 30 | 31 | [dev-dependencies] 32 | pretty_assertions = { workspace = true } 33 | wasmtime = { workspace = true } 34 | wasmprinter = { workspace = true } 35 | 36 | [workspace] 37 | members = [ 38 | "crates/ast", 39 | "crates/codegen", 40 | "crates/common", 41 | "crates/lib", 42 | "crates/parser", 43 | "crates/resolver", 44 | ] 45 | 46 | [workspace.package] 47 | version = "0.2.6" 48 | authors = ["Robin Brown"] 49 | license = "MIT OR Apache-2.0" 50 | edition = "2018" 51 | homepage = "https://claw-lang.dev/" 52 | repository = "https://github.com/esoterra/claw-lang" 53 | 54 | [workspace.dependencies] 55 | claw-common = { path = "./crates/common", version = "0.2.6" } 56 | claw-ast = { path = "./crates/ast", version = "0.2.6" } 57 | claw-parser = { path = "./crates/parser", version = "0.2.6" } 58 | claw-resolver = { path = "./crates/resolver", version = "0.2.6" } 59 | claw-codegen = { path = "./crates/codegen", version = "0.2.6" } 60 | 61 | clap = { version = "3.0.0-rc.7", features = ["derive"] } 62 | thiserror = "1.0.30" 63 | miette = { version = "7.2.0", features = ["fancy"] } 64 | logos = "0.13.0" 65 | wasm-encoder = "0.207" 66 | cranelift-entity = "0.105.3" 67 | wat = "1.207" 68 | pretty_assertions = "1.1.0" 69 | wasmtime = "20" 70 | wasmprinter = "0.207" 71 | wit-parser = "0.207" 72 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Robin Brown 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Robin Brown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

claw-cli

3 | 4 |

5 | The compiler for the Claw programming language 6 |

7 | 8 |

9 | build status 10 | Crates.io version 11 | Download 12 | docs.rs docs 13 |

14 | 15 |

16 | build status 17 |

18 |
19 | 20 | Claw is a programming language that compiles to Wasm Components. 21 | Values in Claw have the exact same types as Component model values and the imports/exports of a Claw source file represent a Component "World". 22 | 23 | This means that there's no bindings generators or indirection required. 24 | You can receive component values as arguments, operate on them, create them, and return them. 25 | 26 | ```js 27 | let mut counter: s64 = 0; 28 | 29 | export func increment() -> s64 { 30 | counter = counter + 1; 31 | return counter; 32 | } 33 | 34 | export func decrement() -> s64 { 35 | counter = counter - 1; 36 | return counter; 37 | } 38 | ``` 39 | 40 | (support for the full range of component model values is still a WIP) 41 | 42 | ## Use Cases & Goals 43 | 44 | ### Component Testing 45 | 46 | Claw's ability to define component imports and simple logic easily will be well suited for writing Component tests. 47 | 48 | ```js 49 | import add: func(lhs: s32, rhs: s32) -> s32; 50 | 51 | export func test() -> result<(), string> { 52 | if add(1, 1) == 2 { 53 | return ok(()); 54 | } else { 55 | return err("test failed"); 56 | } 57 | } 58 | ``` 59 | 60 | By adding a `check!(...)` builtin that returns `ok(())` when the condition is true and `err("")` when its false 61 | and a Rust-style `?` early return operator, we can make writing these tests a lot easier and make the output much better. 62 | 63 | ```js 64 | import add: func(lhs: s32, rhs: s32) -> s32; 65 | 66 | export tests: interface { 67 | func test() -> result<(), string> { 68 | check!(add(1, 1) == 2)?; 69 | ... 70 | return ok(()); 71 | } 72 | 73 | ... 74 | } 75 | ``` 76 | 77 | ### Adapters & Polyfills 78 | 79 | Sometimes users will have components written for one world but want to run them in another. 80 | 81 | Claw could make it easy to write simple adapters or polyfills so that users can run their existing code more places. 82 | 83 | ### Virtualizations & Mocks 84 | 85 | With components, we can achieve an incredible local dev experience where resources like message buses and key value stores 86 | can be implemented as simple components and used to run applications for testing and development. 87 | 88 | Claw can be well suited to writing simple in-memory virtualizations that make testing and development easy. 89 | 90 | ### Extensions 91 | 92 | Some applications (e.g. [database](https://docs.singlestore.com/cloud/reference/code-engine-powered-by-wasm/create-wasm-udfs/)) 93 | can already be extended using Wasm and as this becomes more common users may want to write small pieces of logic that act as filters or policy, 94 | define how to process events, or implement missing math or domain functions. 95 | 96 | Claw can make writing these extensions easy while still generating really small Components that can stored, transmitted, and instantiated quickly. 97 | 98 | ### Simple Services 99 | 100 | TODO 101 | 102 | ## Relationship with Other Projects 103 | 104 | There are several projects for representing different aspects of the Component Model 105 | 106 | * [WIT](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md) - The official IDL for the Component Model 107 | * [WAC](https://github.com/peterhuene/wac/) - An extension of WIT that adds the ability to define how to wire components together 108 | * [WAVE](https://github.com/lann/wave) - A format for encoding Component-Model values in an idiomatic json-like way 109 | 110 | Claw will use WIT syntax for defining types, WAC syntax for defining composition, and WAVE syntax for literal expressions 111 | combining them all together so that it's intuitive to use these different tools. 112 | 113 | ![image](https://github.com/esoterra/claw-lang/assets/3458116/de0673f1-7b92-48c6-b1c3-e52479797778) 114 | 115 | -------------------------------------------------------------------------------- /ci/README.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | ``` 4 | rustc ci/publish.rs 5 | ./publish publish 6 | ``` -------------------------------------------------------------------------------- /ci/publish.rs: -------------------------------------------------------------------------------- 1 | //! Helper script to publish the warg suites of crates 2 | //! 3 | //! * `./publish bump` - bump crate versions in-tree 4 | //! * `./publish verify` - verify crates can be published to crates.io 5 | //! * `./publish publish` - actually publish crates to crates.io 6 | 7 | use std::collections::HashMap; 8 | use std::env; 9 | use std::fs; 10 | use std::path::{Path, PathBuf}; 11 | use std::process::{Command, Stdio}; 12 | use std::thread; 13 | use std::time::Duration; 14 | 15 | // note that this list must be topologically sorted by dependencies 16 | const CRATES_TO_PUBLISH: &[&str] = &[ 17 | "claw-common", 18 | "claw-ast", 19 | "claw-parser", 20 | "claw-resolver", 21 | "claw-codegen", 22 | "compile-claw", 23 | "claw-cli", 24 | ]; 25 | 26 | // Anything **not** mentioned in this array is required to have an `=a.b.c` 27 | // dependency requirement on it to enable breaking api changes even in "patch" 28 | // releases since everything not mentioned here is just an organizational detail 29 | // that no one else should rely on. 30 | const PUBLIC_CRATES: &[&str] = &[ 31 | "claw-common", 32 | "claw-ast", 33 | "claw-parser", 34 | "claw-resolver", 35 | "claw-codegen", 36 | "compile-claw", 37 | "claw-cli", 38 | ]; 39 | 40 | struct Workspace { 41 | version: String, 42 | } 43 | 44 | struct Crate { 45 | manifest: PathBuf, 46 | name: String, 47 | version: String, 48 | publish: bool, 49 | } 50 | 51 | fn main() { 52 | let mut crates = Vec::new(); 53 | let root = read_crate(None, "./Cargo.toml".as_ref()); 54 | let ws = Workspace { 55 | version: root.version.clone(), 56 | }; 57 | crates.push(root); 58 | find_crates("crates".as_ref(), &ws, &mut crates); 59 | 60 | let pos = CRATES_TO_PUBLISH 61 | .iter() 62 | .enumerate() 63 | .map(|(i, c)| (*c, i)) 64 | .collect::>(); 65 | crates.sort_by_key(|krate| pos.get(&krate.name[..])); 66 | 67 | match &env::args().nth(1).expect("must have one argument")[..] { 68 | name @ "bump" | name @ "bump-patch" => { 69 | for krate in crates.iter() { 70 | bump_version(&krate, &crates, name == "bump-patch"); 71 | } 72 | // update the lock file 73 | assert!(Command::new("cargo") 74 | .arg("fetch") 75 | .status() 76 | .unwrap() 77 | .success()); 78 | } 79 | 80 | "publish" => { 81 | // We have so many crates to publish we're frequently either 82 | // rate-limited or we run into issues where crates can't publish 83 | // successfully because they're waiting on the index entries of 84 | // previously-published crates to propagate. This means we try to 85 | // publish in a loop and we remove crates once they're successfully 86 | // published. Failed-to-publish crates get enqueued for another try 87 | // later on. 88 | for _ in 0..10 { 89 | crates.retain(|krate| !publish(krate)); 90 | 91 | if crates.is_empty() { 92 | break; 93 | } 94 | 95 | println!( 96 | "{} crates failed to publish, waiting for a bit to retry", 97 | crates.len(), 98 | ); 99 | thread::sleep(Duration::from_secs(40)); 100 | } 101 | 102 | assert!(crates.is_empty(), "failed to publish all crates"); 103 | 104 | println!(""); 105 | } 106 | 107 | "verify" => { 108 | verify(&crates); 109 | } 110 | 111 | s => panic!("unknown command: {}", s), 112 | } 113 | } 114 | 115 | fn find_crates(dir: &Path, ws: &Workspace, dst: &mut Vec) { 116 | if dir.join("Cargo.toml").exists() { 117 | let krate = read_crate(Some(ws), &dir.join("Cargo.toml")); 118 | if !krate.publish || CRATES_TO_PUBLISH.iter().any(|c| krate.name == *c) { 119 | dst.push(krate); 120 | } else { 121 | panic!("failed to find {:?} in whitelist or blacklist", krate.name); 122 | } 123 | } 124 | 125 | for entry in dir.read_dir().unwrap() { 126 | let entry = entry.unwrap(); 127 | if entry.file_type().unwrap().is_dir() { 128 | find_crates(&entry.path(), ws, dst); 129 | } 130 | } 131 | } 132 | 133 | fn read_crate(ws: Option<&Workspace>, manifest: &Path) -> Crate { 134 | let mut name = None; 135 | let mut version = None; 136 | let mut publish = true; 137 | for line in fs::read_to_string(manifest).unwrap().lines() { 138 | if name.is_none() && line.starts_with("name = \"") { 139 | name = Some( 140 | line.replace("name = \"", "") 141 | .replace("\"", "") 142 | .trim() 143 | .to_string(), 144 | ); 145 | } 146 | if version.is_none() && line.starts_with("version = \"") { 147 | version = Some( 148 | line.replace("version = \"", "") 149 | .replace("\"", "") 150 | .trim() 151 | .to_string(), 152 | ); 153 | } 154 | if let Some(ws) = ws { 155 | if version.is_none() && (line.starts_with("version.workspace = true") || line.starts_with("version = { workspace = true }")) { 156 | version = Some(ws.version.clone()); 157 | } 158 | } 159 | if line.starts_with("publish = false") { 160 | publish = false; 161 | } 162 | } 163 | let name = name.unwrap(); 164 | let version = version.unwrap(); 165 | Crate { 166 | manifest: manifest.to_path_buf(), 167 | name, 168 | version, 169 | publish, 170 | } 171 | } 172 | 173 | fn bump_version(krate: &Crate, crates: &[Crate], patch: bool) { 174 | let contents = fs::read_to_string(&krate.manifest).unwrap(); 175 | let next_version = |krate: &Crate| -> String { 176 | if CRATES_TO_PUBLISH.contains(&&krate.name[..]) { 177 | bump(&krate.version, patch) 178 | } else { 179 | krate.version.clone() 180 | } 181 | }; 182 | 183 | let mut new_manifest = String::new(); 184 | let mut is_deps = false; 185 | for line in contents.lines() { 186 | let mut rewritten = false; 187 | if !is_deps && line.starts_with("version =") { 188 | if CRATES_TO_PUBLISH.contains(&&krate.name[..]) { 189 | println!( 190 | "bump `{}` {} => {}", 191 | krate.name, 192 | krate.version, 193 | next_version(krate), 194 | ); 195 | new_manifest.push_str(&line.replace(&krate.version, &next_version(krate))); 196 | rewritten = true; 197 | } 198 | } 199 | 200 | is_deps = if line.starts_with("[") { 201 | line.contains("dependencies") 202 | } else { 203 | is_deps 204 | }; 205 | 206 | for other in crates { 207 | // If `other` isn't a published crate then it's not going to get a 208 | // bumped version so we don't need to update anything in the 209 | // manifest. 210 | if !other.publish { 211 | continue; 212 | } 213 | if !is_deps || !line.starts_with(&format!("{} ", other.name)) { 214 | continue; 215 | } 216 | if !line.contains(&other.version) { 217 | if !line.contains("version =") || !krate.publish { 218 | continue; 219 | } 220 | panic!( 221 | "{:?} has a dep on {} but doesn't list version {}", 222 | krate.manifest, other.name, other.version 223 | ); 224 | } 225 | if krate.publish { 226 | if PUBLIC_CRATES.contains(&other.name.as_str()) { 227 | assert!( 228 | !line.contains("\"="), 229 | "{} should not have an exact version requirement on {}", 230 | krate.name, 231 | other.name 232 | ); 233 | } else { 234 | assert!( 235 | line.contains("\"="), 236 | "{} should have an exact version requirement on {}", 237 | krate.name, 238 | other.name 239 | ); 240 | } 241 | } 242 | rewritten = true; 243 | new_manifest.push_str(&line.replace(&other.version, &next_version(other))); 244 | break; 245 | } 246 | if !rewritten { 247 | new_manifest.push_str(line); 248 | } 249 | new_manifest.push_str("\n"); 250 | } 251 | fs::write(&krate.manifest, new_manifest).unwrap(); 252 | } 253 | 254 | /// Performs a major version bump increment on the semver version `version`. 255 | /// 256 | /// This function will perform a semver-major-version bump on the `version` 257 | /// specified. This is used to calculate the next version of a crate in this 258 | /// repository since we're currently making major version bumps for all our 259 | /// releases. This may end up getting tweaked as we stabilize crates and start 260 | /// doing more minor/patch releases, but for now this should do the trick. 261 | fn bump(version: &str, patch_bump: bool) -> String { 262 | let mut iter = version.split('.').map(|s| s.parse::().unwrap()); 263 | let major = iter.next().expect("major version"); 264 | let minor = iter.next().expect("minor version"); 265 | let patch = iter.next().expect("patch version"); 266 | 267 | if patch_bump { 268 | return format!("{}.{}.{}", major, minor, patch + 1); 269 | } 270 | if major != 0 { 271 | format!("{}.0.0", major + 1) 272 | } else if minor != 0 { 273 | format!("0.{}.0", minor + 1) 274 | } else { 275 | format!("0.0.{}", patch + 1) 276 | } 277 | } 278 | 279 | fn publish(krate: &Crate) -> bool { 280 | if !CRATES_TO_PUBLISH.iter().any(|s| *s == krate.name) { 281 | return true; 282 | } 283 | 284 | // First make sure the crate isn't already published at this version. This 285 | // script may be re-run and there's no need to re-attempt previous work. 286 | let output = Command::new("curl") 287 | .arg(&format!("https://crates.io/api/v1/crates/{}", krate.name)) 288 | .output() 289 | .expect("failed to invoke `curl`"); 290 | if output.status.success() 291 | && String::from_utf8_lossy(&output.stdout) 292 | .contains(&format!("\"newest_version\":\"{}\"", krate.version)) 293 | { 294 | println!( 295 | "skip publish {} because {} is latest version", 296 | krate.name, krate.version, 297 | ); 298 | return true; 299 | } 300 | 301 | let status = Command::new("cargo") 302 | .arg("publish") 303 | .current_dir(krate.manifest.parent().unwrap()) 304 | .arg("--no-verify") 305 | .status() 306 | .expect("failed to run cargo"); 307 | if !status.success() { 308 | println!("FAIL: failed to publish `{}`: {}", krate.name, status); 309 | return false; 310 | } 311 | 312 | true 313 | } 314 | 315 | // Verify the current tree is publish-able to crates.io. The intention here is 316 | // that we'll run `cargo package` on everything which verifies the build as-if 317 | // it were published to crates.io. This requires using an incrementally-built 318 | // directory registry generated from `cargo vendor` because the versions 319 | // referenced from `Cargo.toml` may not exist on crates.io. 320 | fn verify(crates: &[Crate]) { 321 | drop(fs::remove_dir_all(".cargo")); 322 | drop(fs::remove_dir_all("vendor")); 323 | let vendor = Command::new("cargo") 324 | .arg("vendor") 325 | .stderr(Stdio::inherit()) 326 | .output() 327 | .unwrap(); 328 | assert!(vendor.status.success()); 329 | 330 | fs::create_dir_all(".cargo").unwrap(); 331 | fs::write(".cargo/config.toml", vendor.stdout).unwrap(); 332 | 333 | for krate in crates { 334 | if !krate.publish { 335 | continue; 336 | } 337 | verify_and_vendor(&krate); 338 | } 339 | 340 | fn verify_and_vendor(krate: &Crate) { 341 | let mut cmd = Command::new("cargo"); 342 | cmd.arg("package") 343 | .arg("--manifest-path") 344 | .arg(&krate.manifest) 345 | .env("CARGO_TARGET_DIR", "./target"); 346 | let status = cmd.status().unwrap(); 347 | assert!(status.success(), "failed to verify {:?}", &krate.manifest); 348 | let tar = Command::new("tar") 349 | .arg("xf") 350 | .arg(format!( 351 | "../target/package/{}-{}.crate", 352 | krate.name, krate.version 353 | )) 354 | .current_dir("./vendor") 355 | .status() 356 | .unwrap(); 357 | assert!(tar.success()); 358 | fs::write( 359 | format!( 360 | "./vendor/{}-{}/.cargo-checksum.json", 361 | krate.name, krate.version 362 | ), 363 | "{\"files\":{}}", 364 | ) 365 | .unwrap(); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /crates/ast/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claw-ast" 3 | description = "The Claw language Abstract Syntax Tree (AST)" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | license = { workspace = true } 7 | edition = { workspace = true } 8 | repository = { workspace = true } 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | miette = { workspace = true } 14 | claw-common = { workspace = true } 15 | cranelift-entity = { workspace = true } 16 | wit-parser = { workspace = true } 17 | -------------------------------------------------------------------------------- /crates/ast/src/component.rs: -------------------------------------------------------------------------------- 1 | //! Contains the [Component] struct which is the root 2 | //! of the AST and contains root items (e.g. import, function), 3 | //! inner AST nodes (e.g. expression), and the source code. 4 | 5 | use std::collections::HashMap; 6 | 7 | use cranelift_entity::{entity_impl, PrimaryMap}; 8 | 9 | use crate::PackageName; 10 | use claw_common::Source; 11 | 12 | use super::{ 13 | expressions::{Expression, ExpressionId}, 14 | statements::{Statement, StatementId}, 15 | types::{FnType, TypeDefId, TypeDefinition}, 16 | NameId, Span, TypeId, ValType, 17 | }; 18 | 19 | /// The unique ID of an Import item 20 | /// 21 | /// IDs must only be passed to the [Component] they were 22 | /// made by and this is not statically or dynamically validated. 23 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 24 | pub struct ImportId(u32); 25 | entity_impl!(ImportId, "import"); 26 | 27 | /// The unique ID of a Global item 28 | /// 29 | /// IDs must only be passed to the [Component] they were 30 | /// made by and this is not statically or dynamically validated. 31 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 32 | pub struct GlobalId(u32); 33 | entity_impl!(GlobalId, "global"); 34 | 35 | /// The unique ID of a Function item 36 | /// 37 | /// IDs must only be passed to the [Component] they were 38 | /// made by and this is not statically or dynamically validated. 39 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 40 | pub struct FunctionId(u32); 41 | entity_impl!(FunctionId, "func"); 42 | 43 | /// Each Claw source file represents a Component 44 | /// and this struct represents the root of the AST. 45 | /// 46 | /// The different types of AST nodes each have a unique ID type, 47 | /// so it is impossible to try to retrieve an import as a function. 48 | /// 49 | /// No static or dynamic validation that an ID is from the correct 50 | /// AST is performed and if an ID from one AST is provided to another 51 | /// bad things will happen! 52 | #[derive(Debug)] 53 | pub struct Component { 54 | /// The source text that the component was created from. 55 | src: Source, 56 | 57 | // Top level items 58 | imports: PrimaryMap, 59 | type_defs: PrimaryMap, 60 | globals: PrimaryMap, 61 | functions: PrimaryMap, 62 | 63 | // Inner items 64 | types: PrimaryMap, 65 | type_spans: HashMap, 66 | 67 | statements: PrimaryMap, 68 | statement_spans: HashMap, 69 | 70 | expressions: PrimaryMap, 71 | expression_spans: HashMap, 72 | 73 | names: PrimaryMap, 74 | name_spans: HashMap, 75 | } 76 | 77 | impl Component { 78 | /// Create a new empty Component AST for a source file. 79 | /// 80 | /// This does not do any parsing!!! 81 | pub fn new(src: Source) -> Self { 82 | Self { 83 | src, 84 | imports: Default::default(), 85 | type_defs: Default::default(), 86 | globals: Default::default(), 87 | functions: Default::default(), 88 | types: Default::default(), 89 | type_spans: Default::default(), 90 | statements: Default::default(), 91 | statement_spans: Default::default(), 92 | expressions: Default::default(), 93 | expression_spans: Default::default(), 94 | names: Default::default(), 95 | name_spans: Default::default(), 96 | } 97 | } 98 | 99 | /// The source code that the AST represents. 100 | pub fn source(&self) -> Source { 101 | self.src.clone() 102 | } 103 | 104 | /// Add a top-level import item to the AST. 105 | pub fn push_import(&mut self, import: Import) -> ImportId { 106 | self.imports.push(import) 107 | } 108 | 109 | /// Iterate over the top-level import items. 110 | pub fn iter_imports(&self) -> impl Iterator { 111 | self.imports.iter() 112 | } 113 | 114 | /// Get a specific import item by its id. 115 | pub fn get_import(&self, import: ImportId) -> &Import { 116 | &self.imports[import] 117 | } 118 | 119 | /// Add a top-level type definition item to the AST. 120 | pub fn push_type_def(&mut self, type_def: TypeDefinition) -> TypeDefId { 121 | self.type_defs.push(type_def) 122 | } 123 | 124 | /// Iterate over the top-level type definition items. 125 | pub fn iter_type_defs(&self) -> impl Iterator { 126 | self.type_defs.iter() 127 | } 128 | 129 | /// Get a specific type definition item by its id. 130 | pub fn get_type_def(&self, type_def: TypeDefId) -> &TypeDefinition { 131 | &self.type_defs[type_def] 132 | } 133 | 134 | /// Add a top-level global item to the AST. 135 | pub fn push_global(&mut self, global: Global) -> GlobalId { 136 | self.globals.push(global) 137 | } 138 | 139 | /// Iterate over the top-level global items. 140 | pub fn iter_globals(&self) -> impl Iterator { 141 | self.globals.iter() 142 | } 143 | 144 | /// Get a specific global item by its id. 145 | pub fn get_global(&self, global: GlobalId) -> &Global { 146 | &self.globals[global] 147 | } 148 | 149 | /// Add a top-level function item to the AST. 150 | pub fn push_function(&mut self, function: Function) -> FunctionId { 151 | self.functions.push(function) 152 | } 153 | 154 | /// Iterate over the top-level function items. 155 | pub fn iter_functions(&self) -> impl Iterator { 156 | self.functions.iter() 157 | } 158 | 159 | /// Get a specific function item by its id. 160 | pub fn get_function(&self, function: FunctionId) -> &Function { 161 | &self.functions[function] 162 | } 163 | 164 | /// Create a new name AST node. 165 | pub fn new_name(&mut self, name: String, span: Span) -> NameId { 166 | let id = self.names.push(name); 167 | self.name_spans.insert(id, span); 168 | id 169 | } 170 | 171 | /// Get the value of a name. 172 | pub fn get_name(&self, id: NameId) -> &str { 173 | self.names.get(id).unwrap() 174 | } 175 | 176 | /// Get the source span for this name. 177 | pub fn name_span(&self, id: NameId) -> Span { 178 | *self.name_spans.get(&id).unwrap() 179 | } 180 | 181 | /// Create a new valtype AST node. 182 | pub fn new_type(&mut self, valtype: ValType, span: Span) -> TypeId { 183 | let id = self.types.push(valtype); 184 | self.type_spans.insert(id, span); 185 | id 186 | } 187 | 188 | /// Get the value of a valtype AST node. 189 | pub fn get_type(&self, id: TypeId) -> &ValType { 190 | self.types.get(id).unwrap() 191 | } 192 | 193 | /// Get the source span for this valtype. 194 | pub fn type_span(&self, id: TypeId) -> Span { 195 | *self.type_spans.get(&id).unwrap() 196 | } 197 | 198 | /// Create a new statement AST node. 199 | pub fn new_statement(&mut self, statement: Statement, span: Span) -> StatementId { 200 | let id = self.statements.push(statement); 201 | self.statement_spans.insert(id, span); 202 | id 203 | } 204 | 205 | /// Get the value of a statement AST node. 206 | pub fn get_statement(&self, id: StatementId) -> &Statement { 207 | self.statements.get(id).unwrap() 208 | } 209 | 210 | /// Get the source span for this statement. 211 | pub fn statement_span(&self, id: StatementId) -> Span { 212 | *self.statement_spans.get(&id).unwrap() 213 | } 214 | 215 | /// Create a new expression AST node. 216 | pub fn new_expression(&mut self, expression: Expression, span: Span) -> ExpressionId { 217 | let id = self.expressions.push(expression); 218 | self.expression_spans.insert(id, span); 219 | id 220 | } 221 | 222 | /// Get the value of a expression AST node. 223 | pub fn get_expression(&self, id: ExpressionId) -> &Expression { 224 | self.expressions.get(id).unwrap() 225 | } 226 | 227 | /// Get the source span for this expression. 228 | pub fn expression_span(&self, id: ExpressionId) -> Span { 229 | *self.expression_spans.get(&id).unwrap() 230 | } 231 | } 232 | 233 | /// Import AST node (Claw) 234 | /// 235 | /// There are two versions: plain and import-from. 236 | #[derive(Debug, PartialEq, Eq, Clone)] 237 | pub enum Import { 238 | Plain(PlainImport), 239 | ImportFrom(ImportFrom), 240 | } 241 | 242 | /// Plain Import AST node (Claw) 243 | /// 244 | /// ```claw 245 | /// import foo: func() -> u32; 246 | /// ``` 247 | #[derive(Debug, PartialEq, Eq, Clone)] 248 | pub struct PlainImport { 249 | /// The name of the item to import. 250 | pub ident: NameId, 251 | /// The name given to the imported item. 252 | /// Defaults to the specified name if omitted. 253 | pub alias: Option, 254 | /// The type of the imported item. 255 | pub external_type: ExternalType, 256 | } 257 | 258 | /// Import From AST node (Claw) 259 | /// 260 | /// ```claw 261 | /// import { foo } from bar; 262 | /// ``` 263 | #[derive(Debug, PartialEq, Eq, Clone)] 264 | pub struct ImportFrom { 265 | /// The first name is the imported item's name 266 | /// The second optional name is an alias 267 | pub items: Vec<(NameId, Option)>, 268 | /// The package being imported from 269 | pub package: PackageName, 270 | /// Which interface from the package to import 271 | pub interface: String, 272 | } 273 | 274 | /// External Type AST node (Claw) 275 | /// 276 | /// ```claw 277 | /// func(foo: string) -> bool 278 | /// ``` 279 | #[derive(Debug, PartialEq, Eq, Clone)] 280 | pub enum ExternalType { 281 | Function(FnType), 282 | } 283 | 284 | /// Global Item AST node (Claw) 285 | /// 286 | /// ```claw 287 | /// let foo: u32 = 1; 288 | /// ``` 289 | #[derive(Debug, Clone)] 290 | pub struct Global { 291 | /// Whether the global is exported. 292 | /// 293 | /// Indicated by the keyword `export` in front 294 | /// of the global item. 295 | pub exported: bool, 296 | /// Whether the global is mutable. 297 | /// 298 | /// Indicated by the `mut` keyword before after `let`. 299 | pub mutable: bool, 300 | /// The name of the global. 301 | pub ident: NameId, 302 | /// The type of the global. 303 | pub type_id: TypeId, 304 | /// The initialization expression for the global. 305 | pub init_value: ExpressionId, 306 | } 307 | 308 | /// Function Item AST node (Claw) 309 | /// 310 | /// ```claw 311 | /// func always-false() -> bool { 312 | /// return false; 313 | /// } 314 | /// ``` 315 | #[derive(Debug)] 316 | pub struct Function { 317 | /// Whether the global is exported. 318 | /// 319 | /// Indicated by the keyword `export` in front 320 | /// of the function item. 321 | pub exported: bool, 322 | /// The name of the function. 323 | pub ident: NameId, 324 | /// The function's parameters. 325 | /// 326 | /// Each parameter has a name and type. 327 | pub params: Vec<(NameId, TypeId)>, 328 | /// The result type of the function. 329 | /// 330 | /// Result type is unit if omitted. 331 | pub results: Option, 332 | /// The body of the function. 333 | pub body: Vec, 334 | } 335 | -------------------------------------------------------------------------------- /crates/ast/src/expressions.rs: -------------------------------------------------------------------------------- 1 | use super::NameId; 2 | use cranelift_entity::entity_impl; 3 | 4 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 5 | pub struct ExpressionId(u32); 6 | entity_impl!(ExpressionId, "expression"); 7 | 8 | pub trait ContextEq { 9 | fn context_eq(&self, other: &Self, context: &Context) -> bool; 10 | } 11 | 12 | #[derive(Debug, PartialEq, Clone)] 13 | pub enum Expression { 14 | Identifier(Identifier), 15 | Enum(EnumLiteral), 16 | Literal(Literal), 17 | Call(Call), 18 | Unary(UnaryExpression), 19 | Binary(BinaryExpression), 20 | } 21 | 22 | impl ContextEq for ExpressionId { 23 | fn context_eq(&self, other: &Self, context: &super::Component) -> bool { 24 | let self_span = context.expression_span(*self); 25 | let other_span = context.expression_span(*other); 26 | if self_span != other_span { 27 | dbg!(self_span, other_span); 28 | return false; 29 | } 30 | 31 | let self_expr = context.get_expression(*self); 32 | let other_expr = context.get_expression(*other); 33 | if !self_expr.context_eq(other_expr, context) { 34 | dbg!(self_expr, other_expr); 35 | return false; 36 | } 37 | true 38 | } 39 | } 40 | 41 | impl ContextEq for Expression { 42 | fn context_eq(&self, other: &Self, context: &super::Component) -> bool { 43 | match (self, other) { 44 | (Expression::Identifier(left), Expression::Identifier(right)) => { 45 | left.context_eq(right, context) 46 | } 47 | (Expression::Literal(left), Expression::Literal(right)) => { 48 | left.context_eq(right, context) 49 | } 50 | (Expression::Call(left), Expression::Call(right)) => left.context_eq(right, context), 51 | (Expression::Unary(left), Expression::Unary(right)) => left.context_eq(right, context), 52 | (Expression::Binary(left), Expression::Binary(right)) => { 53 | left.context_eq(right, context) 54 | } 55 | _ => false, 56 | } 57 | } 58 | } 59 | 60 | #[derive(Debug, PartialEq, Clone)] 61 | pub struct Identifier { 62 | pub ident: NameId, 63 | } 64 | 65 | impl From for Expression { 66 | fn from(val: Identifier) -> Self { 67 | Expression::Identifier(val) 68 | } 69 | } 70 | 71 | impl ContextEq for Identifier { 72 | fn context_eq(&self, other: &Self, context: &super::Component) -> bool { 73 | context.get_name(self.ident) == context.get_name(other.ident) 74 | } 75 | } 76 | 77 | #[derive(Debug, PartialEq, Clone)] 78 | pub struct EnumLiteral { 79 | pub enum_name: NameId, 80 | pub case_name: NameId, 81 | } 82 | 83 | impl From for Expression { 84 | fn from(val: EnumLiteral) -> Self { 85 | Expression::Enum(val) 86 | } 87 | } 88 | 89 | impl ContextEq for EnumLiteral { 90 | fn context_eq(&self, other: &Self, context: &super::Component) -> bool { 91 | context.get_name(self.enum_name) == context.get_name(other.enum_name) 92 | && context.get_name(self.case_name) == context.get_name(other.case_name) 93 | } 94 | } 95 | 96 | #[derive(Debug, PartialEq, Clone)] 97 | pub enum Literal { 98 | Integer(u64), 99 | Float(f64), 100 | String(String), 101 | } 102 | 103 | impl From for Expression { 104 | fn from(val: Literal) -> Self { 105 | Expression::Literal(val) 106 | } 107 | } 108 | 109 | impl ContextEq for Literal { 110 | fn context_eq(&self, other: &Self, _context: &super::Component) -> bool { 111 | self == other 112 | } 113 | } 114 | 115 | #[derive(Debug, PartialEq, Clone)] 116 | pub struct Call { 117 | pub ident: NameId, 118 | pub args: Vec, 119 | } 120 | 121 | impl From for Expression { 122 | fn from(val: Call) -> Self { 123 | Expression::Call(val) 124 | } 125 | } 126 | 127 | impl ContextEq for Call { 128 | fn context_eq(&self, other: &Self, context: &super::Component) -> bool { 129 | let ident_eq = self.ident.context_eq(&other.ident, context); 130 | let args_eq = self 131 | .args 132 | .iter() 133 | .zip(other.args.iter()) 134 | .map(|(l, r)| l.context_eq(r, context)) 135 | .all(|v| v); 136 | 137 | ident_eq && args_eq 138 | } 139 | } 140 | 141 | // Unary Operators 142 | 143 | #[derive(Debug, PartialEq, Clone, Copy)] 144 | pub enum UnaryOp { 145 | Negate, 146 | } 147 | 148 | #[derive(Debug, PartialEq, Clone)] 149 | pub struct UnaryExpression { 150 | pub op: UnaryOp, 151 | pub inner: ExpressionId, 152 | } 153 | 154 | impl From for Expression { 155 | fn from(val: UnaryExpression) -> Self { 156 | Expression::Unary(val) 157 | } 158 | } 159 | 160 | impl ContextEq for UnaryExpression { 161 | fn context_eq(&self, other: &Self, context: &super::Component) -> bool { 162 | let self_inner = context.get_expression(self.inner); 163 | let other_inner = context.get_expression(other.inner); 164 | self_inner.context_eq(other_inner, context) 165 | } 166 | } 167 | 168 | // Binary Operators 169 | 170 | #[derive(Debug, PartialEq, Clone, Copy)] 171 | pub enum BinaryOp { 172 | // Arithmetic Operations 173 | Multiply, 174 | Divide, 175 | Modulo, 176 | Add, 177 | Subtract, 178 | 179 | // Shifting Operations 180 | BitShiftL, 181 | BitShiftR, 182 | ArithShiftR, 183 | 184 | // Comparisons 185 | LessThan, 186 | LessThanEqual, 187 | GreaterThan, 188 | GreaterThanEqual, 189 | Equals, 190 | NotEquals, 191 | 192 | // Bitwise Operations 193 | BitOr, 194 | BitXor, 195 | BitAnd, 196 | 197 | // Logical Operations 198 | LogicalOr, 199 | LogicalAnd, 200 | } 201 | 202 | #[derive(Debug, PartialEq, Clone)] 203 | pub struct BinaryExpression { 204 | pub op: BinaryOp, 205 | pub left: ExpressionId, 206 | pub right: ExpressionId, 207 | } 208 | 209 | impl From for Expression { 210 | fn from(val: BinaryExpression) -> Self { 211 | Expression::Binary(val) 212 | } 213 | } 214 | 215 | impl ContextEq for BinaryExpression { 216 | fn context_eq(&self, other: &Self, context: &super::Component) -> bool { 217 | let self_left = context.get_expression(self.left); 218 | let other_left = context.get_expression(other.left); 219 | let left_eq = self_left.context_eq(other_left, context); 220 | 221 | let self_right = context.get_expression(self.right); 222 | let other_right = context.get_expression(other.right); 223 | let right_eq = self_right.context_eq(other_right, context); 224 | 225 | left_eq && right_eq 226 | } 227 | } 228 | 229 | impl BinaryExpression { 230 | pub fn is_relation(&self) -> bool { 231 | use BinaryOp as BE; 232 | matches!( 233 | self.op, 234 | BE::LessThan 235 | | BE::LessThanEqual 236 | | BE::GreaterThan 237 | | BE::GreaterThanEqual 238 | | BE::Equals 239 | | BE::NotEquals 240 | ) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /crates/ast/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod component; 2 | pub mod expressions; 3 | pub mod statements; 4 | pub mod types; 5 | 6 | use cranelift_entity::entity_impl; 7 | use miette::SourceSpan; 8 | 9 | pub use wit_parser::PackageName; 10 | 11 | pub type Span = SourceSpan; 12 | 13 | pub use component::*; 14 | pub use expressions::*; 15 | pub use statements::*; 16 | pub use types::*; 17 | 18 | pub fn merge(left: &Span, right: &Span) -> Span { 19 | let left_most = left.offset(); 20 | let right_most = right.offset() + right.len(); 21 | let len = right_most - left_most; 22 | Span::from((left_most, len)) 23 | } 24 | 25 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 26 | pub struct NameId(u32); 27 | entity_impl!(NameId, "name"); 28 | 29 | impl ContextEq for NameId { 30 | fn context_eq(&self, other: &Self, context: &Component) -> bool { 31 | let self_str = context.get_name(*self); 32 | let other_str = context.get_name(*other); 33 | let str_eq = self_str == other_str; 34 | 35 | let self_span = context.name_span(*self); 36 | let other_span = context.name_span(*other); 37 | let span_eq = self_span == other_span; 38 | 39 | str_eq && span_eq 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/ast/src/statements.rs: -------------------------------------------------------------------------------- 1 | use cranelift_entity::entity_impl; 2 | 3 | use super::{expressions::ExpressionId, types::TypeId, Call, NameId}; 4 | 5 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 6 | pub struct StatementId(u32); 7 | entity_impl!(StatementId, "name"); 8 | 9 | #[derive(Debug, PartialEq, Clone)] 10 | pub enum Statement { 11 | Let(Let), 12 | Assign(Assign), 13 | Call(Call), 14 | If(If), 15 | Return(Return), 16 | } 17 | 18 | #[derive(Debug, PartialEq, Clone)] 19 | pub struct Let { 20 | pub mutable: bool, 21 | pub ident: NameId, 22 | pub annotation: Option, 23 | pub expression: ExpressionId, 24 | } 25 | 26 | #[derive(Debug, PartialEq, Clone)] 27 | pub struct Assign { 28 | pub ident: NameId, 29 | pub expression: ExpressionId, 30 | } 31 | 32 | #[derive(Debug, PartialEq, Clone)] 33 | pub struct If { 34 | pub condition: ExpressionId, 35 | pub block: Vec, 36 | } 37 | 38 | #[derive(Debug, PartialEq, Clone)] 39 | pub struct Return { 40 | pub expression: Option, 41 | } 42 | -------------------------------------------------------------------------------- /crates/ast/src/types.rs: -------------------------------------------------------------------------------- 1 | use cranelift_entity::entity_impl; 2 | 3 | use super::{Component, NameId}; 4 | 5 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 6 | pub struct TypeId(u32); 7 | entity_impl!(TypeId, "type"); 8 | 9 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 10 | pub struct TypeDefId(u32); 11 | entity_impl!(TypeDefId, "typedef"); 12 | 13 | /// The type for all values 14 | #[derive(Debug, Hash, Clone)] 15 | pub enum ValType { 16 | Result(ResultType), 17 | Primitive(PrimitiveType), 18 | } 19 | 20 | #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] 21 | pub enum PrimitiveType { 22 | // The boolean type 23 | Bool, 24 | // 8-bit Integers 25 | U8, 26 | S8, 27 | // 16-bit Integers 28 | U16, 29 | S16, 30 | // 32-bit Integers 31 | U32, 32 | S32, 33 | // 64-bit Integers 34 | U64, 35 | S64, 36 | // Floating Point Numbers 37 | F32, 38 | F64, 39 | // String type 40 | String, 41 | } 42 | 43 | #[derive(Debug, Hash, Clone)] 44 | pub struct ResultType { 45 | pub ok: TypeId, 46 | pub err: TypeId, 47 | } 48 | 49 | impl ValType { 50 | pub fn eq(&self, other: &Self, comp: &Component) -> bool { 51 | match (self, other) { 52 | (ValType::Result(left), ValType::Result(right)) => { 53 | let l_ok = comp.get_type(left.ok); 54 | let r_ok = comp.get_type(right.ok); 55 | let ok_eq = l_ok.eq(r_ok, comp); 56 | 57 | let l_err = comp.get_type(left.err); 58 | let r_err = comp.get_type(right.err); 59 | let err_eq = l_err.eq(r_err, comp); 60 | 61 | ok_eq && err_eq 62 | } 63 | (ValType::Primitive(left), ValType::Primitive(right)) => left == right, 64 | _ => false, 65 | } 66 | } 67 | } 68 | 69 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 70 | pub enum TypeDefinition { 71 | Record(RecordTypeDef), 72 | } 73 | 74 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 75 | pub struct RecordTypeDef { 76 | fields: Vec<(NameId, TypeId)>, 77 | } 78 | 79 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 80 | pub struct FnType { 81 | pub params: Vec<(NameId, TypeId)>, 82 | pub results: Option, 83 | } 84 | -------------------------------------------------------------------------------- /crates/codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claw-codegen" 3 | description = "The Claw language Wasm code generator" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | license = { workspace = true } 7 | edition = { workspace = true } 8 | repository = { workspace = true } 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | miette = { workspace = true } 14 | thiserror = { workspace = true } 15 | claw-ast = { workspace = true } 16 | claw-resolver = { workspace = true } 17 | wasm-encoder = { workspace = true } 18 | cranelift-entity = { workspace = true } 19 | wat = { workspace = true } 20 | 21 | [build-dependencies] 22 | wat = { workspace = true } 23 | -------------------------------------------------------------------------------- /crates/codegen/allocator.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory $memory (export "memory") 1) 3 | (global $last (mut i32) (i32.const 8)) 4 | (func $realloc (export "realloc") 5 | (param $old_ptr i32) 6 | (param $old_size i32) 7 | (param $align i32) 8 | (param $new_size i32) 9 | (result i32) 10 | (local $ret i32) 11 | ;; Test if the old pointer is non-null 12 | local.get $old_ptr 13 | if 14 | ;; If the old size is bigger than the new size then 15 | ;; this is a shrink and transparently allow it 16 | local.get $old_size 17 | local.get $new_size 18 | i32.gt_u 19 | if 20 | local.get $old_ptr 21 | return 22 | end 23 | ;; otherwise fall through to allocate a new chunk which will later 24 | ;; copy data over 25 | end 26 | ;; align up `$last` 27 | (global.set $last 28 | (i32.and 29 | (i32.add 30 | (global.get $last) 31 | (i32.add 32 | (local.get $align) 33 | (i32.const -1))) 34 | (i32.xor 35 | (i32.add 36 | (local.get $align) 37 | (i32.const -1)) 38 | (i32.const -1)))) 39 | ;; save the current value of `$last` as the return value 40 | global.get $last 41 | local.set $ret 42 | ;; bump our pointer 43 | (global.set $last 44 | (i32.add 45 | (global.get $last) 46 | (local.get $new_size))) 47 | ;; while `memory.size` is less than `$last`, grow memory 48 | ;; by one page 49 | (loop $loop 50 | (if 51 | (i32.lt_u 52 | (i32.mul (memory.size) (i32.const 65536)) 53 | (global.get $last)) 54 | (then 55 | i32.const 1 56 | memory.grow 57 | ;; test to make sure growth succeeded 58 | i32.const -1 59 | i32.eq 60 | if unreachable end 61 | br $loop))) 62 | ;; ensure anything necessary is set to valid data by spraying a bit 63 | ;; pattern that is invalid 64 | local.get $ret 65 | i32.const 0xde 66 | local.get $new_size 67 | memory.fill 68 | ;; If the old pointer is present then that means this was a reallocation 69 | ;; of an existing chunk which means the existing data must be copied. 70 | local.get $old_ptr 71 | if 72 | local.get $ret ;; destination 73 | local.get $old_ptr ;; source 74 | local.get $old_size ;; size 75 | memory.copy 76 | end 77 | local.get $ret 78 | ) 79 | (func $clear (export "clear") 80 | i32.const 8 81 | global.set $last 82 | ) 83 | ) -------------------------------------------------------------------------------- /crates/codegen/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, path::Path}; 2 | 3 | fn main() { 4 | let out_dir = env::var_os("OUT_DIR").unwrap(); 5 | let dest_path = Path::new(&out_dir).join("allocator.wasm"); 6 | 7 | let wat = include_str!("./allocator.wat"); 8 | let wasm = wat::parse_str(wat).unwrap(); 9 | 10 | fs::write(dest_path, wasm).unwrap(); 11 | println!("cargo:rerun-if-changed=build.rs"); 12 | println!("cargo:rerun-if-changed=allocator.wat"); 13 | } 14 | -------------------------------------------------------------------------------- /crates/codegen/src/builders/component.rs: -------------------------------------------------------------------------------- 1 | use wasm_encoder as enc; 2 | 3 | #[derive(Default)] 4 | pub struct ComponentBuilder { 5 | component: enc::Component, 6 | 7 | num_types: u32, 8 | num_funcs: u32, 9 | num_core_funcs: u32, 10 | num_core_mems: u32, 11 | num_modules: u32, 12 | num_module_instances: u32, 13 | num_instances: u32, 14 | } 15 | 16 | #[derive(Clone, Copy, Debug)] 17 | pub struct ComponentInstanceIndex(u32); 18 | 19 | #[derive(Clone, Copy, Debug)] 20 | pub struct ComponentModuleIndex(u32); 21 | 22 | #[derive(Clone, Copy, Debug)] 23 | pub struct ComponentModuleInstanceIndex(u32); 24 | 25 | #[derive(Clone, Copy, Debug)] 26 | pub struct ComponentTypeIndex(u32); 27 | 28 | #[derive(Clone, Copy, Debug)] 29 | pub struct ComponentFunctionIndex(u32); 30 | 31 | #[derive(Clone, Copy, Debug)] 32 | pub struct ComponentCoreFunctionIndex(u32); 33 | 34 | #[derive(Clone, Copy, Debug)] 35 | pub struct ComponentCoreMemoryIndex(u32); 36 | 37 | pub enum InlineExportItem { 38 | Func(ComponentCoreFunctionIndex), 39 | } 40 | 41 | pub enum ModuleInstantiateArgs { 42 | Instance(ComponentModuleInstanceIndex), 43 | } 44 | 45 | impl ComponentBuilder { 46 | pub fn module(&mut self, module: enc::Module) -> ComponentModuleIndex { 47 | self.component.section(&enc::ModuleSection(&module)); 48 | self.next_mod_idx() 49 | } 50 | 51 | pub fn module_bytes(&mut self, bytes: &[u8]) -> ComponentModuleIndex { 52 | self.component.section(&enc::RawSection { 53 | id: enc::ComponentSectionId::CoreModule.into(), 54 | data: bytes, 55 | }); 56 | self.next_mod_idx() 57 | } 58 | 59 | #[allow(dead_code)] 60 | pub fn inline_export( 61 | &mut self, 62 | exports: &[(String, InlineExportItem)], 63 | ) -> ComponentModuleInstanceIndex { 64 | let exports: Vec<(String, enc::ExportKind, u32)> = exports 65 | .iter() 66 | .map(|(name, arg)| match arg { 67 | InlineExportItem::Func(func) => (name.to_owned(), enc::ExportKind::Func, func.0), 68 | }) 69 | .collect(); 70 | let mut section = enc::InstanceSection::new(); 71 | section.export_items(exports); 72 | self.component.section(§ion); 73 | self.next_mod_instance_idx() 74 | } 75 | 76 | pub fn instantiate( 77 | &mut self, 78 | module: ComponentModuleIndex, 79 | args: Vec<(S, ModuleInstantiateArgs)>, 80 | ) -> ComponentModuleInstanceIndex 81 | where 82 | S: AsRef, 83 | { 84 | let args: Vec<_> = args 85 | .into_iter() 86 | .map(|(name, arg)| match arg { 87 | ModuleInstantiateArgs::Instance(instance) => { 88 | (name, enc::ModuleArg::Instance(instance.0)) 89 | } 90 | }) 91 | .collect(); 92 | let mut section = enc::InstanceSection::new(); 93 | section.instantiate(module.0, args); 94 | self.component.section(§ion); 95 | self.next_mod_instance_idx() 96 | } 97 | 98 | pub fn func_type<'b, P>( 99 | &mut self, 100 | params: P, 101 | results: Option, 102 | ) -> ComponentTypeIndex 103 | where 104 | P: IntoIterator, 105 | P::IntoIter: ExactSizeIterator, 106 | { 107 | let mut section = enc::ComponentTypeSection::new(); 108 | let mut builder = section.function(); 109 | builder.params(params); 110 | match results { 111 | Some(return_type) => { 112 | builder.result(return_type); 113 | } 114 | None => { 115 | builder.results([] as [(&str, enc::ComponentValType); 0]); 116 | } 117 | } 118 | self.component.section(§ion); 119 | self.next_type_idx() 120 | } 121 | 122 | pub fn instance_type(&mut self, instance_type: &enc::InstanceType) -> ComponentTypeIndex { 123 | let mut section = enc::ComponentTypeSection::new(); 124 | section.instance(instance_type); 125 | self.component.section(§ion); 126 | self.next_type_idx() 127 | } 128 | 129 | pub fn import_func( 130 | &mut self, 131 | name: &str, 132 | fn_type: ComponentTypeIndex, 133 | ) -> ComponentFunctionIndex { 134 | let mut section = enc::ComponentImportSection::new(); 135 | let ty = enc::ComponentTypeRef::Func(fn_type.0); 136 | section.import(name, ty); 137 | self.component.section(§ion); 138 | self.next_func_idx() 139 | } 140 | 141 | pub fn import_instance( 142 | &mut self, 143 | name: &str, 144 | instance_type_index: ComponentTypeIndex, 145 | ) -> ComponentInstanceIndex { 146 | let mut section = enc::ComponentImportSection::new(); 147 | let ty = enc::ComponentTypeRef::Instance(instance_type_index.0); 148 | section.import(name, ty); 149 | self.component.section(§ion); 150 | self.next_instance_idx() 151 | } 152 | 153 | pub fn lower_func( 154 | &mut self, 155 | func: ComponentFunctionIndex, 156 | memory: ComponentCoreMemoryIndex, 157 | realloc: ComponentCoreFunctionIndex, 158 | ) -> ComponentCoreFunctionIndex { 159 | let options: [enc::CanonicalOption; 2] = [ 160 | enc::CanonicalOption::Memory(memory.0), 161 | enc::CanonicalOption::Realloc(realloc.0), 162 | ]; 163 | let mut section = enc::CanonicalFunctionSection::new(); 164 | section.lower(func.0, options); 165 | self.component.section(§ion); 166 | self.next_core_func_idx() 167 | } 168 | 169 | pub fn alias_memory( 170 | &mut self, 171 | instance: ComponentModuleInstanceIndex, 172 | name: &str, 173 | ) -> ComponentCoreMemoryIndex { 174 | let mut section = enc::ComponentAliasSection::new(); 175 | section.alias(enc::Alias::CoreInstanceExport { 176 | instance: instance.0, 177 | kind: enc::ExportKind::Memory, 178 | name, 179 | }); 180 | self.component.section(§ion); 181 | self.next_core_memory_idx() 182 | } 183 | 184 | pub fn alias_core_func( 185 | &mut self, 186 | instance: ComponentModuleInstanceIndex, 187 | name: &str, 188 | ) -> ComponentCoreFunctionIndex { 189 | let mut section = enc::ComponentAliasSection::new(); 190 | section.alias(enc::Alias::CoreInstanceExport { 191 | instance: instance.0, 192 | kind: enc::ExportKind::Func, 193 | name, 194 | }); 195 | self.component.section(§ion); 196 | self.next_core_func_idx() 197 | } 198 | 199 | pub fn alias_func( 200 | &mut self, 201 | instance: ComponentInstanceIndex, 202 | name: &str, 203 | ) -> ComponentFunctionIndex { 204 | let mut section = enc::ComponentAliasSection::new(); 205 | section.alias(enc::Alias::InstanceExport { 206 | instance: instance.0, 207 | kind: enc::ComponentExportKind::Func, 208 | name, 209 | }); 210 | self.component.section(§ion); 211 | self.next_func_idx() 212 | } 213 | 214 | pub fn lift_func( 215 | &mut self, 216 | func: ComponentCoreFunctionIndex, 217 | fn_type: ComponentTypeIndex, 218 | memory: ComponentCoreMemoryIndex, 219 | realloc: ComponentCoreFunctionIndex, 220 | post_return: ComponentCoreFunctionIndex, 221 | ) -> ComponentFunctionIndex { 222 | let mut section = enc::CanonicalFunctionSection::new(); 223 | let options: [enc::CanonicalOption; 3] = [ 224 | enc::CanonicalOption::Memory(memory.0), 225 | enc::CanonicalOption::Realloc(realloc.0), 226 | enc::CanonicalOption::PostReturn(post_return.0), 227 | ]; 228 | section.lift(func.0, fn_type.0, options); 229 | self.component.section(§ion); 230 | self.next_func_idx() 231 | } 232 | 233 | pub fn export_func( 234 | &mut self, 235 | name: &str, 236 | func: ComponentFunctionIndex, 237 | fn_type: ComponentTypeIndex, 238 | ) -> ComponentFunctionIndex { 239 | let mut section = enc::ComponentExportSection::new(); 240 | section.export( 241 | name, 242 | enc::ComponentExportKind::Func, 243 | func.0, 244 | Some(enc::ComponentTypeRef::Func(fn_type.0)), 245 | ); 246 | self.component.section(§ion); 247 | self.next_func_idx() 248 | } 249 | 250 | pub fn finalize(self) -> enc::Component { 251 | self.component 252 | } 253 | 254 | fn next_mod_idx(&mut self) -> ComponentModuleIndex { 255 | let index = ComponentModuleIndex(self.num_modules); 256 | self.num_modules += 1; 257 | index 258 | } 259 | 260 | fn next_mod_instance_idx(&mut self) -> ComponentModuleInstanceIndex { 261 | let index = ComponentModuleInstanceIndex(self.num_module_instances); 262 | self.num_module_instances += 1; 263 | index 264 | } 265 | 266 | fn next_type_idx(&mut self) -> ComponentTypeIndex { 267 | let index = ComponentTypeIndex(self.num_types); 268 | self.num_types += 1; 269 | index 270 | } 271 | 272 | fn next_func_idx(&mut self) -> ComponentFunctionIndex { 273 | let index = ComponentFunctionIndex(self.num_funcs); 274 | self.num_funcs += 1; 275 | index 276 | } 277 | 278 | fn next_instance_idx(&mut self) -> ComponentInstanceIndex { 279 | let index = ComponentInstanceIndex(self.num_instances); 280 | self.num_instances += 1; 281 | index 282 | } 283 | 284 | fn next_core_func_idx(&mut self) -> ComponentCoreFunctionIndex { 285 | let index = ComponentCoreFunctionIndex(self.num_core_funcs); 286 | self.num_core_funcs += 1; 287 | index 288 | } 289 | 290 | fn next_core_memory_idx(&mut self) -> ComponentCoreMemoryIndex { 291 | let index = ComponentCoreMemoryIndex(self.num_core_mems); 292 | self.num_core_mems += 1; 293 | index 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /crates/codegen/src/builders/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod component; 2 | pub mod module; 3 | -------------------------------------------------------------------------------- /crates/codegen/src/builders/module.rs: -------------------------------------------------------------------------------- 1 | use wasm_encoder as enc; 2 | 3 | #[derive(Default)] 4 | pub struct ModuleBuilder { 5 | types: enc::TypeSection, 6 | imports: enc::ImportSection, 7 | funcs: enc::FunctionSection, 8 | globals: enc::GlobalSection, 9 | exports: enc::ExportSection, 10 | data: enc::DataSection, 11 | 12 | code: Vec>, 13 | 14 | num_types: u32, 15 | num_funcs: u32, 16 | num_memories: u32, 17 | num_globals: u32, 18 | num_data: u32, 19 | } 20 | 21 | #[derive(Clone, Copy, Debug)] 22 | pub struct ModuleTypeIndex(u32); 23 | 24 | #[derive(Clone, Copy, Debug)] 25 | pub struct ModuleFunctionIndex(u32); 26 | 27 | #[allow(dead_code)] 28 | #[derive(Clone, Copy, Debug)] 29 | pub struct ModuleMemoryIndex(u32); 30 | 31 | #[allow(dead_code)] 32 | #[derive(Clone, Copy, Debug)] 33 | pub struct ModuleGlobalIndex(u32); 34 | 35 | #[derive(Clone, Copy, Debug)] 36 | pub struct ModuleDataIndex(u32); 37 | 38 | impl From for u32 { 39 | fn from(value: ModuleFunctionIndex) -> Self { 40 | value.0 41 | } 42 | } 43 | 44 | impl From for u32 { 45 | fn from(value: ModuleDataIndex) -> Self { 46 | value.0 47 | } 48 | } 49 | 50 | impl ModuleBuilder { 51 | pub fn func_type(&mut self, params: P, results: R) -> ModuleTypeIndex 52 | where 53 | P: IntoIterator, 54 | P::IntoIter: ExactSizeIterator, 55 | R: IntoIterator, 56 | R::IntoIter: ExactSizeIterator, 57 | { 58 | self.types.function(params, results); 59 | self.next_type_idx() 60 | } 61 | 62 | pub fn import_memory(&mut self, module: &str, field: &str) -> ModuleMemoryIndex { 63 | let mem_type = enc::MemoryType { 64 | minimum: 1, 65 | maximum: None, 66 | memory64: false, 67 | shared: false, 68 | page_size_log2: None, 69 | }; 70 | let mem_ty = enc::EntityType::Memory(mem_type); 71 | self.imports.import(module, field, mem_ty); 72 | 73 | self.next_memory_idx() 74 | } 75 | 76 | pub fn import_func( 77 | &mut self, 78 | module: &str, 79 | field: &str, 80 | fn_type: ModuleTypeIndex, 81 | ) -> ModuleFunctionIndex { 82 | let fn_type = enc::EntityType::Function(fn_type.0); 83 | self.imports.import(module, field, fn_type); 84 | self.code.push(None); 85 | self.next_func_idx() 86 | } 87 | 88 | pub fn function(&mut self, fn_type: ModuleTypeIndex) -> ModuleFunctionIndex { 89 | self.funcs.function(fn_type.0); 90 | self.code.push(None); 91 | self.next_func_idx() 92 | } 93 | 94 | pub fn code(&mut self, func: ModuleFunctionIndex, code: enc::Function) { 95 | let index = func.0 as usize; 96 | match self.code[index] { 97 | Some(_) => panic!("Code for function {} already provided", index), 98 | None => { 99 | self.code[index] = Some(code); 100 | } 101 | } 102 | } 103 | 104 | pub fn global( 105 | &mut self, 106 | mutable: bool, 107 | valtype: enc::ValType, 108 | init_expr: &enc::ConstExpr, 109 | ) -> ModuleGlobalIndex { 110 | let global_type = enc::GlobalType { 111 | mutable, 112 | shared: false, 113 | val_type: valtype, 114 | }; 115 | self.globals.global(global_type, init_expr); 116 | self.next_global_idx() 117 | } 118 | 119 | pub fn export_func(&mut self, name: &str, func: ModuleFunctionIndex) { 120 | self.exports.export(name, enc::ExportKind::Func, func.0); 121 | } 122 | 123 | pub fn data(&mut self, data: &[u8]) -> ModuleDataIndex { 124 | self.data.passive(data.iter().copied()); 125 | self.next_data_idx() 126 | } 127 | 128 | pub fn finalize(self) -> enc::Module { 129 | let mut module = enc::Module::new(); 130 | module.section(&self.types); 131 | module.section(&self.imports); 132 | module.section(&self.funcs); 133 | module.section(&self.globals); 134 | module.section(&self.exports); 135 | 136 | if self.num_data > 0 { 137 | module.section(&enc::DataCountSection { 138 | count: self.num_data, 139 | }); 140 | } 141 | 142 | // Encode code sections 143 | let mut code = enc::CodeSection::new(); 144 | for func in self.code.into_iter() { 145 | match func { 146 | Some(func) => { 147 | code.function(&func); 148 | } 149 | None => {} 150 | } 151 | } 152 | module.section(&code); 153 | if self.num_data > 0 { 154 | module.section(&self.data); 155 | } 156 | 157 | module 158 | } 159 | 160 | fn next_type_idx(&mut self) -> ModuleTypeIndex { 161 | let index = ModuleTypeIndex(self.num_types); 162 | self.num_types += 1; 163 | index 164 | } 165 | 166 | fn next_func_idx(&mut self) -> ModuleFunctionIndex { 167 | let index = ModuleFunctionIndex(self.num_funcs); 168 | self.num_funcs += 1; 169 | index 170 | } 171 | 172 | fn next_memory_idx(&mut self) -> ModuleMemoryIndex { 173 | let index = ModuleMemoryIndex(self.num_memories); 174 | self.num_memories += 1; 175 | index 176 | } 177 | 178 | fn next_global_idx(&mut self) -> ModuleGlobalIndex { 179 | let index = ModuleGlobalIndex(self.num_globals); 180 | self.num_globals += 1; 181 | index 182 | } 183 | 184 | fn next_data_idx(&mut self) -> ModuleDataIndex { 185 | let index = ModuleDataIndex(self.num_data); 186 | self.num_data += 1; 187 | index 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /crates/codegen/src/function.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use claw_ast as ast; 4 | use claw_ast::FunctionId; 5 | use claw_resolver::{types::ResolvedType, ResolvedComponent}; 6 | use wasm_encoder as enc; 7 | 8 | use crate::{ 9 | builders::{ 10 | component::{ComponentBuilder, ComponentTypeIndex}, 11 | module::{ModuleBuilder, ModuleTypeIndex}, 12 | }, 13 | types::{align_to, EncodeType}, 14 | GenerationError, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, 15 | }; 16 | 17 | pub struct FunctionEncoder<'gen> { 18 | comp: &'gen ast::Component, 19 | rcomp: &'gen ResolvedComponent, 20 | 21 | funcs: HashMap, 22 | } 23 | 24 | pub struct EncodedFuncs { 25 | pub funcs: HashMap, 26 | } 27 | 28 | pub struct EncodedFunction { 29 | pub spill_params: Option, 30 | pub params: Vec, 31 | pub flat_params: Vec, 32 | pub results: Option, 33 | } 34 | 35 | pub struct SpilledParams { 36 | pub size: u32, 37 | pub align: u32, 38 | } 39 | 40 | pub struct ParamInfo { 41 | pub name: String, 42 | pub rtype: ResolvedType, 43 | pub index_offset: u32, 44 | pub mem_offset: u32, 45 | } 46 | 47 | impl<'gen> FunctionEncoder<'gen> { 48 | pub fn new(comp: &'gen ast::Component, rcomp: &'gen ResolvedComponent) -> Self { 49 | let funcs = HashMap::new(); 50 | 51 | Self { comp, rcomp, funcs } 52 | } 53 | 54 | pub fn encode(mut self) -> Result { 55 | // Encode function 56 | for (id, function) in self.comp.iter_functions() { 57 | let func = self.encode_func(function)?; 58 | self.funcs.insert(id, func); 59 | } 60 | 61 | Ok(EncodedFuncs { funcs: self.funcs }) 62 | } 63 | 64 | fn encode_func( 65 | &mut self, 66 | function: &ast::Function, 67 | ) -> Result { 68 | let params = function 69 | .params 70 | .iter() 71 | .map(|(name, type_id)| { 72 | let name = self.comp.get_name(*name).to_owned(); 73 | let rtype = ResolvedType::Defined(*type_id); 74 | (name, rtype) 75 | }) 76 | .collect(); 77 | let results = function.results.map(ResolvedType::Defined); 78 | 79 | let func = EncodedFunction::new(params, results, self.comp, self.rcomp); 80 | Ok(func) 81 | } 82 | } 83 | 84 | impl EncodedFunction { 85 | pub fn new( 86 | params: Vec<(String, ResolvedType)>, 87 | results: Option, 88 | comp: &ast::Component, 89 | rcomp: &ResolvedComponent, 90 | ) -> Self { 91 | // Layout parameters 92 | let (spill_params, params, flat_params) = prepare_params(params, comp, rcomp); 93 | // Layout return types 94 | let results = results.map(|results| ResultsInfo { 95 | rtype: results, 96 | spill: ResultSpillInfo::new(results, comp, rcomp), 97 | }); 98 | 99 | Self { 100 | spill_params, 101 | params, 102 | flat_params, 103 | results, 104 | } 105 | } 106 | 107 | #[allow(dead_code)] 108 | pub fn encode_comp_type( 109 | &self, 110 | builder: &mut ComponentBuilder, 111 | comp: &ast::Component, 112 | rcomp: &ResolvedComponent, 113 | ) -> ComponentTypeIndex { 114 | let params = self 115 | .params 116 | .iter() 117 | .map(|info| (info.name.as_str(), info.rtype.to_comp_valtype(comp, rcomp))); 118 | let result = self 119 | .results 120 | .as_ref() 121 | .map(|info| info.rtype.to_comp_valtype(comp, rcomp)); 122 | builder.func_type(params, result) 123 | } 124 | 125 | pub fn encode_mod_type(&self, builder: &mut ModuleBuilder) -> ModuleTypeIndex { 126 | let params = self.flat_params.iter().copied(); 127 | match self.results.as_ref().map(|info| &info.spill) { 128 | Some(ResultSpillInfo::Flat { valtype }) => builder.func_type(params, [*valtype]), 129 | Some(ResultSpillInfo::Spilled) => builder.func_type(params, [enc::ValType::I32]), 130 | None => builder.func_type(params, []), 131 | } 132 | } 133 | } 134 | 135 | fn prepare_params( 136 | params: Vec<(String, ResolvedType)>, 137 | comp: &ast::Component, 138 | rcomp: &ResolvedComponent, 139 | ) -> (Option, Vec, Vec) { 140 | // Flatten parameters 141 | let mut flat_params = Vec::new(); 142 | let mut mem_offset = 0; 143 | let mut align = 0; 144 | let mut param_info = Vec::new(); 145 | 146 | for (name, rtype) in params { 147 | let index_offset = flat_params.len() as u32; 148 | let alignment = rtype.align(comp, rcomp); 149 | mem_offset = align_to(mem_offset, alignment); 150 | align = std::cmp::max(align, alignment); 151 | let info = ParamInfo { 152 | name, 153 | rtype, 154 | index_offset, 155 | mem_offset, 156 | }; 157 | param_info.push(info); 158 | rtype.append_flattened(comp, rcomp, &mut flat_params); 159 | mem_offset += rtype.mem_size(comp, rcomp); 160 | } 161 | // Either generate as locals or spill to memory based on flattened size 162 | let spill = flat_params.len() > MAX_FLAT_PARAMS as usize; 163 | let spill = if spill { 164 | flat_params.clear(); 165 | flat_params.push(enc::ValType::I32); 166 | let size = mem_offset; 167 | Some(SpilledParams { size, align }) 168 | } else { 169 | None 170 | }; 171 | (spill, param_info, flat_params) 172 | } 173 | 174 | pub struct ResultsInfo { 175 | pub rtype: ResolvedType, 176 | pub spill: ResultSpillInfo, 177 | } 178 | 179 | impl ResultsInfo { 180 | pub fn spill(&self) -> bool { 181 | self.spill.spill() 182 | } 183 | } 184 | 185 | pub enum ResultSpillInfo { 186 | Flat { valtype: enc::ValType }, 187 | Spilled, 188 | } 189 | 190 | impl ResultSpillInfo { 191 | pub fn new(rtype: ResolvedType, comp: &ast::Component, rcomp: &ResolvedComponent) -> Self { 192 | if rtype.flat_size(comp, rcomp) > MAX_FLAT_RESULTS as u32 { 193 | ResultSpillInfo::Spilled 194 | } else { 195 | let result_types = rtype.flatten(comp, rcomp); 196 | assert_eq!(result_types.len(), 1); 197 | let valtype = result_types[0]; 198 | ResultSpillInfo::Flat { valtype } 199 | } 200 | } 201 | 202 | pub fn spill(&self) -> bool { 203 | matches!(self, ResultSpillInfo::Spilled) 204 | } 205 | 206 | pub fn valtype(&self) -> enc::ValType { 207 | match self { 208 | ResultSpillInfo::Flat { valtype } => *valtype, 209 | ResultSpillInfo::Spilled => enc::ValType::I32, 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /crates/codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::single_match)] 2 | 3 | mod builders; 4 | mod code; 5 | mod expression; 6 | mod function; 7 | mod imports; 8 | mod module; 9 | mod statement; 10 | mod types; 11 | 12 | use builders::component::*; 13 | 14 | use claw_ast as ast; 15 | use claw_resolver::{ResolvedComponent, ResolverError}; 16 | use miette::Diagnostic; 17 | use thiserror::Error; 18 | use types::EncodeType; 19 | 20 | #[derive(Error, Debug, Diagnostic)] 21 | pub enum GenerationError { 22 | #[error(transparent)] 23 | #[diagnostic(transparent)] 24 | Resolver(#[from] ResolverError), 25 | } 26 | 27 | pub const MAX_FLAT_PARAMS: u8 = 16; 28 | pub const MAX_FLAT_RESULTS: u8 = 1; 29 | 30 | pub fn generate( 31 | comp: &ast::Component, 32 | rcomp: &ResolvedComponent, 33 | ) -> Result, GenerationError> { 34 | let builder = generate_component(comp, rcomp)?; 35 | Ok(builder.finalize().finish()) 36 | } 37 | 38 | fn generate_component( 39 | comp: &ast::Component, 40 | rcomp: &ResolvedComponent, 41 | ) -> Result { 42 | let mut builder = ComponentBuilder::default(); 43 | 44 | let alloc_module = builder.module_bytes(gen_allocator()); 45 | 46 | let args: Vec<(&str, ModuleInstantiateArgs)> = vec![]; 47 | let alloc_instance = builder.instantiate(alloc_module, args); 48 | 49 | let memory = builder.alias_memory(alloc_instance, "memory"); 50 | let realloc = builder.alias_core_func(alloc_instance, "realloc"); 51 | 52 | let import_encoder = imports::ImportEncoder::new(&mut builder, comp, rcomp, memory, realloc); 53 | let imports = import_encoder.encode()?; 54 | 55 | let function_encoder = function::FunctionEncoder::new(comp, rcomp); 56 | let functions = function_encoder.encode()?; 57 | 58 | let code_module = builder.module(module::generate(comp, rcomp, &imports, &functions)?); 59 | 60 | let args = vec![ 61 | ("alloc", ModuleInstantiateArgs::Instance(alloc_instance)), 62 | ( 63 | "claw", 64 | ModuleInstantiateArgs::Instance(imports.imports_instance), 65 | ), 66 | ]; 67 | let code_instance = builder.instantiate(code_module, args); 68 | 69 | generate_exports(comp, rcomp, code_instance, memory, realloc, &mut builder)?; 70 | 71 | Ok(builder) 72 | } 73 | 74 | struct ExportGenerator<'ctx> { 75 | comp: &'ctx ast::Component, 76 | rcomp: &'ctx ResolvedComponent, 77 | 78 | code_instance: ComponentModuleInstanceIndex, 79 | memory: ComponentCoreMemoryIndex, 80 | realloc: ComponentCoreFunctionIndex, 81 | } 82 | 83 | impl<'ctx> ExportGenerator<'ctx> { 84 | fn generate(&mut self, builder: &mut ComponentBuilder) -> Result<(), GenerationError> { 85 | for (_, function) in self.comp.iter_functions() { 86 | if function.exported { 87 | self.generate_function_export(function, builder)?; 88 | } 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | fn generate_function_export( 95 | &mut self, 96 | function: &ast::Function, 97 | builder: &mut ComponentBuilder, 98 | ) -> Result<(), GenerationError> { 99 | let name = self.comp.get_name(function.ident); 100 | // Alias module instance export into component 101 | let core_func_idx = builder.alias_core_func(self.code_instance, name); 102 | // Alias the post return 103 | let post_return_idx = 104 | builder.alias_core_func(self.code_instance, format!("{}_post_return", name).as_str()); 105 | 106 | // Encode component func type 107 | let params = function.params.iter().map(|(param_name, param_type)| { 108 | let param_name = self.comp.get_name(*param_name); 109 | let param_type = self.comp.get_type(*param_type); 110 | let param_type = match param_type { 111 | ast::ValType::Result(_) => todo!(), 112 | ast::ValType::Primitive(ptype) => ptype.to_comp_valtype(self.comp, self.rcomp), 113 | }; 114 | (param_name, param_type) 115 | }); 116 | let results = function.results.map(|result_type| { 117 | let result_type = self.comp.get_type(result_type); 118 | match result_type { 119 | ast::ValType::Result(_) => todo!(), 120 | ast::ValType::Primitive(ptype) => ptype.to_comp_valtype(self.comp, self.rcomp), 121 | } 122 | }); 123 | let type_idx = builder.func_type(params, results); 124 | 125 | // Lift aliased function to component function 126 | let func_idx = builder.lift_func( 127 | core_func_idx, 128 | type_idx, 129 | self.memory, 130 | self.realloc, 131 | post_return_idx, 132 | ); 133 | // Export component function 134 | builder.export_func(name, func_idx, type_idx); 135 | 136 | Ok(()) 137 | } 138 | } 139 | 140 | fn generate_exports( 141 | comp: &ast::Component, 142 | rcomp: &ResolvedComponent, 143 | code_instance: ComponentModuleInstanceIndex, 144 | memory: ComponentCoreMemoryIndex, 145 | realloc: ComponentCoreFunctionIndex, 146 | builder: &mut ComponentBuilder, 147 | ) -> Result<(), GenerationError> { 148 | let mut gen = ExportGenerator { 149 | comp, 150 | rcomp, 151 | code_instance, 152 | memory, 153 | realloc, 154 | }; 155 | gen.generate(builder) 156 | } 157 | 158 | // ValType 159 | 160 | pub fn gen_allocator() -> &'static [u8] { 161 | let allocator_wasm = include_bytes!(concat!(env!("OUT_DIR"), "/allocator.wasm")); 162 | allocator_wasm 163 | } 164 | -------------------------------------------------------------------------------- /crates/codegen/src/module.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use ast::{FunctionId, NameId}; 4 | use claw_ast as ast; 5 | use claw_resolver::{ImportFuncId, ImportFunction, ResolvedComponent}; 6 | use wasm_encoder as enc; 7 | 8 | use crate::{ 9 | builders::module::*, 10 | code::CodeGenerator, 11 | function::{EncodedFuncs, EncodedFunction}, 12 | imports::{EncodedImportFunc, EncodedImports}, 13 | types::EncodeType, 14 | GenerationError, 15 | }; 16 | 17 | pub(crate) fn generate( 18 | comp: &ast::Component, 19 | rcomp: &ResolvedComponent, 20 | imports: &EncodedImports, 21 | functions: &EncodedFuncs, 22 | ) -> Result { 23 | ModuleGenerator::new(comp, rcomp, imports, functions).generate() 24 | } 25 | 26 | pub struct ModuleGenerator<'gen> { 27 | pub comp: &'gen ast::Component, 28 | pub rcomp: &'gen ResolvedComponent, 29 | imports: &'gen EncodedImports, 30 | functions: &'gen EncodedFuncs, 31 | pub module: ModuleBuilder, 32 | 33 | func_idx_for_import: HashMap, 34 | func_idx_for_func: HashMap, 35 | } 36 | 37 | impl<'gen> ModuleGenerator<'gen> { 38 | fn new( 39 | comp: &'gen ast::Component, 40 | rcomp: &'gen ResolvedComponent, 41 | imports: &'gen EncodedImports, 42 | functions: &'gen EncodedFuncs, 43 | ) -> Self { 44 | Self { 45 | comp, 46 | rcomp, 47 | imports, 48 | functions, 49 | module: Default::default(), 50 | func_idx_for_import: Default::default(), 51 | func_idx_for_func: Default::default(), 52 | } 53 | } 54 | 55 | pub fn generate(mut self) -> Result { 56 | // There is only ever one memory, memory zero 57 | let (_memory, realloc, clear) = self.encode_import_allocator(); 58 | 59 | for (id, import_func) in self.rcomp.imports.funcs.iter() { 60 | let encoded_import_func = self.imports.funcs.get(&id).unwrap(); 61 | let func_idx = self.encode_import_func(import_func, encoded_import_func); 62 | self.func_idx_for_import.insert(id, func_idx); 63 | } 64 | 65 | self.encode_globals()?; 66 | 67 | // Encode functions 68 | for (id, function) in self.comp.iter_functions() { 69 | let encoded_func = self.functions.funcs.get(&id).unwrap(); 70 | let func_idx = self.encode_func(function, encoded_func)?; 71 | self.func_idx_for_func.insert(id, func_idx); 72 | } 73 | // Encode function code 74 | for (id, encoded_func) in self.functions.funcs.iter() { 75 | let id = *id; 76 | let code_gen = CodeGenerator::new( 77 | &mut self.module, 78 | self.comp, 79 | self.rcomp, 80 | self.imports, 81 | self.functions, 82 | &self.func_idx_for_import, 83 | &self.func_idx_for_func, 84 | encoded_func, 85 | id, 86 | realloc, 87 | )?; 88 | let builder = code_gen.finalize()?; 89 | let mod_func_idx = self.func_idx_for_func[&id]; 90 | self.module.code(mod_func_idx, builder); 91 | } 92 | 93 | // Encode post returns 94 | for (id, function) in self.comp.iter_functions() { 95 | // Encode function 96 | let ident = function.ident; 97 | let encoded_func = self.functions.funcs.get(&id).unwrap(); 98 | let post_return = self.encode_post_return_func(ident, encoded_func)?; 99 | // Encode code 100 | let mut builder = enc::Function::new(vec![]); 101 | builder.instruction(&enc::Instruction::Call(clear.into())); 102 | builder.instruction(&enc::Instruction::End); 103 | self.module.code(post_return, builder); 104 | } 105 | 106 | Ok(self.module.finalize()) 107 | } 108 | 109 | fn encode_import_allocator( 110 | &mut self, 111 | ) -> (ModuleMemoryIndex, ModuleFunctionIndex, ModuleFunctionIndex) { 112 | let memory: ModuleMemoryIndex = self.module.import_memory("alloc", "memory"); 113 | 114 | let realloc_type = self 115 | .module 116 | .func_type(vec![enc::ValType::I32; 4], vec![enc::ValType::I32; 1]); 117 | let realloc = self.module.import_func("alloc", "realloc", realloc_type); 118 | 119 | let clear_type = self.module.func_type(vec![], vec![]); 120 | let clear = self.module.import_func("alloc", "clear", clear_type); 121 | 122 | (memory, realloc, clear) 123 | } 124 | 125 | fn encode_import_func( 126 | &mut self, 127 | import_func: &ImportFunction, 128 | encoded_import_func: &EncodedImportFunc, 129 | ) -> ModuleFunctionIndex { 130 | let type_idx = encoded_import_func.encode_mod_type(&mut self.module); 131 | let import_alias = import_func.alias.as_str(); 132 | self.module.import_func("claw", import_alias, type_idx) 133 | } 134 | 135 | fn encode_globals(&mut self) -> Result<(), GenerationError> { 136 | for (id, global) in self.comp.iter_globals() { 137 | let valtypes = global.type_id.flatten(self.comp, self.rcomp); 138 | assert_eq!(valtypes.len(), 1, "Cannot use non-primitive globals"); 139 | let valtype = valtypes[0]; 140 | 141 | let init_expr = if let Some(init_value) = self.rcomp.global_vals.get(&id) { 142 | let valtype = self.comp.get_type(global.type_id); 143 | match valtype { 144 | ast::ValType::Result(_) => todo!(), 145 | ast::ValType::Primitive(ptype) => literal_to_const_expr(init_value, *ptype), 146 | } 147 | } else { 148 | panic!("Cannot generate WASM for unresolved global") 149 | }; 150 | 151 | self.module.global(global.mutable, valtype, &init_expr); 152 | } 153 | Ok(()) 154 | } 155 | 156 | fn encode_post_return_func( 157 | &mut self, 158 | ident: NameId, 159 | encoded_func: &EncodedFunction, 160 | ) -> Result { 161 | let return_type = &encoded_func.results; 162 | let type_idx = match return_type { 163 | Some(info) => self.module.func_type([info.spill.valtype()], []), 164 | None => self.module.func_type([], []), 165 | }; 166 | let func_idx = self.module.function(type_idx); 167 | 168 | let name = self.comp.get_name(ident); 169 | let name = format!("{}_post_return", name); 170 | self.module.export_func(name.as_str(), func_idx); 171 | 172 | Ok(func_idx) 173 | } 174 | 175 | fn encode_func( 176 | &mut self, 177 | function: &ast::Function, 178 | encoded_func: &EncodedFunction, 179 | ) -> Result { 180 | let type_idx = encoded_func.encode_mod_type(&mut self.module); 181 | let func_idx = self.module.function(type_idx); 182 | 183 | if function.exported { 184 | let ident = function.ident; 185 | let name = self.comp.get_name(ident); 186 | // Export function from module 187 | self.module.export_func(name, func_idx); 188 | } 189 | 190 | Ok(func_idx) 191 | } 192 | } 193 | 194 | // Literal 195 | 196 | fn literal_to_const_expr(literal: &ast::Literal, ptype: ast::PrimitiveType) -> enc::ConstExpr { 197 | use ast::{Literal, PrimitiveType}; 198 | match (ptype, literal) { 199 | (PrimitiveType::S32 | PrimitiveType::U32, Literal::Integer(value)) => { 200 | enc::ConstExpr::i32_const(*value as i32) 201 | } 202 | (PrimitiveType::S64 | PrimitiveType::U64, Literal::Integer(value)) => { 203 | enc::ConstExpr::i64_const(*value as i64) 204 | } 205 | (PrimitiveType::F32, Literal::Float(value)) => enc::ConstExpr::f32_const(*value as f32), 206 | (PrimitiveType::F64, Literal::Float(value)) => enc::ConstExpr::f64_const(*value), 207 | _ => todo!(), 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /crates/codegen/src/statement.rs: -------------------------------------------------------------------------------- 1 | use crate::code::{CodeGenerator, ExpressionAllocator}; 2 | 3 | use super::GenerationError; 4 | use ast::{ExpressionId, NameId, Statement}; 5 | use claw_ast as ast; 6 | use claw_resolver::ItemId; 7 | 8 | use cranelift_entity::EntityRef; 9 | use wasm_encoder as enc; 10 | use wasm_encoder::Instruction; 11 | 12 | pub trait EncodeStatement { 13 | fn alloc_expr_locals(&self, allocator: &mut ExpressionAllocator) 14 | -> Result<(), GenerationError>; 15 | 16 | fn encode(&self, code_gen: &mut CodeGenerator) -> Result<(), GenerationError>; 17 | } 18 | 19 | impl EncodeStatement for Statement { 20 | fn alloc_expr_locals( 21 | &self, 22 | allocator: &mut ExpressionAllocator, 23 | ) -> Result<(), GenerationError> { 24 | let statement: &dyn EncodeStatement = match self { 25 | Statement::Let(statement) => statement, 26 | Statement::Assign(statement) => statement, 27 | Statement::Call(statement) => statement, 28 | Statement::If(statement) => statement, 29 | Statement::Return(statement) => statement, 30 | }; 31 | statement.alloc_expr_locals(allocator) 32 | } 33 | 34 | fn encode(&self, code_gen: &mut CodeGenerator) -> Result<(), GenerationError> { 35 | let statement: &dyn EncodeStatement = match self { 36 | Statement::Let(statement) => statement, 37 | Statement::Assign(statement) => statement, 38 | Statement::Call(statement) => statement, 39 | Statement::If(statement) => statement, 40 | Statement::Return(statement) => statement, 41 | }; 42 | statement.encode(code_gen) 43 | } 44 | } 45 | 46 | impl EncodeStatement for ast::Let { 47 | fn alloc_expr_locals( 48 | &self, 49 | allocator: &mut ExpressionAllocator, 50 | ) -> Result<(), GenerationError> { 51 | allocator.alloc_child(self.expression) 52 | } 53 | 54 | fn encode(&self, code_gen: &mut CodeGenerator) -> Result<(), GenerationError> { 55 | encode_assignment(self.ident, self.expression, code_gen) 56 | } 57 | } 58 | 59 | impl EncodeStatement for ast::Assign { 60 | fn alloc_expr_locals( 61 | &self, 62 | allocator: &mut ExpressionAllocator, 63 | ) -> Result<(), GenerationError> { 64 | allocator.alloc_child(self.expression) 65 | } 66 | 67 | fn encode(&self, code_gen: &mut CodeGenerator) -> Result<(), GenerationError> { 68 | encode_assignment(self.ident, self.expression, code_gen) 69 | } 70 | } 71 | 72 | impl EncodeStatement for ast::Call { 73 | fn alloc_expr_locals( 74 | &self, 75 | allocator: &mut ExpressionAllocator, 76 | ) -> Result<(), GenerationError> { 77 | for arg in self.args.iter() { 78 | allocator.alloc_child(*arg)?; 79 | } 80 | Ok(()) 81 | } 82 | 83 | fn encode(&self, code_gen: &mut CodeGenerator) -> Result<(), GenerationError> { 84 | for arg in self.args.iter() { 85 | code_gen.encode_child(*arg)?; 86 | } 87 | let item = code_gen.lookup_name(self.ident); 88 | code_gen.encode_call(item, &self.args, None)?; 89 | Ok(()) 90 | } 91 | } 92 | 93 | impl EncodeStatement for ast::If { 94 | fn alloc_expr_locals( 95 | &self, 96 | allocator: &mut ExpressionAllocator, 97 | ) -> Result<(), GenerationError> { 98 | allocator.alloc_child(self.condition)?; 99 | for statement in self.block.iter() { 100 | allocator.alloc_statement(*statement)?; 101 | } 102 | Ok(()) 103 | } 104 | 105 | fn encode(&self, code_gen: &mut CodeGenerator) -> Result<(), GenerationError> { 106 | code_gen.encode_child(self.condition)?; 107 | let fields = code_gen.fields(self.condition)?; 108 | assert_eq!(fields.len(), 1); 109 | code_gen.read_expr_field(self.condition, &fields[0]); 110 | code_gen.instruction(&Instruction::If(enc::BlockType::Empty)); 111 | for statement in self.block.iter() { 112 | code_gen.encode_statement(*statement)?; 113 | } 114 | code_gen.instruction(&Instruction::End); 115 | Ok(()) 116 | } 117 | } 118 | 119 | impl EncodeStatement for ast::Return { 120 | fn alloc_expr_locals( 121 | &self, 122 | allocator: &mut ExpressionAllocator, 123 | ) -> Result<(), GenerationError> { 124 | if let Some(expression) = self.expression { 125 | allocator.alloc_child(expression)?; 126 | } 127 | Ok(()) 128 | } 129 | 130 | fn encode(&self, code_gen: &mut CodeGenerator) -> Result<(), GenerationError> { 131 | if let Some(expression) = self.expression { 132 | code_gen.encode_child(expression)?; 133 | 134 | let fields = code_gen.fields(expression)?; 135 | if code_gen.spill_return() { 136 | for field in fields.iter() { 137 | code_gen.read_return_ptr()?; 138 | code_gen.field_address(field); 139 | code_gen.read_expr_field(expression, field); 140 | code_gen.write_mem(field); 141 | } 142 | code_gen.read_return_ptr()?; 143 | } else { 144 | for field in fields.iter() { 145 | code_gen.read_expr_field(expression, field); 146 | } 147 | } 148 | } 149 | code_gen.instruction(&Instruction::Return); 150 | Ok(()) 151 | } 152 | } 153 | 154 | fn encode_assignment( 155 | ident: NameId, 156 | expression: ExpressionId, 157 | code_gen: &mut CodeGenerator, 158 | ) -> Result<(), GenerationError> { 159 | code_gen.encode_child(expression)?; 160 | let fields = code_gen.fields(expression)?; 161 | match code_gen.lookup_name(ident) { 162 | ItemId::ImportFunc(_) => panic!("Assigning to imported function isn't allowed!!"), 163 | ItemId::Type(_) => panic!("Assigning to imported type isn't allowed!!"), 164 | ItemId::Global(global) => { 165 | // TODO handle composite globals 166 | for field in fields { 167 | code_gen.read_expr_field(expression, &field); 168 | code_gen.instruction(&Instruction::GlobalSet(global.index() as u32)); 169 | } 170 | } 171 | ItemId::Param(_) => panic!("Assigning to parameters isn't allowed!!"), 172 | ItemId::Local(local) => { 173 | for field in fields { 174 | code_gen.read_expr_field(expression, &field); 175 | code_gen.write_local_field(local, &field); 176 | } 177 | } 178 | ItemId::Function(_) => panic!("Assigning to functions isn't allowed!!"), 179 | } 180 | Ok(()) 181 | } 182 | -------------------------------------------------------------------------------- /crates/common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claw-common" 3 | description = "The Claw language common types and functions" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | license = { workspace = true } 7 | edition = { workspace = true } 8 | repository = { workspace = true } 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | miette = { workspace = true } 14 | thiserror = { workspace = true } 15 | -------------------------------------------------------------------------------- /crates/common/src/diagnostic.rs: -------------------------------------------------------------------------------- 1 | use miette::{Diagnostic, Report}; 2 | 3 | pub trait UnwrapPretty { 4 | type Output; 5 | 6 | fn unwrap_pretty(self) -> Self::Output; 7 | } 8 | 9 | impl UnwrapPretty for Result 10 | where 11 | E: Diagnostic + Sync + Send + 'static, 12 | { 13 | type Output = T; 14 | 15 | fn unwrap_pretty(self) -> Self::Output { 16 | match self { 17 | Ok(output) => output, 18 | Err(diagnostic) => { 19 | panic!("{:?}", Report::new(diagnostic)); 20 | } 21 | } 22 | } 23 | } 24 | 25 | pub trait OkPretty { 26 | type Output; 27 | 28 | fn ok_pretty(self) -> Option; 29 | } 30 | 31 | impl OkPretty for Result 32 | where 33 | E: Diagnostic + Sync + Send + 'static, 34 | { 35 | type Output = T; 36 | 37 | fn ok_pretty(self) -> Option { 38 | match self { 39 | Ok(output) => Some(output), 40 | Err(diagnostic) => { 41 | println!("{:?}", Report::new(diagnostic)); 42 | None 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/common/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod diagnostic; 2 | mod stack_map; 3 | 4 | use miette::NamedSource; 5 | use std::sync::Arc; 6 | 7 | pub use diagnostic::*; 8 | pub use stack_map::*; 9 | 10 | pub type Source = Arc>; 11 | 12 | pub fn make_source(name: &str, source: &str) -> Source { 13 | Arc::new(NamedSource::new(name, source.to_owned())) 14 | } 15 | -------------------------------------------------------------------------------- /crates/common/src/stack_map.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::collections::HashMap; 3 | use std::hash::Hash; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct StackMap { 7 | mapping: HashMap, 8 | history: Vec<(K, Option)>, 9 | } 10 | 11 | impl std::default::Default for StackMap { 12 | fn default() -> Self { 13 | Self { 14 | mapping: Default::default(), 15 | history: Default::default(), 16 | } 17 | } 18 | } 19 | 20 | pub struct StackMapCheckpoint(usize); 21 | 22 | impl From> for StackMap { 23 | fn from(value: HashMap) -> Self { 24 | Self { 25 | mapping: value, 26 | history: Default::default(), 27 | } 28 | } 29 | } 30 | 31 | impl StackMap 32 | where 33 | K: Clone + cmp::Eq + Hash, 34 | V: Clone, 35 | { 36 | pub fn insert(&mut self, key: K, value: V) { 37 | let previous = self.mapping.insert(key.clone(), value); 38 | self.history.push((key, previous)); 39 | } 40 | 41 | pub fn checkpoint(&self) -> StackMapCheckpoint { 42 | StackMapCheckpoint(self.history.len()) 43 | } 44 | 45 | pub fn restore(&mut self, checkpoint: StackMapCheckpoint) { 46 | while self.history.len() > checkpoint.0 { 47 | self.pop(); 48 | } 49 | } 50 | 51 | fn pop(&mut self) { 52 | if let Some((key, value)) = self.history.pop() { 53 | if let Some(value) = value { 54 | self.mapping.insert(key, value); 55 | } else { 56 | self.mapping.remove(&key); 57 | } 58 | } else { 59 | panic!("pop called more than push"); 60 | } 61 | } 62 | 63 | pub fn lookup(&self, key: &K) -> Option<&V> { 64 | self.mapping.get(key) 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::StackMap; 71 | 72 | #[test] 73 | fn test_checkpoint_restore() { 74 | let mut map = StackMap::default(); 75 | map.insert("a", 1); 76 | map.insert("b", 2); 77 | map.insert("c", 3); 78 | assert_eq!(map.lookup(&"a"), Some(&1)); 79 | assert_eq!(map.lookup(&"b"), Some(&2)); 80 | assert_eq!(map.lookup(&"c"), Some(&3)); 81 | let checkpoint1 = map.checkpoint(); 82 | 83 | map.insert("a", 4); 84 | map.insert("b", 5); 85 | map.insert("c", 6); 86 | assert_eq!(map.lookup(&"a"), Some(&4)); 87 | assert_eq!(map.lookup(&"b"), Some(&5)); 88 | assert_eq!(map.lookup(&"c"), Some(&6)); 89 | let checkpoint2 = map.checkpoint(); 90 | 91 | map.insert("a", 7); 92 | map.insert("b", 8); 93 | map.insert("c", 9); 94 | assert_eq!(map.lookup(&"a"), Some(&7)); 95 | assert_eq!(map.lookup(&"b"), Some(&8)); 96 | assert_eq!(map.lookup(&"c"), Some(&9)); 97 | 98 | map.restore(checkpoint2); 99 | assert_eq!(map.lookup(&"a"), Some(&4)); 100 | assert_eq!(map.lookup(&"b"), Some(&5)); 101 | assert_eq!(map.lookup(&"c"), Some(&6)); 102 | 103 | map.restore(checkpoint1); 104 | assert_eq!(map.lookup(&"a"), Some(&1)); 105 | assert_eq!(map.lookup(&"b"), Some(&2)); 106 | assert_eq!(map.lookup(&"c"), Some(&3)); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /crates/common/tests/miette.rs: -------------------------------------------------------------------------------- 1 | use miette::{Diagnostic, NamedSource, Report, SourceSpan}; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug, Diagnostic)] 5 | #[error("oops!")] 6 | struct StructError { 7 | // The Source that we're gonna be printing snippets out of. 8 | // This can be a String if you don't have or care about file names. 9 | #[source_code] 10 | src: NamedSource, 11 | // Snippets and highlights can be included in the diagnostic! 12 | #[label("This bit here")] 13 | bad_bit: SourceSpan, 14 | } 15 | 16 | #[derive(Error, Debug, Diagnostic)] 17 | enum EnumError { 18 | #[error("oops!")] 19 | Base { 20 | // The Source that we're gonna be printing snippets out of. 21 | // This can be a String if you don't have or care about file names. 22 | #[source_code] 23 | src: NamedSource, 24 | // Snippets and highlights can be included in the diagnostic! 25 | #[label("This bit here")] 26 | bad_bit: SourceSpan, 27 | }, 28 | } 29 | 30 | #[derive(Error, Debug, Diagnostic)] 31 | enum NestedEnumError { 32 | #[error(transparent)] 33 | #[diagnostic(transparent)] 34 | Error(#[from] EnumError), 35 | } 36 | 37 | #[test] 38 | fn test_miette_struct_error() { 39 | let src = NamedSource::new("foobar.txt", "[1.3, \"foobar\", false]".to_owned()); 40 | let error = StructError { 41 | src, 42 | bad_bit: SourceSpan::from((6, 8)), 43 | }; 44 | println!("{:?}", Report::new(error)); 45 | 46 | let src = NamedSource::new("foobar.txt", "[1.3, \"foobar\", false]".to_owned()); 47 | let error = EnumError::Base { 48 | src, 49 | bad_bit: SourceSpan::from((6, 8)), 50 | }; 51 | println!("{:?}", Report::new(error)); 52 | 53 | let src = NamedSource::new("foobar.txt", "[1.3, \"foobar\", false]".to_owned()); 54 | let error = EnumError::Base { 55 | src, 56 | bad_bit: SourceSpan::from((6, 8)), 57 | }; 58 | let error = NestedEnumError::Error(error); 59 | println!("{:?}", Report::new(error)); 60 | 61 | // panic!(); 62 | } 63 | -------------------------------------------------------------------------------- /crates/lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compile-claw" 3 | description = "The Claw language compiler library" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | license = { workspace = true } 7 | edition = { workspace = true } 8 | repository = { workspace = true } 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | claw-common = { workspace = true } 14 | claw-parser = { workspace = true } 15 | claw-resolver = { workspace = true } 16 | claw-codegen = { workspace = true } 17 | wit-parser = { workspace = true } 18 | thiserror = { workspace = true } 19 | miette = { workspace = true } 20 | 21 | [dev-dependencies] 22 | wasmtime = { workspace = true } 23 | wasmprinter = { workspace = true } -------------------------------------------------------------------------------- /crates/lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | use claw_codegen::{generate, GenerationError}; 2 | use claw_common::make_source; 3 | use claw_parser::{parse, tokenize, LexerError, ParserError}; 4 | use claw_resolver::{resolve, wit::ResolvedWit, ResolverError}; 5 | use wit_parser::Resolve; 6 | 7 | use miette::Diagnostic; 8 | use thiserror::Error; 9 | 10 | #[derive(Error, Debug, Diagnostic)] 11 | pub enum Error { 12 | #[error(transparent)] 13 | #[diagnostic(transparent)] 14 | Lexer(#[from] LexerError), 15 | 16 | #[error(transparent)] 17 | #[diagnostic(transparent)] 18 | Parser(#[from] ParserError), 19 | 20 | #[error(transparent)] 21 | #[diagnostic(transparent)] 22 | Resolver(#[from] ResolverError), 23 | 24 | #[error(transparent)] 25 | #[diagnostic(transparent)] 26 | Generator(#[from] GenerationError), 27 | } 28 | 29 | pub fn compile(source_name: String, source_code: &str, wit: Resolve) -> Result, Error> { 30 | let src = make_source(source_name.as_str(), source_code); 31 | 32 | let tokens = tokenize(src.clone(), source_code)?; 33 | 34 | let comp = parse(src.clone(), tokens)?; 35 | 36 | let wit = ResolvedWit::new(wit); 37 | 38 | let rcomp = resolve(&comp, wit)?; 39 | 40 | let output = generate(&comp, &rcomp)?; 41 | 42 | Ok(output) 43 | } 44 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/adding-conflicting-types.claw: -------------------------------------------------------------------------------- 1 | func foo() { 2 | let a: u32 = 1; 3 | let b: u64 = 2; 4 | let c = a + b; 5 | } -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/adding-conflicting-types.error.txt: -------------------------------------------------------------------------------- 1 | x Conflicting types inferred for expression type1 != type0 2 | ,-[adding-conflicting-types.claw:4:17] 3 | 3 | let b: u64 = 2; 4 | 4 | let c = a + b; 5 | : | 6 | : `-- This bit 7 | 5 | } 8 | `---- 9 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/global-without-annotation.claw: -------------------------------------------------------------------------------- 1 | let a = 0; 2 | 3 | func foo() -> u32 { 4 | return a; 5 | } 6 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/global-without-annotation.error.txt: -------------------------------------------------------------------------------- 1 | x Global variables must have explicit types annotated starting with ':' 2 | ,-[global-without-annotation.claw:1:7] 3 | 1 | let a = 0; 4 | : | 5 | : `-- Found Assign 6 | 2 | 7 | `---- 8 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/global-without-initialization.claw: -------------------------------------------------------------------------------- 1 | let a: u32; 2 | 3 | func foo() -> u32 { 4 | return a; 5 | } 6 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/global-without-initialization.error.txt: -------------------------------------------------------------------------------- 1 | x Global variables must be initialized starting with '=' 2 | ,-[global-without-initialization.claw:1:11] 3 | 1 | let a: u32; 4 | : | 5 | : `-- Found Semicolon 6 | 2 | 7 | `---- 8 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/invalid-token.claw: -------------------------------------------------------------------------------- 1 | func foo($a: u32) -> u32 { 2 | return $a; 3 | } 4 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/invalid-token.error.txt: -------------------------------------------------------------------------------- 1 | x Unable to tokenize input 2 | ,-[invalid-token.claw:1:10] 3 | 1 | func foo($a: u32) -> u32 { 4 | : | 5 | : `-- Here 6 | 2 | return $a; 7 | `---- 8 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/modifying-immutable-global.claw: -------------------------------------------------------------------------------- 1 | let a: u32 = 1; 2 | 3 | func foo() { 4 | a = 2; 5 | } -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/modifying-immutable-global.error.txt: -------------------------------------------------------------------------------- 1 | x Assigned to immutable variable "a" 2 | ,-[modifying-immutable-global.claw:1:5] 3 | 1 | let a: u32 = 1; 4 | : | 5 | : `-- Defined here 6 | 2 | 7 | 3 | func foo() { 8 | 4 | a = 2; 9 | : | 10 | : `-- Assigned here 11 | 5 | } 12 | `---- 13 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/modifying-immutable-local.claw: -------------------------------------------------------------------------------- 1 | func foo() { 2 | let a: u32 = 1; 3 | a = 2; 4 | } -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/modifying-immutable-local.error.txt: -------------------------------------------------------------------------------- 1 | x Assigned to immutable variable "a" 2 | ,-[modifying-immutable-local.claw:2:9] 3 | 1 | func foo() { 4 | 2 | let a: u32 = 1; 5 | : | 6 | : `-- Defined here 7 | 3 | a = 2; 8 | : | 9 | : `-- Assigned here 10 | 4 | } 11 | `---- 12 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/param-local-type-mismatch.claw: -------------------------------------------------------------------------------- 1 | func foo(a: u32) { 2 | let b: u64 = a; 3 | } -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/param-local-type-mismatch.error.txt: -------------------------------------------------------------------------------- 1 | x Conflicting types inferred for expression type0 != type1 2 | ,-[param-local-type-mismatch.claw:2:18] 3 | 1 | func foo(a: u32) { 4 | 2 | let b: u64 = a; 5 | : | 6 | : `-- This bit 7 | 3 | } 8 | `---- 9 | -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/using-unbound-name.claw: -------------------------------------------------------------------------------- 1 | func foo() { 2 | let a = b; 3 | } -------------------------------------------------------------------------------- /crates/lib/tests/bad-programs/using-unbound-name.error.txt: -------------------------------------------------------------------------------- 1 | x Failed to resolve name "b" 2 | ,-[using-unbound-name.claw:2:13] 3 | 1 | func foo() { 4 | 2 | let a = b; 5 | : | 6 | : `-- Name referenced here 7 | 3 | } 8 | `---- 9 | -------------------------------------------------------------------------------- /crates/lib/tests/compile-error.rs: -------------------------------------------------------------------------------- 1 | use compile_claw::compile; 2 | use miette::{GraphicalReportHandler, GraphicalTheme}; 3 | 4 | use std::fs; 5 | 6 | use wit_parser::Resolve; 7 | 8 | #[test] 9 | fn test_bad_programs() { 10 | for f in fs::read_dir("./tests/bad-programs").unwrap() { 11 | let f = f.unwrap(); 12 | let source_name = f.file_name().into_string().unwrap(); 13 | 14 | if source_name.ends_with(".error.txt") { 15 | continue; // skip error files 16 | } 17 | 18 | assert!(source_name.ends_with(".claw")); 19 | 20 | let source_code = fs::read_to_string(f.path()).unwrap(); 21 | 22 | let mut error_file_path = f.path(); 23 | error_file_path.set_extension("error.txt"); 24 | let error_file_contents = fs::read_to_string(error_file_path).unwrap(); 25 | 26 | let wit = Resolve::new(); 27 | 28 | let result = compile(source_name.clone(), &source_code, wit); 29 | match result { 30 | Ok(_) => { 31 | eprintln!( 32 | "File '{}' compiled without error when the following error was expected:", 33 | source_name 34 | ); 35 | eprintln!("{}", error_file_contents); 36 | panic!() 37 | } 38 | Err(error) => { 39 | let mut error_string = String::new(); 40 | GraphicalReportHandler::new_themed(GraphicalTheme::none()) 41 | .render_report(&mut error_string, &error) 42 | .unwrap(); 43 | assert_eq!(error_string, error_file_contents); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/lib/tests/programs/arithmetic.claw: -------------------------------------------------------------------------------- 1 | export func test-u8-masking() -> bool { 2 | let zero: u8 = 255 + 1; 3 | return zero == 0; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /crates/lib/tests/programs/compare.claw: -------------------------------------------------------------------------------- 1 | export func min-u32(left: u32, right: u32) -> u32 { 2 | if left < right { 3 | return left; 4 | } 5 | return right; 6 | } 7 | 8 | export func max-u32(left: u32, right: u32) -> u32 { 9 | if left > right { 10 | return left; 11 | } 12 | return right; 13 | } 14 | 15 | export func min-u64(left: u64, right: u64) -> u64 { 16 | if left < right { 17 | return left; 18 | } 19 | return right; 20 | } 21 | 22 | export func max-u64(left: u64, right: u64) -> u64 { 23 | if left > right { 24 | return left; 25 | } 26 | return right; 27 | } -------------------------------------------------------------------------------- /crates/lib/tests/programs/counter.claw: -------------------------------------------------------------------------------- 1 | let mut counter-s32: s32 = 0; 2 | 3 | export func increment-s32() -> s32 { 4 | counter-s32 = counter-s32 + 1; 5 | return counter-s32; 6 | } 7 | 8 | export func decrement-s32() -> s32 { 9 | counter-s32 = counter-s32 - 1; 10 | return counter-s32; 11 | } 12 | 13 | let mut counter-s64: s64 = 0; 14 | 15 | export func increment-s64() -> s64 { 16 | counter-s64 = counter-s64 + 1; 17 | return counter-s64; 18 | } 19 | 20 | export func decrement-s64() -> s64 { 21 | counter-s64 = counter-s64 - 1; 22 | return counter-s64; 23 | } 24 | -------------------------------------------------------------------------------- /crates/lib/tests/programs/factorial.claw: -------------------------------------------------------------------------------- 1 | export func factorial(n: u64) -> u64 { 2 | if n == 0 { 3 | return 1; 4 | } 5 | if n == 1 { 6 | return 1; 7 | } 8 | return n * factorial(n - 1); 9 | } 10 | -------------------------------------------------------------------------------- /crates/lib/tests/programs/identity.claw: -------------------------------------------------------------------------------- 1 | export func identity(value: u64) -> u64 { 2 | return value; 3 | } -------------------------------------------------------------------------------- /crates/lib/tests/programs/proxy_call.claw: -------------------------------------------------------------------------------- 1 | import imported: func(a: u32) -> u32; 2 | 3 | export func exported(a: u32) -> u32 { 4 | return imported(a); 5 | } 6 | -------------------------------------------------------------------------------- /crates/lib/tests/programs/quadratic.claw: -------------------------------------------------------------------------------- 1 | export func quad-f32(a: f32, b: f32, c: f32, x: f32) -> f32 { 2 | return a * x * x + b * x + c; 3 | } 4 | 5 | export func quad-f32-let(a: f32, b: f32, c: f32, x: f32) -> f32 { 6 | let a-term = a * x * x; 7 | let b-term = b * x; 8 | let c-term = c; 9 | return a-term + b-term + c-term; 10 | } 11 | 12 | export func quad-f64(a: f64, b: f64, c: f64, x: f64) -> f64 { 13 | return a * x * x + b * x + c; 14 | } 15 | 16 | export func quad-f64-let(a: f64, b: f64, c: f64, x: f64) -> f64 { 17 | let a-term = a * x * x; 18 | let b-term = b * x; 19 | let c-term = c; 20 | return a-term + b-term + c-term; 21 | } -------------------------------------------------------------------------------- /crates/lib/tests/programs/strings.claw: -------------------------------------------------------------------------------- 1 | export func identity(s: string) -> string { 2 | return s; 3 | } 4 | 5 | export func hello-world() -> string { 6 | return "hello, world!"; 7 | } 8 | 9 | export func concat(left: string, right: string) -> string { 10 | return left + right; 11 | } 12 | -------------------------------------------------------------------------------- /crates/lib/tests/programs/timer-proxy.claw: -------------------------------------------------------------------------------- 1 | import { now } from wasi:clocks/monotonic-clock; 2 | import { level, log } from wasi:logging/logging; 3 | 4 | import foo as bar: func(a: string) -> string; 5 | 6 | let max-nanos: u64 = 1000000; 7 | 8 | export func foo(a: string) -> string { 9 | let start = now(); 10 | let res = bar(a); 11 | let end = now(); 12 | if end - start > max-nanos { 13 | log(level::warn, "profiling", "Calling foo took a long time"); 14 | } 15 | return res; 16 | } 17 | -------------------------------------------------------------------------------- /crates/lib/tests/programs/unary.claw: -------------------------------------------------------------------------------- 1 | let mut val: s32 = 0; 2 | 3 | export func set(v: s32) -> s32 { 4 | let old = val; 5 | val = v; 6 | return old; 7 | } 8 | 9 | export func get-inverse() -> s32 { 10 | return -val; 11 | } 12 | -------------------------------------------------------------------------------- /crates/lib/tests/programs/wit/claw.wit: -------------------------------------------------------------------------------- 1 | package claw:samples; 2 | 3 | world arithmetic { 4 | export test-u8-masking: func() -> bool; 5 | } 6 | 7 | world compare { 8 | export min-u32: func(left: u32, right: u32) -> u32; 9 | export max-u32: func(left: u32, right: u32) -> u32; 10 | export min-u64: func(left: u64, right: u64) -> u64; 11 | export max-u64: func(left: u64, right: u64) -> u64; 12 | } 13 | 14 | world counter { 15 | export increment-s32: func() -> s32; 16 | export decrement-s32: func() -> s32; 17 | export increment-s64: func() -> s64; 18 | export decrement-s64: func() -> s64; 19 | } 20 | 21 | world factorial { 22 | export factorial: func(n: u64) -> u64; 23 | } 24 | 25 | world identity { 26 | export identity: func(value: u64) -> u64; 27 | } 28 | 29 | world proxy-call { 30 | import imported: func(a: u32) -> u32; 31 | export exported: func(a: u32) -> u32; 32 | } 33 | 34 | world quadratic { 35 | export quad-f32: func(a: float32, b: float32, c: float32, x: float32) -> float32; 36 | export quad-f32-let: func(a: float32, b: float32, c: float32, x: float32) -> float32; 37 | export quad-f64: func(a: float64, b: float64, c: float64, x: float64) -> float64; 38 | export quad-f64-let: func(a: float64, b: float64, c: float64, x: float64) -> float64; 39 | } 40 | 41 | world strings { 42 | export identity: func(s: string) -> string; 43 | export hello-world: func() -> string; 44 | export concat: func(left: string, right: string) -> string; 45 | } 46 | 47 | world timer-proxy { 48 | import wasi:clocks/monotonic-clock; 49 | import wasi:logging/logging; 50 | 51 | import foo: func(a: string) -> string; 52 | export foo: func(a: string) -> string; 53 | } 54 | 55 | world unary { 56 | export set: func(v: s32) -> s32; 57 | export get-inverse: func() -> s32; 58 | } -------------------------------------------------------------------------------- /crates/lib/tests/programs/wit/deps/clocks/monotonic-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks; 2 | 3 | /// WASI Monotonic Clock is a clock API intended to let users measure elapsed 4 | /// time. 5 | /// 6 | /// It is intended to be portable at least between Unix-family platforms and 7 | /// Windows. 8 | /// 9 | /// A monotonic clock is a clock which has an unspecified initial value, and 10 | /// successive reads of the clock will produce non-decreasing values. 11 | /// 12 | /// It is intended for measuring elapsed time. 13 | interface monotonic-clock { 14 | /// An instant in time, in nanoseconds. An instant is relative to an 15 | /// unspecified initial value, and can only be compared to instances from 16 | /// the same monotonic-clock. 17 | type instant = u64; 18 | 19 | /// Read the current value of the clock. 20 | /// 21 | /// The clock is monotonic, therefore calling this function repeatedly will 22 | /// produce a sequence of non-decreasing values. 23 | now: func() -> instant; 24 | } -------------------------------------------------------------------------------- /crates/lib/tests/programs/wit/deps/logging/logging.wit: -------------------------------------------------------------------------------- 1 | package wasi:logging; 2 | 3 | /// WASI Logging is a logging API intended to let users emit log messages with 4 | /// simple priority levels and context values. 5 | interface logging { 6 | /// A log level, describing a kind of message. 7 | enum level { 8 | /// Describes messages about the values of variables and the flow of 9 | /// control within a program. 10 | trace, 11 | 12 | /// Describes messages likely to be of interest to someone debugging a 13 | /// program. 14 | debug, 15 | 16 | /// Describes messages likely to be of interest to someone monitoring a 17 | /// program. 18 | info, 19 | 20 | /// Describes messages indicating hazardous situations. 21 | warn, 22 | 23 | /// Describes messages indicating serious errors. 24 | error, 25 | 26 | /// Describes messages indicating fatal errors. 27 | critical, 28 | } 29 | 30 | /// Emit a log message. 31 | /// 32 | /// A log message has a `level` describing what kind of message is being 33 | /// sent, a context, which is an uninterpreted string meant to help 34 | /// consumers group similar messages, and a string containing the message 35 | /// text. 36 | log: func(level: level, context: string, message: string); 37 | } -------------------------------------------------------------------------------- /crates/lib/tests/runtime.rs: -------------------------------------------------------------------------------- 1 | use claw_common::UnwrapPretty; 2 | use compile_claw::compile; 3 | 4 | use std::fs; 5 | 6 | use wasmtime::component::{bindgen, Component, Linker}; 7 | use wasmtime::{Config, Engine, Store}; 8 | use wit_parser::Resolve; 9 | 10 | #[allow(dead_code)] 11 | struct Runtime { 12 | engine: Engine, 13 | component: Component, 14 | linker: Linker<()>, 15 | store: Store<()>, 16 | } 17 | 18 | impl Runtime { 19 | pub fn new(name: &str) -> Self { 20 | let path = format!("./tests/programs/{}.claw", name); 21 | let input = fs::read_to_string(path).unwrap(); 22 | let mut wit = Resolve::new(); 23 | wit.push_path("./tests/programs/wit").unwrap(); 24 | let component_bytes = compile(name.to_owned(), &input, wit).unwrap_pretty(); 25 | 26 | println!("{}", wasmprinter::print_bytes(&component_bytes).unwrap()); 27 | 28 | let mut config = Config::new(); 29 | config.wasm_component_model(true); 30 | let engine = Engine::new(&config).unwrap(); 31 | 32 | let component = Component::new(&engine, &component_bytes).unwrap(); 33 | let linker = Linker::new(&engine); 34 | let store = Store::new(&engine, ()); 35 | 36 | Runtime { 37 | engine, 38 | component, 39 | linker, 40 | store, 41 | } 42 | } 43 | } 44 | 45 | #[test] 46 | fn test_arithmetic() { 47 | bindgen!("arithmetic" in "tests/programs/wit"); 48 | 49 | let mut runtime = Runtime::new("arithmetic"); 50 | 51 | let (arithmetic, _) = 52 | Arithmetic::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 53 | 54 | assert!(arithmetic.call_test_u8_masking(&mut runtime.store).unwrap()); 55 | } 56 | 57 | #[test] 58 | fn test_counter() { 59 | bindgen!("counter" in "tests/programs/wit"); 60 | 61 | let mut runtime = Runtime::new("counter"); 62 | 63 | let (counter_s64, _) = 64 | Counter::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 65 | 66 | for i in 1..200 { 67 | // Increase by one 68 | assert_eq!( 69 | counter_s64.call_increment_s32(&mut runtime.store).unwrap(), 70 | i 71 | ); 72 | assert_eq!( 73 | counter_s64.call_increment_s64(&mut runtime.store).unwrap(), 74 | i as i64 75 | ); 76 | // Increase then decrease by one 77 | assert_eq!( 78 | counter_s64.call_increment_s32(&mut runtime.store).unwrap(), 79 | i + 1 80 | ); 81 | assert_eq!( 82 | counter_s64.call_increment_s64(&mut runtime.store).unwrap(), 83 | i as i64 + 1 84 | ); 85 | assert_eq!( 86 | counter_s64.call_decrement_s32(&mut runtime.store).unwrap(), 87 | i 88 | ); 89 | assert_eq!( 90 | counter_s64.call_decrement_s64(&mut runtime.store).unwrap(), 91 | i as i64 92 | ); 93 | } 94 | 95 | for i in (1..200).rev() { 96 | assert_eq!( 97 | counter_s64.call_decrement_s32(&mut runtime.store).unwrap(), 98 | i - 1 99 | ); 100 | assert_eq!( 101 | counter_s64.call_decrement_s64(&mut runtime.store).unwrap(), 102 | i as i64 - 1 103 | ); 104 | } 105 | } 106 | 107 | #[test] 108 | fn test_factorial() { 109 | bindgen!("factorial" in "tests/programs/wit"); 110 | 111 | let mut runtime = Runtime::new("factorial"); 112 | 113 | let (factorial, _) = 114 | Factorial::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 115 | 116 | for (i, val) in [1, 1, 2, 6, 24, 120].iter().enumerate() { 117 | let fact = factorial 118 | .call_factorial(&mut runtime.store, i as u64) 119 | .unwrap(); 120 | assert_eq!( 121 | fact, *val, 122 | "factorial({}) was {} instead of {}", 123 | i, fact, *val 124 | ); 125 | } 126 | } 127 | 128 | #[test] 129 | fn test_identity() { 130 | bindgen!("identity" in "tests/programs/wit"); 131 | 132 | let mut runtime = Runtime::new("identity"); 133 | 134 | let (identity, _) = 135 | Identity::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 136 | 137 | for i in [0, 1, 2, 12, 5634, 34] { 138 | assert_eq!(identity.call_identity(&mut runtime.store, i).unwrap(), i); 139 | } 140 | } 141 | 142 | #[test] 143 | fn test_compare() { 144 | bindgen!("compare" in "tests/programs/wit"); 145 | 146 | let mut runtime = Runtime::new("compare"); 147 | 148 | let (compare, _) = 149 | Compare::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 150 | 151 | for i in 1..200 { 152 | for j in 1..200 { 153 | let expected_min = std::cmp::min(i, j); 154 | let expected_max = std::cmp::max(i, j); 155 | let actual_min = compare.call_min_u32(&mut runtime.store, i, j).unwrap(); 156 | let actual_max = compare.call_max_u32(&mut runtime.store, i, j).unwrap(); 157 | assert_eq!( 158 | expected_min, actual_min, 159 | "expected min({}, {}) to be {} not {}", 160 | i, j, expected_min, actual_min 161 | ); 162 | assert_eq!( 163 | expected_max, actual_max, 164 | "expected max({}, {}) to be {} not {}", 165 | i, j, expected_max, actual_max 166 | ); 167 | 168 | let i = i as u64; 169 | let j = j as u64; 170 | let expected_min = expected_min as u64; 171 | let expected_max = expected_max as u64; 172 | let actual_min = compare.call_min_u64(&mut runtime.store, i, j).unwrap(); 173 | let actual_max = compare.call_max_u64(&mut runtime.store, i, j).unwrap(); 174 | assert_eq!( 175 | expected_min, actual_min, 176 | "expected min({}, {}) to be {} not {}", 177 | i, j, expected_min, actual_min 178 | ); 179 | assert_eq!( 180 | expected_max, actual_max, 181 | "expected max({}, {}) to be {} not {}", 182 | i, j, expected_max, actual_max 183 | ); 184 | } 185 | } 186 | } 187 | 188 | #[test] 189 | fn test_proxy_call() { 190 | bindgen!("proxy-call" in "tests/programs/wit"); 191 | 192 | let mut runtime = Runtime::new("proxy_call"); 193 | 194 | impl ProxyCallImports for () { 195 | fn imported(&mut self, a: u32) -> Result { 196 | Ok(a) 197 | } 198 | } 199 | 200 | ProxyCall::add_to_linker(&mut runtime.linker, |s| s).unwrap(); 201 | 202 | let (proxy_call, _) = 203 | ProxyCall::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 204 | 205 | for x in 0..10 { 206 | let actual = proxy_call.call_exported(&mut runtime.store, x).unwrap(); 207 | assert_eq!(x, actual); 208 | } 209 | } 210 | 211 | #[test] 212 | fn test_quadratic() { 213 | bindgen!("quadratic" in "tests/programs/wit"); 214 | 215 | let mut runtime = Runtime::new("quadratic"); 216 | 217 | let (quadratic, _) = 218 | Quadratic::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 219 | 220 | for x in 0..10 { 221 | let expected = 2 * x * x + 3 * x + 4; 222 | let actual_f32 = quadratic 223 | .call_quad_f32(&mut runtime.store, 2.0, 3.0, 4.0, x as f32) 224 | .unwrap(); 225 | let actual_f32_let = quadratic 226 | .call_quad_f32_let(&mut runtime.store, 2.0, 3.0, 4.0, x as f32) 227 | .unwrap(); 228 | let actual_f64 = quadratic 229 | .call_quad_f64(&mut runtime.store, 2.0, 3.0, 4.0, x as f64) 230 | .unwrap(); 231 | let actual_f64_let = quadratic 232 | .call_quad_f64_let(&mut runtime.store, 2.0, 3.0, 4.0, x as f64) 233 | .unwrap(); 234 | 235 | assert_eq!(expected as f32, actual_f32); 236 | assert_eq!(expected as f32, actual_f32_let); 237 | assert_eq!(expected as f64, actual_f64); 238 | assert_eq!(expected as f64, actual_f64_let); 239 | } 240 | } 241 | 242 | #[test] 243 | fn test_strings() { 244 | bindgen!("strings" in "tests/programs/wit"); 245 | 246 | let mut runtime = Runtime::new("strings"); 247 | 248 | let (strings, _) = 249 | Strings::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 250 | 251 | let long_string = "Z".repeat(1000); 252 | let cases = [ 253 | "", 254 | "asdf", 255 | "673hlksdfkjh5r;4hj6s", 256 | "a", 257 | long_string.as_str(), 258 | ]; 259 | 260 | for case in cases { 261 | assert_eq!( 262 | case, 263 | strings.call_identity(&mut runtime.store, case).unwrap() 264 | ); 265 | } 266 | 267 | assert_eq!( 268 | strings.call_hello_world(&mut runtime.store).unwrap(), 269 | "hello, world!" 270 | ); 271 | 272 | for case in cases { 273 | assert_eq!( 274 | format!("{}Lorem Ipsum", case).as_str(), 275 | strings 276 | .call_concat(&mut runtime.store, case, "Lorem Ipsum") 277 | .unwrap() 278 | ); 279 | } 280 | } 281 | 282 | #[test] 283 | fn test_timer_proxy() { 284 | bindgen!("timer-proxy" in "tests/programs/wit"); 285 | 286 | let mut runtime = Runtime::new("timer-proxy"); 287 | 288 | impl TimerProxyImports for () { 289 | fn foo(&mut self, a: String) -> wasmtime::Result { 290 | wasmtime::Result::Ok(a) 291 | } 292 | } 293 | 294 | use wasi::logging::logging; 295 | impl logging::Host for () { 296 | fn log( 297 | &mut self, 298 | _level: logging::Level, 299 | context: String, 300 | message: String, 301 | ) -> wasmtime::Result<()> { 302 | println!("{}: {}", context, message); 303 | wasmtime::Result::Ok(()) 304 | } 305 | } 306 | 307 | use wasi::clocks::monotonic_clock; 308 | impl monotonic_clock::Host for () { 309 | fn now(&mut self) -> wasmtime::Result { 310 | wasmtime::Result::Ok(monotonic_clock::Instant::from(1u64)) 311 | } 312 | } 313 | 314 | TimerProxy::add_to_linker(&mut runtime.linker, |s| s).unwrap(); 315 | 316 | let (timer, _) = 317 | TimerProxy::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 318 | 319 | let found = timer.call_foo(&mut runtime.store, "asdf").unwrap(); 320 | assert_eq!(found, "asdf"); 321 | } 322 | 323 | #[test] 324 | fn test_unary() { 325 | bindgen!("unary" in "tests/programs/wit"); 326 | 327 | let mut runtime = Runtime::new("unary"); 328 | 329 | let (unary, _) = 330 | Unary::instantiate(&mut runtime.store, &runtime.component, &runtime.linker).unwrap(); 331 | 332 | for x in 0..10_i32 { 333 | unary.call_set(&mut runtime.store, x).unwrap(); 334 | let inverse = unary.call_get_inverse(&mut runtime.store).unwrap(); 335 | assert_eq!(-x, inverse); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /crates/parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claw-parser" 3 | description = "The Claw language lexer and parser" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | license = { workspace = true } 7 | edition = { workspace = true } 8 | repository = { workspace = true } 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | claw-ast = { workspace = true } 14 | miette = { workspace = true } 15 | claw-common = { workspace = true } 16 | cranelift-entity = { workspace = true } 17 | logos = { workspace = true } 18 | thiserror = { workspace = true } 19 | 20 | [dev-dependencies] 21 | pretty_assertions = { workspace = true } -------------------------------------------------------------------------------- /crates/parser/src/component.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::Token; 2 | use crate::{ 3 | expressions::parse_expression, statements::parse_block, types::parse_valtype, ParseInput, 4 | ParserError, 5 | }; 6 | use ast::{FunctionId, GlobalId, Import, ImportFrom, ImportId, NameId, PlainImport, TypeId}; 7 | use claw_ast as ast; 8 | 9 | use claw_common::Source; 10 | 11 | use crate::names::{parse_ident, parse_interface_name}; 12 | 13 | pub fn parse_component(src: Source, input: &mut ParseInput) -> Result { 14 | let mut component = ast::Component::new(src); 15 | 16 | while !input.done() { 17 | // Check for the export keyword 18 | let exported = input.next_if(Token::Export).is_some(); 19 | 20 | // Determine the kind of item and parse it 21 | match input.peek()?.token { 22 | Token::Import => { 23 | parse_import(input, &mut component)?; 24 | } 25 | Token::Let => { 26 | parse_global(input, &mut component, exported)?; 27 | } 28 | Token::Func => { 29 | parse_func(input, &mut component, exported)?; 30 | } 31 | _ => { 32 | return Err(input.unexpected_token("Top level item (e.g. import, global, function")) 33 | } 34 | } 35 | } 36 | 37 | Ok(component) 38 | } 39 | 40 | fn parse_import( 41 | input: &mut ParseInput, 42 | comp: &mut ast::Component, 43 | ) -> Result { 44 | let token = input.peekn(1).unwrap(); 45 | let import = match token { 46 | Token::LBrace => Import::ImportFrom(parse_import_from(input, comp)?), 47 | Token::Identifier(_) => Import::Plain(parse_plain_import(input, comp)?), 48 | _ => return Err(input.unexpected_token("Invalid import")), 49 | }; 50 | 51 | Ok(comp.push_import(import)) 52 | } 53 | 54 | fn parse_plain_import( 55 | input: &mut ParseInput, 56 | comp: &mut ast::Component, 57 | ) -> Result { 58 | input.assert_next(Token::Import, "Import item")?; 59 | let ident = parse_ident(input, comp)?; 60 | let alias = match input.peek()?.token { 61 | Token::As => { 62 | // Consume the `as` 63 | let _ = input.next(); 64 | Some(parse_ident(input, comp)?) 65 | } 66 | _ => None, 67 | }; 68 | input.assert_next(Token::Colon, "Plain imports must annotate their type")?; 69 | let external_type = parse_external_type(input, comp)?; 70 | input.assert_next( 71 | Token::Semicolon, 72 | "Plain imports must be ended by semicolons", 73 | )?; 74 | 75 | Ok(PlainImport { 76 | ident, 77 | alias, 78 | external_type, 79 | }) 80 | } 81 | 82 | fn parse_import_from( 83 | input: &mut ParseInput, 84 | comp: &mut ast::Component, 85 | ) -> Result { 86 | input.assert_next(Token::Import, "Import")?; 87 | input.assert_next(Token::LBrace, "Imported items")?; 88 | 89 | let mut items = Vec::new(); 90 | loop { 91 | if input.peek()?.token == Token::RBrace { 92 | break; 93 | } 94 | 95 | items.push(parse_import_item(input, comp)?); 96 | 97 | if input.next_if(Token::Comma).is_none() { 98 | break; 99 | } 100 | } 101 | 102 | input.assert_next(Token::RBrace, "End of imported items")?; 103 | input.assert_next(Token::From, "Specify the package to import from")?; 104 | 105 | let (package, interface) = parse_interface_name(input)?; 106 | 107 | input.assert_next(Token::Semicolon, "Imports must be ended with a semicolon")?; 108 | 109 | Ok(ImportFrom { 110 | items, 111 | package, 112 | interface, 113 | }) 114 | } 115 | 116 | fn parse_import_item( 117 | input: &mut ParseInput, 118 | comp: &mut ast::Component, 119 | ) -> Result<(NameId, Option), ParserError> { 120 | let ident = parse_ident(input, comp)?; 121 | 122 | let alias = if input.peek()?.token == Token::As { 123 | input.next()?; 124 | let alias = parse_ident(input, comp)?; 125 | Some(alias) 126 | } else { 127 | None 128 | }; 129 | 130 | Ok((ident, alias)) 131 | } 132 | 133 | fn parse_global( 134 | input: &mut ParseInput, 135 | comp: &mut ast::Component, 136 | exported: bool, 137 | ) -> Result { 138 | let err_no_let = "Global variable definitions must start with 'let'"; 139 | input.assert_next(Token::Let, err_no_let)?; 140 | 141 | let mutable = input.next_if(Token::Mut).is_some(); 142 | let ident = parse_ident(input, comp)?; 143 | 144 | let err_no_colon = "Global variables must have explicit types annotated starting with ':'"; 145 | input.assert_next(Token::Colon, err_no_colon)?; 146 | 147 | let type_id = parse_valtype(input, comp)?; 148 | 149 | let err_no_assign = "Global variables must be initialized starting with '='"; 150 | input.assert_next(Token::Assign, err_no_assign)?; 151 | 152 | let init_value = parse_expression(input, comp)?; 153 | 154 | let err_no_semicolon = "Global variable definitions must end with ';'"; 155 | input.assert_next(Token::Semicolon, err_no_semicolon)?; 156 | 157 | let global = ast::Global { 158 | exported, 159 | mutable, 160 | ident, 161 | type_id, 162 | init_value, 163 | }; 164 | 165 | Ok(comp.push_global(global)) 166 | } 167 | 168 | fn parse_func( 169 | input: &mut ParseInput, 170 | comp: &mut ast::Component, 171 | exported: bool, 172 | ) -> Result { 173 | input.assert_next(Token::Func, "Function signature")?; 174 | let ident = parse_ident(input, comp)?; 175 | let params = parse_params(input, comp)?; 176 | let results = parse_results(input, comp)?; 177 | let (body, _) = parse_block(input, comp)?; 178 | 179 | let function = ast::Function { 180 | exported, 181 | ident, 182 | params, 183 | results, 184 | body, 185 | }; 186 | 187 | Ok(comp.push_function(function)) 188 | } 189 | 190 | fn parse_params( 191 | input: &mut ParseInput, 192 | comp: &mut ast::Component, 193 | ) -> Result, ParserError> { 194 | input.assert_next(Token::LParen, "Function parameters are parenthesized")?; 195 | 196 | let mut arguments = Vec::new(); 197 | while input.peek()?.token != Token::RParen { 198 | let argument = parse_param(input, comp)?; 199 | arguments.push(argument); 200 | 201 | if input.peek()?.token != Token::Comma { 202 | break; 203 | } 204 | 205 | let _ = input.next(); 206 | } 207 | input.assert_next( 208 | Token::RParen, 209 | "Function parameter parenthesis must be closed", 210 | )?; 211 | 212 | Ok(arguments) 213 | } 214 | 215 | fn parse_param( 216 | input: &mut ParseInput, 217 | comp: &mut ast::Component, 218 | ) -> Result<(NameId, TypeId), ParserError> { 219 | let ident = parse_ident(input, comp)?; 220 | input.assert_next(Token::Colon, "Colon ':'")?; 221 | let type_id = parse_valtype(input, comp)?; 222 | Ok((ident, type_id)) 223 | } 224 | 225 | fn parse_results( 226 | input: &mut ParseInput, 227 | comp: &mut ast::Component, 228 | ) -> Result, ParserError> { 229 | let return_type = match input.next_if(Token::Arrow) { 230 | Some(_) => Some(parse_valtype(input, comp)?), 231 | None => None, 232 | }; 233 | Ok(return_type) 234 | } 235 | 236 | fn parse_external_type( 237 | input: &mut ParseInput, 238 | comp: &mut ast::Component, 239 | ) -> Result { 240 | Ok(ast::ExternalType::Function(parse_fn_type(input, comp)?)) 241 | } 242 | 243 | fn parse_fn_type( 244 | input: &mut ParseInput, 245 | comp: &mut ast::Component, 246 | ) -> Result { 247 | input.assert_next(Token::Func, "Function keyword")?; 248 | let params = parse_params(input, comp)?; 249 | let results = parse_results(input, comp)?; 250 | 251 | Ok(ast::FnType { params, results }) 252 | } 253 | 254 | #[cfg(test)] 255 | mod tests { 256 | use super::*; 257 | use crate::make_input; 258 | use claw_common::UnwrapPretty; 259 | 260 | #[test] 261 | fn test_increment() { 262 | let source = " 263 | let mut counter: u32 = 0; 264 | 265 | export func increment() -> u32 { 266 | counter = counter + 1; 267 | return counter; 268 | }"; 269 | let (src, mut input) = make_input(source); 270 | parse_component(src, &mut input).unwrap_pretty(); 271 | } 272 | 273 | #[test] 274 | fn test_empty_function() { 275 | let source = "func empty() {}"; 276 | let (src, mut input) = make_input(source); 277 | let mut comp = ast::Component::new(src.clone()); 278 | parse_func(&mut input.clone(), &mut comp, false).unwrap_pretty(); 279 | parse_component(src, &mut input).unwrap_pretty(); 280 | } 281 | 282 | #[test] 283 | fn test_basic_function() { 284 | let source = "func increment() -> u32 { return 0; }"; 285 | let (src, mut input) = make_input(source); 286 | let mut comp = ast::Component::new(src.clone()); 287 | parse_func(&mut input.clone(), &mut comp, false).unwrap_pretty(); 288 | parse_component(src, &mut input).unwrap_pretty(); 289 | } 290 | 291 | #[test] 292 | fn test_parse_global() { 293 | let source = "let mut counter: u32 = 0;"; 294 | let (src, mut input) = make_input(source); 295 | let mut comp = ast::Component::new(src); 296 | parse_global(&mut input, &mut comp, false).unwrap_pretty(); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /crates/parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::should_implement_trait)] 2 | #![allow(clippy::while_let_loop)] 3 | #![allow(clippy::while_let_on_iterator)] 4 | 5 | mod component; 6 | mod expressions; 7 | mod lexer; 8 | mod names; 9 | mod statements; 10 | mod types; 11 | 12 | use std::sync::Arc; 13 | 14 | use crate::lexer::{Token, TokenData}; 15 | use ast::{component::Component, Span}; 16 | use claw_ast as ast; 17 | use claw_common::Source; 18 | 19 | use miette::{Diagnostic, NamedSource, SourceSpan}; 20 | use thiserror::Error; 21 | 22 | use component::parse_component; 23 | 24 | pub use lexer::{tokenize, LexerError}; 25 | 26 | #[derive(Error, Debug, Diagnostic)] 27 | pub enum ParserError { 28 | #[error("Failed to parse")] 29 | Base { 30 | #[source_code] 31 | src: Source, 32 | #[label("Unable to parse this code")] 33 | span: SourceSpan, 34 | }, 35 | #[error("{description}")] 36 | UnexpectedToken { 37 | #[source_code] 38 | src: Source, 39 | #[label("Found {token:?}")] 40 | span: SourceSpan, 41 | description: String, 42 | token: Token, 43 | }, 44 | #[error("End of input reached")] 45 | EndOfInput, 46 | #[error("Feature {feature} not supported yet at {token:?}")] 47 | NotYetSupported { feature: String, token: Token }, 48 | } 49 | 50 | pub fn parse(src: Source, tokens: Vec) -> Result { 51 | let mut input = ParseInput::new(src.clone(), tokens); 52 | parse_component(src, &mut input) 53 | } 54 | 55 | #[derive(Debug, Clone)] 56 | pub struct ParseInput { 57 | src: Source, 58 | tokens: Vec, 59 | index: usize, 60 | } 61 | 62 | impl ParseInput { 63 | pub fn new(src: Source, tokens: Vec) -> Self { 64 | ParseInput { 65 | src, 66 | tokens, 67 | index: 0, 68 | } 69 | } 70 | 71 | pub fn unsupported_error(&self, feature: &str) -> ParserError { 72 | ParserError::NotYetSupported { 73 | feature: feature.to_string(), 74 | token: self.tokens[self.index].token.clone(), 75 | } 76 | } 77 | 78 | pub fn unexpected_token(&self, description: &str) -> ParserError { 79 | let index = if self.index == 0 { 80 | self.index 81 | } else { 82 | self.index - 1 83 | }; 84 | let data = &self.tokens[index]; 85 | ParserError::UnexpectedToken { 86 | src: self.src.clone(), 87 | span: data.span, 88 | description: description.to_string(), 89 | token: data.token.clone(), 90 | } 91 | } 92 | 93 | pub fn get_source(&self) -> Source { 94 | self.src.clone() 95 | } 96 | 97 | pub fn has(&self, num: usize) -> bool { 98 | self.index + num <= self.tokens.len() 99 | } 100 | 101 | pub fn done(&self) -> bool { 102 | self.index >= self.tokens.len() 103 | } 104 | 105 | pub fn peek(&self) -> Result<&TokenData, ParserError> { 106 | self.tokens.get(self.index).ok_or(ParserError::EndOfInput) 107 | } 108 | 109 | pub fn peekn(&self, n: usize) -> Option<&Token> { 110 | self.tokens.get(self.index + n).map(|t| &t.token) 111 | } 112 | 113 | pub fn next(&mut self) -> Result<&TokenData, ParserError> { 114 | let result = self.tokens.get(self.index); 115 | self.index += 1; 116 | result.ok_or(ParserError::EndOfInput) 117 | } 118 | 119 | pub fn assert_next(&mut self, token: Token, description: &str) -> Result { 120 | let next = self.next()?; 121 | if next.token == token { 122 | Ok(next.span) 123 | } else { 124 | Err(self.unexpected_token(description)) 125 | } 126 | } 127 | 128 | pub fn next_if(&mut self, token: Token) -> Option { 129 | { 130 | let next = self.peek().ok()?; 131 | if next.token != token { 132 | return None; 133 | } 134 | } 135 | Some(self.next().ok()?.span) 136 | } 137 | 138 | pub fn slice_next(&mut self, num: usize) -> Result<&[TokenData], ParserError> { 139 | if self.has(num) { 140 | let result = &self.tokens[self.index..self.index + num]; 141 | self.index += num; 142 | Ok(result) 143 | } else { 144 | Err(ParserError::EndOfInput) 145 | } 146 | } 147 | } 148 | 149 | pub fn make_input(source: &str) -> (Source, ParseInput) { 150 | let src = Arc::new(NamedSource::new("test", source.to_string())); 151 | let tokens = crate::lexer::tokenize(src.clone(), source).unwrap(); 152 | (src.clone(), ParseInput::new(src, tokens)) 153 | } 154 | 155 | pub fn make_span(start: usize, len: usize) -> Span { 156 | Span::new(start.into(), len) 157 | } 158 | 159 | #[cfg(test)] 160 | mod tests { 161 | use super::*; 162 | 163 | #[test] 164 | fn test_peek() { 165 | let (_src, mut input) = make_input("export func"); 166 | assert_eq!(input.peek().unwrap().token, Token::Export); 167 | assert_eq!(input.peek().unwrap().token, Token::Export); 168 | assert_eq!(input.peek().unwrap().token, Token::Export); 169 | input.next().unwrap(); 170 | assert_eq!(input.peek().unwrap().token, Token::Func); 171 | assert_eq!(input.peek().unwrap().token, Token::Func); 172 | assert_eq!(input.peek().unwrap().token, Token::Func); 173 | } 174 | 175 | #[test] 176 | fn test_peekn() { 177 | let (_src, mut input) = make_input("export func () -> {}"); 178 | assert_eq!(input.peekn(0).unwrap(), &Token::Export); 179 | assert_eq!(input.peekn(1).unwrap(), &Token::Func); 180 | assert_eq!(input.peekn(2).unwrap(), &Token::LParen); 181 | input.next().unwrap(); 182 | assert_eq!(input.peekn(0).unwrap(), &Token::Func); 183 | assert_eq!(input.peekn(1).unwrap(), &Token::LParen); 184 | assert_eq!(input.peekn(2).unwrap(), &Token::RParen); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /crates/parser/src/names.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{Component, NameId, PackageName}; 2 | use crate::lexer::Token; 3 | use crate::{ParseInput, ParserError}; 4 | 5 | /// Parse an identifier 6 | pub fn parse_ident(input: &mut ParseInput, comp: &mut Component) -> Result { 7 | match &input.peek()?.token { 8 | Token::Identifier(ident) => { 9 | let ident = ident.clone(); 10 | let span = input.next().unwrap().span; 11 | Ok(comp.new_name(ident, span)) 12 | } 13 | _ => { 14 | input.next().unwrap(); 15 | Err(input.unexpected_token("Parsing identifier")) 16 | } 17 | } 18 | } 19 | 20 | /// Parse an interface name into package and interface portions 21 | pub fn parse_interface_name(input: &mut ParseInput) -> Result<(PackageName, String), ParserError> { 22 | let namespace = parse_identifier(input)?; 23 | input.assert_next( 24 | Token::Colon, 25 | "Package namespace and name must be separated by a colon", 26 | )?; 27 | let name = parse_identifier(input)?; 28 | input.assert_next(Token::Div, "Interface name comes after a '/'")?; 29 | let interface = parse_identifier(input)?; 30 | 31 | let package = PackageName { 32 | namespace, 33 | name, 34 | version: None, 35 | }; 36 | Ok((package, interface)) 37 | } 38 | 39 | fn parse_identifier(input: &mut ParseInput) -> Result { 40 | match &input.next()?.token { 41 | Token::Identifier(ident) => Ok(ident.clone()), 42 | _ => Err(input.unexpected_token("Identifier part of interface name")), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/parser/src/statements.rs: -------------------------------------------------------------------------------- 1 | use ast::{Call, Statement}; 2 | 3 | use crate::ast::{self, merge, Component, Span, StatementId}; 4 | use crate::lexer::Token; 5 | use crate::names::parse_ident; 6 | use crate::{expressions::parse_expression, types::parse_valtype, ParseInput, ParserError}; 7 | 8 | pub fn parse_block( 9 | input: &mut ParseInput, 10 | comp: &mut Component, 11 | ) -> Result<(Vec, Span), ParserError> { 12 | let start_span = input.assert_next(Token::LBrace, "Left brace '{'")?; 13 | 14 | let mut statements = Vec::new(); 15 | while input.peek()?.token != Token::RBrace { 16 | statements.push(parse_statement(input, comp)?); 17 | } 18 | 19 | let end_span = input.assert_next(Token::RBrace, "Right brace '}'")?; 20 | 21 | let span = merge(&start_span, &end_span); 22 | Ok((statements, span)) 23 | } 24 | 25 | pub fn parse_statement( 26 | input: &mut ParseInput, 27 | comp: &mut Component, 28 | ) -> Result { 29 | let peek0 = &input.peek()?.token; 30 | let peek1 = input.peekn(1); 31 | match (peek0, peek1) { 32 | (Token::Return, _) => parse_return(input, comp), 33 | (Token::Let, _) => parse_let(input, comp), 34 | (Token::If, _) => parse_if(input, comp), 35 | (Token::Identifier(_), Some(Token::LParen)) => parse_call(input, comp), 36 | (Token::Identifier(_), _) => parse_assign(input, comp), 37 | _ => { 38 | _ = input.next(); 39 | Err(input.unexpected_token("Invalid statement start")) 40 | } 41 | } 42 | } 43 | 44 | fn parse_let(input: &mut ParseInput, comp: &mut Component) -> Result { 45 | // Prefix 46 | let start_span = input.assert_next(Token::Let, "Let keyword 'let'")?; 47 | let mutable = input.next_if(Token::Mut).is_some(); 48 | let ident = parse_ident(input, comp)?; 49 | 50 | // Annotation 51 | let annotation = match input.next_if(Token::Colon) { 52 | Some(_) => Some(parse_valtype(input, comp)?), 53 | None => None, 54 | }; 55 | 56 | // Suffix 57 | input.assert_next(Token::Assign, "Assignment '='")?; 58 | let expression = parse_expression(input, comp)?; 59 | let end_span = input.assert_next(Token::Semicolon, "Semicolon ';'")?; 60 | 61 | let statement = ast::Let { 62 | mutable, 63 | ident, 64 | annotation, 65 | expression, 66 | }; 67 | let span = merge(&start_span, &end_span); 68 | Ok(comp.new_statement(ast::Statement::Let(statement), span)) 69 | } 70 | 71 | fn parse_return(input: &mut ParseInput, comp: &mut Component) -> Result { 72 | let start_span = input.assert_next(Token::Return, "Return keyword 'return'")?; 73 | 74 | let (expression, end_span) = match input.next_if(Token::Semicolon) { 75 | Some(end_span) => (None, end_span), 76 | None => { 77 | let expression = parse_expression(input, comp)?; 78 | let end_span = input.assert_next(Token::Semicolon, "Semicolon ';'")?; 79 | (Some(expression), end_span) 80 | } 81 | }; 82 | 83 | let statement = ast::Return { expression }; 84 | let span = merge(&start_span, &end_span); 85 | Ok(comp.new_statement(ast::Statement::Return(statement), span)) 86 | } 87 | 88 | fn parse_call(input: &mut ParseInput, comp: &mut Component) -> Result { 89 | let ident = parse_ident(input, comp)?; 90 | let start_span = comp.name_span(ident); 91 | input.assert_next(Token::LParen, "Function arguments")?; 92 | 93 | let mut args = Vec::new(); 94 | loop { 95 | if input.next_if(Token::RParen).is_some() { 96 | break; 97 | } 98 | 99 | args.push(parse_expression(input, comp)?); 100 | 101 | let token = input.next()?; 102 | match token.token { 103 | Token::Comma => continue, 104 | Token::RParen => break, 105 | _ => return Err(input.unexpected_token("Argument list")), 106 | } 107 | } 108 | 109 | let end_span = input.assert_next(Token::Semicolon, "Statements must end with `;`")?; 110 | 111 | let statement = Statement::Call(Call { ident, args }); 112 | let span = merge(&start_span, &end_span); 113 | 114 | Ok(comp.new_statement(statement, span)) 115 | } 116 | 117 | fn parse_assign(input: &mut ParseInput, comp: &mut Component) -> Result { 118 | let ident = parse_ident(input, comp)?; 119 | let start_span = comp.name_span(ident); 120 | let err_no_assign = "Expected '=' when parsing assignment statement"; 121 | input.assert_next(Token::Assign, err_no_assign)?; 122 | let expression = parse_expression(input, comp)?; 123 | let end_span = input.assert_next(Token::Semicolon, "Semicolon ';'")?; 124 | 125 | let statement = ast::Assign { ident, expression }; 126 | let span = merge(&start_span, &end_span); 127 | Ok(comp.new_statement(ast::Statement::Assign(statement), span)) 128 | } 129 | 130 | fn parse_if(input: &mut ParseInput, comp: &mut Component) -> Result { 131 | let start_span = input.assert_next(Token::If, "If keyword 'if'")?; 132 | let condition = parse_expression(input, comp)?; 133 | let (block, end_span) = parse_block(input, comp)?; 134 | 135 | let statement = ast::If { condition, block }; 136 | let span = merge(&start_span, &end_span); 137 | Ok(comp.new_statement(ast::Statement::If(statement), span)) 138 | } 139 | 140 | #[cfg(test)] 141 | mod tests { 142 | use claw_common::UnwrapPretty; 143 | 144 | use super::*; 145 | use crate::make_input; 146 | 147 | #[test] 148 | fn test_parse_block_empty() { 149 | let source = "{}"; 150 | let (src, mut input) = make_input(source); 151 | let mut comp = Component::new(src); 152 | let _assign_stmt = parse_block(&mut input, &mut comp).unwrap_pretty(); 153 | assert!(input.done()); 154 | } 155 | 156 | #[test] 157 | fn test_parse_block() { 158 | let source = "{a = 0;}"; 159 | let (src, mut input) = make_input(source); 160 | let mut comp = Component::new(src); 161 | let _assign_stmt = parse_block(&mut input, &mut comp).unwrap_pretty(); 162 | assert!(input.done()); 163 | } 164 | 165 | #[test] 166 | fn test_parse_return() { 167 | let source = "return 0;"; 168 | let (src, mut input) = make_input(source); 169 | let mut comp = Component::new(src); 170 | let _return_stmt = parse_return(&mut input, &mut comp).unwrap_pretty(); 171 | assert!(input.done()); 172 | } 173 | 174 | #[test] 175 | fn test_parse_assign() { 176 | let source = "a = 0;"; 177 | let (src, mut input) = make_input(source); 178 | let mut comp = Component::new(src); 179 | let _assign_stmt = parse_assign(&mut input, &mut comp).unwrap_pretty(); 180 | assert!(input.done()); 181 | } 182 | 183 | #[test] 184 | fn test_parse_let() { 185 | let source = "let start = now();"; 186 | let (src, mut input) = make_input(source); 187 | let mut comp = Component::new(src); 188 | let _let_stmt = parse_let(&mut input, &mut comp).unwrap_pretty(); 189 | assert!(input.done()); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /crates/parser/src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::Token; 2 | use crate::{ParseInput, ParserError}; 3 | use ast::{Component, PrimitiveType, TypeId, ValType}; 4 | use claw_ast as ast; 5 | 6 | pub fn parse_valtype(input: &mut ParseInput, comp: &mut Component) -> Result { 7 | let next = input.next()?; 8 | let span = next.span; 9 | let valtype = match next.token { 10 | Token::Bool => ValType::Primitive(PrimitiveType::Bool), 11 | // Unsigned Integers 12 | Token::U8 => ValType::Primitive(PrimitiveType::U8), 13 | Token::U16 => ValType::Primitive(PrimitiveType::U16), 14 | Token::U32 => ValType::Primitive(PrimitiveType::U32), 15 | Token::U64 => ValType::Primitive(PrimitiveType::U64), 16 | // Signed Integers 17 | Token::S8 => ValType::Primitive(PrimitiveType::S8), 18 | Token::S16 => ValType::Primitive(PrimitiveType::S16), 19 | Token::S32 => ValType::Primitive(PrimitiveType::S32), 20 | Token::S64 => ValType::Primitive(PrimitiveType::S64), 21 | // Floats 22 | Token::F32 => ValType::Primitive(PrimitiveType::F32), 23 | Token::F64 => ValType::Primitive(PrimitiveType::F64), 24 | // String 25 | Token::String => ValType::Primitive(PrimitiveType::String), 26 | _ => return Err(input.unexpected_token("Not a legal type")), 27 | }; 28 | let name_id = comp.new_type(valtype, span); 29 | Ok(name_id) 30 | } 31 | -------------------------------------------------------------------------------- /crates/resolver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claw-resolver" 3 | description = "The Claw language name and type resolver" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | license = { workspace = true } 7 | edition = { workspace = true } 8 | repository = { workspace = true } 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | cranelift-entity = { workspace = true } 14 | miette = { workspace = true } 15 | thiserror = { workspace = true } 16 | claw-common = { workspace = true } 17 | claw-ast = { workspace = true } 18 | wit-parser = { workspace = true } 19 | -------------------------------------------------------------------------------- /crates/resolver/src/expression.rs: -------------------------------------------------------------------------------- 1 | use ast::ExpressionId; 2 | use claw_ast as ast; 3 | 4 | use crate::types::{ResolvedType, RESOLVED_BOOL}; 5 | use crate::{FunctionResolver, ItemId, ResolverError}; 6 | 7 | pub(crate) trait ResolveExpression { 8 | /// Walk the AST from this node down setting up the resolver. 9 | /// 10 | /// Implementations must 11 | /// * Call [FunctionResolver::define_name] when introducing new names 12 | /// * Call [FunctionResolver::use_name] when using a name 13 | /// * Call [FunctionResolver::setup_child_expression] on each expression that is a child of this one. 14 | /// 15 | /// Implementations may 16 | /// * Call [FunctionResolver::set_expr_type] if the type of an expression is known. 17 | fn setup_resolve( 18 | &self, 19 | expression: ExpressionId, 20 | resolver: &mut FunctionResolver, 21 | ) -> Result<(), ResolverError> { 22 | _ = (expression, resolver); 23 | Ok(()) 24 | } 25 | 26 | /// In a successful type resolution, this function will be called 27 | /// exactly once when the type of this expression is known. 28 | fn on_resolved( 29 | &self, 30 | rtype: ResolvedType, 31 | expression: ExpressionId, 32 | resolver: &mut FunctionResolver, 33 | ) -> Result<(), ResolverError> { 34 | _ = (rtype, expression, resolver); 35 | Ok(()) 36 | } 37 | 38 | /// In a successful type resolution, this function will be called 39 | /// once for each child of this expression. 40 | fn on_child_resolved( 41 | &self, 42 | rtype: ResolvedType, 43 | expression: ExpressionId, 44 | resolver: &mut FunctionResolver, 45 | ) -> Result<(), ResolverError> { 46 | _ = (rtype, expression, resolver); 47 | Ok(()) 48 | } 49 | } 50 | 51 | macro_rules! gen_resolve_expression { 52 | ([$( $expr_type:ident ),*]) => { 53 | impl ResolveExpression for ast::Expression { 54 | fn setup_resolve( 55 | &self, 56 | expression: ExpressionId, 57 | resolver: &mut FunctionResolver, 58 | ) -> Result<(), ResolverError> { 59 | match self { 60 | $(ast::Expression::$expr_type(inner) => { 61 | let inner: &dyn ResolveExpression = inner; 62 | inner.setup_resolve(expression, resolver) 63 | },)* 64 | } 65 | } 66 | 67 | fn on_resolved(&self, 68 | rtype: ResolvedType, 69 | expression: ExpressionId, 70 | resolver: &mut FunctionResolver, 71 | ) -> Result<(), ResolverError> { 72 | match self { 73 | $(ast::Expression::$expr_type(inner) => inner.on_resolved(rtype, expression, resolver),)* 74 | } 75 | } 76 | 77 | fn on_child_resolved(&self, 78 | rtype: ResolvedType, 79 | expression: ExpressionId, 80 | resolver: &mut FunctionResolver, 81 | ) -> Result<(), ResolverError> { 82 | match self { 83 | $(ast::Expression::$expr_type(inner) => inner.on_child_resolved(rtype, expression, resolver),)* 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | gen_resolve_expression!([Identifier, Literal, Enum, Call, Unary, Binary]); 91 | 92 | impl ResolveExpression for ast::Identifier { 93 | fn setup_resolve( 94 | &self, 95 | expression: ExpressionId, 96 | resolver: &mut FunctionResolver, 97 | ) -> Result<(), ResolverError> { 98 | let item = resolver.use_name(self.ident)?; 99 | match item { 100 | ItemId::Global(global) => { 101 | let global = resolver.component.get_global(global); 102 | resolver.set_expr_type(expression, ResolvedType::Defined(global.type_id)); 103 | } 104 | ItemId::Param(param) => { 105 | let param_type = *resolver.params.get(param).unwrap(); 106 | resolver.set_expr_type(expression, ResolvedType::Defined(param_type)); 107 | } 108 | ItemId::Local(local) => resolver.use_local(local, expression), 109 | _ => {} 110 | } 111 | Ok(()) 112 | } 113 | 114 | fn on_resolved( 115 | &self, 116 | rtype: ResolvedType, 117 | _expression: ExpressionId, 118 | resolver: &mut FunctionResolver, 119 | ) -> Result<(), ResolverError> { 120 | let item = resolver.lookup_name(self.ident)?; 121 | match item { 122 | ItemId::Local(local) => resolver.set_local_type(local, rtype), 123 | _ => {} 124 | } 125 | Ok(()) 126 | } 127 | } 128 | 129 | impl ResolveExpression for ast::Literal { 130 | fn setup_resolve( 131 | &self, 132 | expression: ExpressionId, 133 | resolver: &mut FunctionResolver, 134 | ) -> Result<(), ResolverError> { 135 | match self { 136 | ast::Literal::String(_) => { 137 | resolver.set_expr_type( 138 | expression, 139 | ResolvedType::Primitive(ast::PrimitiveType::String), 140 | ); 141 | } 142 | _ => {} 143 | } 144 | Ok(()) 145 | } 146 | } 147 | 148 | impl ResolveExpression for ast::EnumLiteral { 149 | fn setup_resolve( 150 | &self, 151 | expression: ExpressionId, 152 | resolver: &mut FunctionResolver, 153 | ) -> Result<(), ResolverError> { 154 | let item = resolver.use_name(self.enum_name)?; 155 | match item { 156 | ItemId::Type(rtype) => { 157 | resolver.set_expr_type(expression, rtype); 158 | } 159 | _ => panic!("Can only use literals for enums"), 160 | }; 161 | Ok(()) 162 | } 163 | } 164 | 165 | impl ResolveExpression for ast::Call { 166 | fn setup_resolve( 167 | &self, 168 | expression: ExpressionId, 169 | resolver: &mut FunctionResolver, 170 | ) -> Result<(), ResolverError> { 171 | let item = resolver.use_name(self.ident)?; 172 | let (params, results): (Vec<_>, _) = match item { 173 | ItemId::ImportFunc(import_func) => { 174 | let import_func = &resolver.imports.funcs[import_func]; 175 | let params = import_func.params.iter().map(|(_name, rtype)| *rtype); 176 | let results = import_func.results.unwrap(); 177 | (params.collect(), results) 178 | } 179 | ItemId::Function(func) => { 180 | let func = &resolver.component.get_function(func); 181 | let params = func 182 | .params 183 | .iter() 184 | .map(|(_name, type_id)| ResolvedType::Defined(*type_id)); 185 | let results = ResolvedType::Defined(*func.results.as_ref().unwrap()); 186 | (params.collect(), results) 187 | } 188 | _ => panic!("Can only call functions"), 189 | }; 190 | assert_eq!(params.len(), self.args.len()); 191 | for (arg, rtype) in self.args.iter().copied().zip(params.into_iter()) { 192 | resolver.setup_child_expression(expression, arg)?; 193 | resolver.set_expr_type(arg, rtype); 194 | } 195 | 196 | resolver.set_expr_type(expression, results); 197 | 198 | Ok(()) 199 | } 200 | } 201 | 202 | impl ResolveExpression for ast::UnaryExpression { 203 | fn setup_resolve( 204 | &self, 205 | expression: ExpressionId, 206 | resolver: &mut FunctionResolver, 207 | ) -> Result<(), ResolverError> { 208 | resolver.setup_child_expression(expression, self.inner) 209 | } 210 | 211 | fn on_resolved( 212 | &self, 213 | rtype: ResolvedType, 214 | _expression: ExpressionId, 215 | resolver: &mut FunctionResolver, 216 | ) -> Result<(), ResolverError> { 217 | resolver.set_expr_type(self.inner, rtype); 218 | Ok(()) 219 | } 220 | 221 | fn on_child_resolved( 222 | &self, 223 | rtype: ResolvedType, 224 | expression: ExpressionId, 225 | resolver: &mut FunctionResolver, 226 | ) -> Result<(), ResolverError> { 227 | resolver.set_expr_type(expression, rtype); 228 | Ok(()) 229 | } 230 | } 231 | 232 | // Binary Operators 233 | 234 | impl ResolveExpression for ast::BinaryExpression { 235 | fn setup_resolve( 236 | &self, 237 | expression: ExpressionId, 238 | resolver: &mut FunctionResolver, 239 | ) -> Result<(), ResolverError> { 240 | if self.is_relation() { 241 | resolver.set_expr_type(expression, RESOLVED_BOOL); 242 | } 243 | resolver.setup_child_expression(expression, self.left)?; 244 | resolver.setup_child_expression(expression, self.right)?; 245 | Ok(()) 246 | } 247 | 248 | fn on_resolved( 249 | &self, 250 | rtype: ResolvedType, 251 | _expression: ExpressionId, 252 | resolver: &mut FunctionResolver, 253 | ) -> Result<(), ResolverError> { 254 | if !self.is_relation() { 255 | resolver.set_expr_type(self.left, rtype); 256 | resolver.set_expr_type(self.right, rtype); 257 | } 258 | Ok(()) 259 | } 260 | 261 | fn on_child_resolved( 262 | &self, 263 | rtype: ResolvedType, 264 | expression: ExpressionId, 265 | resolver: &mut FunctionResolver, 266 | ) -> Result<(), ResolverError> { 267 | if !self.is_relation() { 268 | resolver.set_expr_type(expression, rtype); 269 | } 270 | 271 | let left = resolver.expression_types.get(&self.left).copied(); 272 | let right = resolver.expression_types.get(&self.right).copied(); 273 | 274 | match (left, right) { 275 | (Some(_left), Some(_right)) => { 276 | // Both types known, do nothing 277 | } 278 | (Some(left), None) => { 279 | resolver.set_expr_type(self.right, left); 280 | } 281 | (None, Some(right)) => { 282 | resolver.set_expr_type(self.left, right); 283 | } 284 | (None, None) => { 285 | // Neither types known... how did we get here? 286 | unreachable!("If a child has been resolved, at least one child shouldn't be None") 287 | } 288 | } 289 | 290 | Ok(()) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /crates/resolver/src/imports.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use ast::NameId; 4 | use claw_ast as ast; 5 | 6 | use crate::types::ResolvedType; 7 | use crate::wit::{self, InterfaceId}; 8 | use crate::ResolverError; 9 | use cranelift_entity::{entity_impl, PrimaryMap}; 10 | 11 | #[derive(Default)] 12 | pub struct ImportResolver { 13 | pub mapping: HashMap, 14 | pub types: PrimaryMap, 15 | pub funcs: PrimaryMap, 16 | 17 | pub interfaces: Vec, 18 | pub loose_funcs: Vec, 19 | } 20 | 21 | #[derive(Copy, Clone, Debug)] 22 | pub enum ImportItemId { 23 | Type(ResolvedType), 24 | Func(ImportFuncId), 25 | } 26 | 27 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 28 | pub struct ImportFuncId(u32); 29 | entity_impl!(ImportFuncId, "import-func"); 30 | 31 | #[derive(Clone, Debug)] 32 | pub struct ImportFunction { 33 | pub alias: String, 34 | pub name: String, 35 | pub params: Vec<(String, ResolvedType)>, 36 | pub results: Option, 37 | } 38 | 39 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 40 | pub struct ImportTypeId(u32); 41 | entity_impl!(ImportTypeId, "import-type"); 42 | 43 | pub enum ImportType { 44 | Enum(ImportEnum), 45 | } 46 | 47 | pub struct ImportEnum { 48 | pub name: String, 49 | pub cases: Vec, 50 | } 51 | 52 | impl ImportResolver { 53 | pub fn resolve_imports( 54 | &mut self, 55 | comp: &ast::Component, 56 | wit: &wit::ResolvedWit, 57 | ) -> Result<(), ResolverError> { 58 | for (_, import) in comp.iter_imports() { 59 | match import { 60 | ast::Import::Plain(import) => { 61 | self.resolve_plain_import(import, comp); 62 | } 63 | ast::Import::ImportFrom(import) => { 64 | self.resolve_import_from(import, comp, wit)?; 65 | } 66 | } 67 | } 68 | Ok(()) 69 | } 70 | 71 | pub fn resolve_plain_import(&mut self, import: &ast::PlainImport, comp: &ast::Component) { 72 | match &import.external_type { 73 | ast::ExternalType::Function(fn_type) => { 74 | self.resolve_plain_import_func(import.ident, import.alias, fn_type, comp); 75 | } 76 | }; 77 | } 78 | 79 | fn resolve_plain_import_func( 80 | &mut self, 81 | name: NameId, 82 | alias: Option, 83 | fn_type: &ast::FnType, 84 | comp: &ast::Component, 85 | ) { 86 | let name = comp.get_name(name); 87 | 88 | let params = fn_type 89 | .params 90 | .iter() 91 | .map(|(name, type_id)| { 92 | let name = comp.get_name(*name).to_owned(); 93 | (name, ResolvedType::Defined(*type_id)) 94 | }) 95 | .collect(); 96 | 97 | let results = fn_type.results.map(ResolvedType::Defined); 98 | 99 | let alias = match alias { 100 | Some(alias) => comp.get_name(alias), 101 | None => name, 102 | }; 103 | let import_func = ImportFunction { 104 | alias: alias.to_owned(), 105 | name: name.to_owned(), 106 | params, 107 | results, 108 | }; 109 | 110 | let import_func_id = self.funcs.push(import_func); 111 | let import_item_id = ImportItemId::Func(import_func_id); 112 | self.mapping.insert(alias.to_owned(), import_item_id); 113 | self.loose_funcs.push(import_func_id); 114 | } 115 | 116 | pub fn resolve_import_from( 117 | &mut self, 118 | import: &ast::ImportFrom, 119 | comp: &ast::Component, 120 | wit: &wit::ResolvedWit, 121 | ) -> Result<(), ResolverError> { 122 | let interface_id = wit.lookup_interface(&import.package, &import.interface)?; 123 | 124 | let mut resolver = InterfaceResolver::new(interface_id, self, wit); 125 | let mut bindings = Vec::new(); 126 | for (name, alias) in import.items.iter() { 127 | let name = comp.get_name(*name); 128 | let item_id = resolver.resolve_name(name).unwrap(); 129 | let name = match alias { 130 | Some(name) => comp.get_name(*name).to_owned(), 131 | None => name.to_owned(), 132 | }; 133 | bindings.push((name, item_id)); 134 | } 135 | 136 | let resolved = resolver.finalize(); 137 | self.interfaces.push(resolved); 138 | 139 | for (name, item) in bindings { 140 | self.mapping.insert(name, item); 141 | } 142 | 143 | Ok(()) 144 | } 145 | } 146 | 147 | pub struct InterfaceResolver<'ctx> { 148 | wit: &'ctx wit::ResolvedWit, 149 | imports: &'ctx mut ImportResolver, 150 | 151 | interface_id: InterfaceId, 152 | interface: &'ctx wit::Interface, 153 | 154 | resolved_types: HashMap, 155 | items: Vec, 156 | } 157 | 158 | pub struct ResolvedInterface { 159 | pub interface_id: InterfaceId, 160 | pub name: String, 161 | pub items: Vec, 162 | } 163 | 164 | impl<'ctx> InterfaceResolver<'ctx> { 165 | pub fn new( 166 | interface_id: InterfaceId, 167 | imports: &'ctx mut ImportResolver, 168 | wit: &'ctx wit::ResolvedWit, 169 | ) -> Self { 170 | let interface = wit.get_interface(interface_id); 171 | 172 | Self { 173 | wit, 174 | imports, 175 | interface_id, 176 | interface, 177 | resolved_types: Default::default(), 178 | items: Default::default(), 179 | } 180 | } 181 | 182 | pub fn finalize(self) -> ResolvedInterface { 183 | ResolvedInterface { 184 | interface_id: self.interface_id, 185 | name: self.wit.resolve.id_of(self.interface_id).unwrap(), 186 | items: self.items, 187 | } 188 | } 189 | 190 | pub fn resolve_name(&mut self, name: &str) -> Option { 191 | if let Some(func) = self.interface.functions.get(name) { 192 | return Some(self.resolve_import_func(name, func)); 193 | } 194 | if let Some(type_id) = self.interface.types.get(name) { 195 | return Some(ImportItemId::Type(self.resolve_type_id(*type_id))); 196 | } 197 | None 198 | } 199 | 200 | fn resolve_import_func(&mut self, name: &str, func: &wit::Function) -> ImportItemId { 201 | let mut params = Vec::new(); 202 | for (param_name, param_type) in func.params.iter() { 203 | let rtype = self.resolve_type(param_type); 204 | params.push((param_name.clone(), rtype)); 205 | } 206 | 207 | let results = match &func.results { 208 | wit_parser::Results::Named(named_types) => { 209 | assert_eq!(named_types.len(), 0); // Can only handle "empty" named types 210 | None 211 | } 212 | wit_parser::Results::Anon(result_type) => Some(self.resolve_type(result_type)), 213 | }; 214 | 215 | let import_func = ImportFunction { 216 | alias: name.to_owned(), // TODO fix 217 | name: name.to_owned(), 218 | params, 219 | results, 220 | }; 221 | let import_func_id = self.imports.funcs.push(import_func); 222 | self.items.push(ImportItemId::Func(import_func_id)); 223 | ImportItemId::Func(import_func_id) 224 | } 225 | 226 | fn resolve_type(&mut self, type_: &wit::Type) -> ResolvedType { 227 | type PType = ast::PrimitiveType; 228 | match type_ { 229 | // Primitives 230 | wit::Type::Bool => ResolvedType::Primitive(PType::Bool), 231 | wit::Type::U8 => ResolvedType::Primitive(PType::U8), 232 | wit::Type::U16 => ResolvedType::Primitive(PType::U16), 233 | wit::Type::U32 => ResolvedType::Primitive(PType::U32), 234 | wit::Type::U64 => ResolvedType::Primitive(PType::U64), 235 | wit::Type::S8 => ResolvedType::Primitive(PType::S8), 236 | wit::Type::S16 => ResolvedType::Primitive(PType::S16), 237 | wit::Type::S32 => ResolvedType::Primitive(PType::S32), 238 | wit::Type::S64 => ResolvedType::Primitive(PType::S64), 239 | wit::Type::F32 => ResolvedType::Primitive(PType::F32), 240 | wit::Type::F64 => ResolvedType::Primitive(PType::F64), 241 | wit::Type::Char => todo!(), 242 | wit::Type::String => ResolvedType::Primitive(PType::String), 243 | wit::Type::Id(id) => { 244 | if let Some(rtype) = self.resolved_types.get(id) { 245 | *rtype 246 | } else { 247 | self.resolve_type_id(*id) 248 | } 249 | } 250 | } 251 | } 252 | 253 | fn resolve_type_id(&mut self, type_id: wit::TypeId) -> ResolvedType { 254 | let type_def = self.wit.resolve.types.get(type_id).unwrap(); 255 | let name = type_def.name.as_ref().unwrap().to_owned(); 256 | assert_eq!(type_def.owner, wit::TypeOwner::Interface(self.interface_id)); 257 | // Construct the ImportType 258 | let rtype = match &type_def.kind { 259 | wit::TypeDefKind::Enum(enum_type) => { 260 | let name = name.clone(); 261 | let cases = enum_type 262 | .cases 263 | .iter() 264 | .map(|case| &case.name) 265 | .cloned() 266 | .collect(); 267 | let import_enum = ImportEnum { name, cases }; 268 | let import_type = ImportType::Enum(import_enum); 269 | let import_type_id = self.imports.types.push(import_type); 270 | ResolvedType::Import(import_type_id) 271 | } 272 | wit::TypeDefKind::Type(t) => { 273 | return self.resolve_type(t); 274 | } 275 | a => panic!("Unsupported import type kind {:?}", a), 276 | }; 277 | // Record item id and resolved type 278 | self.resolved_types.insert(type_id, rtype); 279 | // Record item in interface ordering 280 | let import_item_id = ImportItemId::Type(rtype); 281 | self.items.push(import_item_id); 282 | rtype 283 | } 284 | } 285 | 286 | pub struct ResolvedImports {} 287 | -------------------------------------------------------------------------------- /crates/resolver/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::single_match)] 2 | 3 | mod expression; 4 | mod function; 5 | mod imports; 6 | mod statement; 7 | pub mod types; 8 | pub mod wit; 9 | 10 | use ast::{FunctionId, GlobalId}; 11 | use claw_ast as ast; 12 | use claw_common::Source; 13 | 14 | use std::collections::HashMap; 15 | use wit::{ResolvedWit, WitError}; 16 | 17 | use miette::{Diagnostic, SourceSpan}; 18 | use thiserror::Error; 19 | 20 | pub use function::*; 21 | pub use imports::*; 22 | pub use types::*; 23 | 24 | pub struct ResolvedComponent { 25 | pub wit: ResolvedWit, 26 | pub global_vals: HashMap, 27 | pub imports: ImportResolver, 28 | pub funcs: HashMap, 29 | } 30 | 31 | #[derive(Clone, Copy, Debug)] 32 | pub enum ItemId { 33 | ImportFunc(ImportFuncId), 34 | Type(ResolvedType), 35 | Global(GlobalId), 36 | Param(ParamId), 37 | Local(LocalId), 38 | Function(FunctionId), 39 | } 40 | 41 | #[derive(Error, Debug, Diagnostic)] 42 | pub enum ResolverError { 43 | #[error("Failed to resolve")] 44 | Base { 45 | #[source_code] 46 | src: Source, 47 | #[label("This bit")] 48 | span: SourceSpan, 49 | }, 50 | #[error("Conflicting types inferred for expression {type_a} != {type_b}")] 51 | TypeConflict { 52 | #[source_code] 53 | src: Source, 54 | #[label("This bit")] 55 | span: SourceSpan, 56 | 57 | type_a: ResolvedType, 58 | type_b: ResolvedType, 59 | }, 60 | #[error("Failed to resolve name \"{ident}\"")] 61 | NameError { 62 | #[source_code] 63 | src: Source, 64 | #[label("Name referenced here")] 65 | span: SourceSpan, 66 | ident: String, 67 | }, 68 | #[error("Assigned to immutable variable \"{ident}\"")] 69 | AssignedToImmutable { 70 | #[source_code] 71 | src: Source, 72 | #[label("Defined here")] 73 | defined_span: SourceSpan, 74 | #[label("Assigned here")] 75 | assigned_span: SourceSpan, 76 | ident: String, 77 | }, 78 | #[error("Function call with wrong number of arguments \"{ident}\"")] 79 | CallArgumentsMismatch { 80 | #[source_code] 81 | src: Source, 82 | #[label("Here")] 83 | span: SourceSpan, 84 | ident: String, 85 | }, 86 | #[error("{0} is not yet supported")] 87 | NotYetSupported(String), 88 | 89 | #[error(transparent)] 90 | #[diagnostic(transparent)] 91 | Wit(#[from] WitError), 92 | } 93 | 94 | pub fn resolve( 95 | comp: &ast::Component, 96 | wit: wit::ResolvedWit, 97 | ) -> Result { 98 | let mut mappings: HashMap = Default::default(); 99 | 100 | let mut imports = ImportResolver::default(); 101 | imports.resolve_imports(comp, &wit)?; 102 | for (name, import) in imports.mapping.iter() { 103 | match import { 104 | ImportItemId::Type(rtype) => { 105 | mappings.insert(name.to_owned(), ItemId::Type(*rtype)); 106 | } 107 | ImportItemId::Func(func) => { 108 | mappings.insert(name.to_owned(), ItemId::ImportFunc(*func)); 109 | } 110 | } 111 | } 112 | 113 | for (id, global) in comp.iter_globals() { 114 | let name = comp.get_name(global.ident); 115 | mappings.insert(name.to_owned(), ItemId::Global(id)); 116 | } 117 | for (id, function) in comp.iter_functions() { 118 | let name = comp.get_name(function.ident); 119 | mappings.insert(name.to_owned(), ItemId::Function(id)); 120 | } 121 | 122 | let mut global_vals: HashMap = HashMap::new(); 123 | 124 | for (id, global) in comp.iter_globals() { 125 | let global_val = match comp.get_expression(global.init_value) { 126 | ast::Expression::Literal(literal) => literal.clone(), 127 | _ => panic!("Only literal expressions allowed in global initializer"), 128 | }; 129 | global_vals.insert(id, global_val); 130 | } 131 | 132 | let mut funcs: HashMap = HashMap::new(); 133 | 134 | for (id, function) in comp.iter_functions() { 135 | let resolver = FunctionResolver::new(comp, &imports, function, &mappings); 136 | funcs.insert(id, resolver.resolve()?); 137 | } 138 | 139 | Ok(ResolvedComponent { 140 | wit, 141 | global_vals, 142 | imports, 143 | funcs, 144 | }) 145 | } 146 | -------------------------------------------------------------------------------- /crates/resolver/src/statement.rs: -------------------------------------------------------------------------------- 1 | use claw_ast as ast; 2 | 3 | use crate::types::{ResolvedType, RESOLVED_BOOL}; 4 | use crate::{FunctionResolver, ItemId, LocalInfo, ResolverError}; 5 | 6 | pub(crate) trait ResolveStatement { 7 | /// Set up locals 8 | /// * Add them to resolver.locals 9 | /// * Identify the local_uses 10 | /// 11 | /// Perform name resolution 12 | /// * Updates resolver.mapping as it goes 13 | /// * Links identifiers to their targets in resolver.bindings 14 | /// 15 | /// Record expression parents 16 | fn setup_resolve(&self, resolver: &mut FunctionResolver) -> Result<(), ResolverError>; 17 | } 18 | 19 | macro_rules! gen_resolve_statement { 20 | ([$( $expr_type:ident ),*]) => { 21 | impl ResolveStatement for ast::Statement { 22 | fn setup_resolve( 23 | &self, 24 | resolver: &mut FunctionResolver, 25 | ) -> Result<(), ResolverError> { 26 | match self { 27 | $(ast::Statement::$expr_type(inner) => { 28 | let inner: &dyn ResolveStatement = inner; 29 | inner.setup_resolve(resolver) 30 | },)* 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | gen_resolve_statement!([Let, Assign, Call, If, Return]); 38 | 39 | impl ResolveStatement for ast::Let { 40 | fn setup_resolve(&self, resolver: &mut FunctionResolver) -> Result<(), ResolverError> { 41 | let info = LocalInfo { 42 | ident: self.ident.to_owned(), 43 | mutable: self.mutable, 44 | annotation: self.annotation.to_owned(), 45 | }; 46 | let local = resolver.locals.push(info); 47 | let span = resolver.component.name_span(self.ident); 48 | resolver.local_spans.insert(local, span); 49 | let item = ItemId::Local(local); 50 | resolver.define_name(self.ident, item)?; 51 | 52 | resolver.setup_expression(self.expression)?; 53 | resolver.use_local(local, self.expression); 54 | 55 | if let Some(annotation) = self.annotation { 56 | resolver.set_local_type(local, ResolvedType::Defined(annotation)) 57 | } 58 | 59 | Ok(()) 60 | } 61 | } 62 | 63 | impl ResolveStatement for ast::Assign { 64 | fn setup_resolve(&self, resolver: &mut FunctionResolver) -> Result<(), ResolverError> { 65 | let item = resolver.use_name(self.ident)?; 66 | 67 | match item { 68 | ItemId::Global(global) => { 69 | let global = resolver.component.get_global(global); 70 | resolver.set_expr_type(self.expression, ResolvedType::Defined(global.type_id)); 71 | 72 | if !global.mutable { 73 | return Err(ResolverError::AssignedToImmutable { 74 | src: resolver.component.source(), 75 | defined_span: resolver.component.name_span(global.ident), 76 | assigned_span: resolver.component.name_span(self.ident), 77 | ident: resolver.component.get_name(self.ident).to_string(), 78 | }); 79 | } 80 | } 81 | ItemId::Param(param) => { 82 | let param_type = *resolver.params.get(param).unwrap(); 83 | resolver.set_expr_type(self.expression, ResolvedType::Defined(param_type)); 84 | } 85 | ItemId::Local(local) => { 86 | resolver.use_local(local, self.expression); 87 | 88 | let local = resolver.locals.get(local).unwrap(); 89 | 90 | if !local.mutable { 91 | return Err(ResolverError::AssignedToImmutable { 92 | src: resolver.component.source(), 93 | defined_span: resolver.component.name_span(local.ident), 94 | assigned_span: resolver.component.name_span(self.ident), 95 | ident: resolver.component.get_name(self.ident).to_string(), 96 | }); 97 | } 98 | } 99 | _ => {} 100 | } 101 | 102 | resolver.setup_expression(self.expression) 103 | } 104 | } 105 | 106 | impl ResolveStatement for ast::Call { 107 | fn setup_resolve(&self, resolver: &mut FunctionResolver) -> Result<(), ResolverError> { 108 | resolver.use_name(self.ident)?; 109 | for arg in self.args.iter() { 110 | resolver.setup_expression(*arg)?; 111 | } 112 | Ok(()) 113 | } 114 | } 115 | 116 | impl ResolveStatement for ast::If { 117 | fn setup_resolve(&self, resolver: &mut FunctionResolver) -> Result<(), ResolverError> { 118 | resolver.set_expr_type(self.condition, RESOLVED_BOOL); 119 | resolver.setup_expression(self.condition)?; 120 | resolver.setup_block(&self.block) 121 | } 122 | } 123 | 124 | impl ResolveStatement for ast::Return { 125 | fn setup_resolve(&self, resolver: &mut FunctionResolver) -> Result<(), ResolverError> { 126 | let return_type = resolver.function.results; 127 | match (return_type, self.expression) { 128 | (Some(return_type), Some(expression)) => { 129 | let rtype = ResolvedType::Defined(return_type); 130 | resolver.set_expr_type(expression, rtype); 131 | resolver.setup_expression(expression)?; 132 | } 133 | (Some(_), None) => panic!( 134 | "Return statements must contain an expression when function has a return type" 135 | ), 136 | (None, Some(_)) => panic!( 137 | "Return statements can't contain an expression when function has no return type" 138 | ), 139 | (None, None) => { 140 | // No child expression or return type, so do nothing 141 | } 142 | } 143 | 144 | Ok(()) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /crates/resolver/src/types.rs: -------------------------------------------------------------------------------- 1 | use ast::TypeId; 2 | use claw_ast as ast; 3 | 4 | use crate::imports::ImportTypeId; 5 | 6 | #[derive(Clone, Copy, Debug)] 7 | pub enum ResolvedType { 8 | Primitive(ast::PrimitiveType), 9 | Import(ImportTypeId), 10 | Defined(TypeId), 11 | } 12 | 13 | impl From for ResolvedType { 14 | fn from(value: TypeId) -> Self { 15 | ResolvedType::Defined(value) 16 | } 17 | } 18 | 19 | pub const RESOLVED_BOOL: ResolvedType = ResolvedType::Primitive(ast::PrimitiveType::Bool); 20 | 21 | impl std::fmt::Display for ResolvedType { 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 23 | match self { 24 | ResolvedType::Primitive(p) => (p as &dyn std::fmt::Debug).fmt(f), 25 | ResolvedType::Import(_) => write!(f, "imported type"), 26 | ResolvedType::Defined(v) => (v as &dyn std::fmt::Debug).fmt(f), 27 | } 28 | } 29 | } 30 | 31 | impl ResolvedType { 32 | pub fn type_eq(&self, other: &ResolvedType, comp: &ast::Component) -> bool { 33 | match (*self, *other) { 34 | // Both primitive 35 | (ResolvedType::Primitive(left), ResolvedType::Primitive(right)) => left == right, 36 | // Both valtype 37 | (ResolvedType::Defined(left), ResolvedType::Defined(right)) => { 38 | let l_valtype = comp.get_type(left); 39 | let r_valtype = comp.get_type(right); 40 | l_valtype.eq(r_valtype, comp) 41 | } 42 | // One primitive, other valtype 43 | (ResolvedType::Primitive(p), ResolvedType::Defined(v)) 44 | | (ResolvedType::Defined(v), ResolvedType::Primitive(p)) => { 45 | let valtype = comp.get_type(v); 46 | match valtype { 47 | ast::ValType::Primitive(p2) => p == *p2, 48 | _ => false, 49 | } 50 | } 51 | _ => todo!(), 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /crates/resolver/src/wit.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use miette::Diagnostic; 4 | 5 | pub use wit_parser::{ 6 | Function, Interface, InterfaceId, PackageName, Resolve, Type, TypeDef, TypeDefKind, TypeId, 7 | TypeOwner, 8 | }; 9 | 10 | #[derive(Error, Debug, Diagnostic)] 11 | pub enum WitError { 12 | #[error("Package {package} does not exist")] 13 | NoSuchPackage { package: PackageName }, 14 | #[error("Interface {interface} does not exist in package {package}")] 15 | NoSuchInterface { 16 | package: PackageName, 17 | interface: String, 18 | }, 19 | #[error("Item {item} does not exists in interface {interface}")] 20 | NoSuchItem { interface: String, item: String }, 21 | } 22 | 23 | #[derive(Debug)] 24 | pub struct ResolvedWit { 25 | pub resolve: Resolve, 26 | } 27 | 28 | pub enum WitItem { 29 | Func(Function), 30 | Type(), 31 | } 32 | 33 | impl ResolvedWit { 34 | pub fn new(resolve: Resolve) -> Self { 35 | Self { resolve } 36 | } 37 | 38 | pub fn lookup_interface( 39 | &self, 40 | package_name: &PackageName, 41 | interface: &String, 42 | ) -> Result { 43 | let package_id = self.resolve.package_names.get(package_name); 44 | let package_id = match package_id { 45 | Some(id) => *id, 46 | None => { 47 | return Err(WitError::NoSuchPackage { 48 | package: package_name.clone(), 49 | }) 50 | } 51 | }; 52 | 53 | let package = self.resolve.packages.get(package_id).unwrap(); 54 | 55 | let interface_id = package.interfaces.get(interface); 56 | let interface_id = match interface_id { 57 | Some(id) => *id, 58 | None => { 59 | return Err(WitError::NoSuchInterface { 60 | package: package_name.clone(), 61 | interface: interface.clone(), 62 | }) 63 | } 64 | }; 65 | 66 | Ok(interface_id) 67 | } 68 | 69 | pub fn get_interface(&self, interface_id: InterfaceId) -> &Interface { 70 | self.resolve.interfaces.get(interface_id).unwrap() 71 | } 72 | 73 | pub fn lookup_func(&self, interface_id: InterfaceId, name: &str) -> Option<&Function> { 74 | let interface = self.get_interface(interface_id); 75 | interface.functions.get(name) 76 | } 77 | 78 | pub fn lookup_type(&self, interface_id: InterfaceId, name: &str) -> Option<&TypeDef> { 79 | let interface = self.get_interface(interface_id); 80 | let type_id = interface.types.get(name)?; 81 | Some(self.resolve.types.get(*type_id).unwrap()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/bin.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::PathBuf, sync::Arc}; 2 | 3 | use clap::Parser; 4 | 5 | use claw_codegen::generate; 6 | use claw_common::OkPretty; 7 | use claw_parser::{parse, tokenize}; 8 | use claw_resolver::{resolve, wit::ResolvedWit}; 9 | use miette::NamedSource; 10 | use wit_parser::Resolve; 11 | 12 | #[derive(Parser, Debug)] 13 | struct Arguments { 14 | #[clap(subcommand)] 15 | command: Command, 16 | } 17 | 18 | #[derive(Parser, Debug)] 19 | enum Command { 20 | Compile(Compile), 21 | } 22 | 23 | #[derive(Parser, Debug)] 24 | struct Compile { 25 | #[clap(short, long)] 26 | input: PathBuf, 27 | #[clap(long)] 28 | wit: Option, 29 | #[clap(short, long)] 30 | output: PathBuf, 31 | } 32 | 33 | impl Compile { 34 | fn run(self) -> Option<()> { 35 | let file_name = self.input.file_name()?.to_string_lossy().to_string(); 36 | let file_string = std::fs::read_to_string(&self.input).ok()?; 37 | let src = Arc::new(NamedSource::new(file_name, file_string.clone())); 38 | 39 | let tokens = tokenize(src.clone(), &file_string).ok_pretty()?; 40 | 41 | let comp = parse(src.clone(), tokens).ok_pretty()?; 42 | 43 | let mut wit = Resolve::new(); 44 | if let Some(wit_path) = self.wit { 45 | wit.push_path(wit_path).unwrap(); 46 | } 47 | let wit = ResolvedWit::new(wit); 48 | let rcomp = resolve(&comp, wit).ok_pretty()?; 49 | 50 | let wasm = generate(&comp, &rcomp).ok_pretty()?; 51 | 52 | match fs::write(&self.output, wasm) { 53 | Ok(_) => println!("Done"), 54 | Err(err) => println!("Error: {:?}", err), 55 | } 56 | 57 | Some(()) 58 | } 59 | } 60 | 61 | fn main() { 62 | let args = Arguments::parse(); 63 | 64 | match args.command { 65 | Command::Compile(compile) => compile.run(), 66 | }; 67 | } 68 | --------------------------------------------------------------------------------