├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── src ├── compiler.ts ├── index.ts ├── methods.ts ├── module.ts ├── nodes.ts ├── reexport.ts ├── test │ ├── __snapshots__ │ │ ├── compiler.test.ts.snap │ │ └── tree.test.ts.snap │ ├── brainfuck.test.ts │ ├── compiler.test.ts │ ├── tree.test.ts │ └── tsconfig.json └── utils.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | build/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug.allowBreakpointsEverywhere": true, 3 | "editor.tabSize": 2, 4 | "files.eol": "\n", 5 | "editor.insertSpaces": true, 6 | "editor.defaultFormatter": "esbenp.prettier-vscode", 7 | "editor.formatOnSave": true 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 [yyyy] [name of copyright owner] 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wazum 2 | 3 | Wazum is a human-friendly compiler infrastructure library for WebAssembly. 4 | 5 | It's an alternative to Binaryen.js with the following benefits: 6 | 7 | ✍️ Wazum is **hand-written** and not machine-generated. 8 | 9 | 🧪 Thoroughly **tested** and **predictable**. 10 | 11 | 🪶 **Lightweight** and tree-shakeable. 12 | 13 | 🤯 Large degree of **flexibility** and **hackability** of the AST. 14 | 15 | 🍀 Full **type-safety**. And I mean **full**. Type correctness is preferred over type inference. 16 | 17 | ✨ Stellar documentation, QoL features, overall **great DX**. Give it a try! 18 | 19 | *note: the above is the end goal, Wazum is still a work in progress.* 20 | 21 | ```ts 22 | // Try Me! 23 | import { w } from 'wazum'; 24 | const m = new w.Module(); 25 | 26 | const add = w.func( 27 | 'add', 28 | { 29 | params: [['i32', 'a'], ['i32', 'b']], 30 | returnType: 'i32', 31 | locals: [] 32 | }, 33 | w.add('i32', w.local.get('i32', 'a'), w.local.get('i32', 'b')) 34 | ); 35 | 36 | m.addFunc(add); 37 | console.log(m.compile()); 38 | ``` 39 | 40 | ### Getting Started 41 | ```bash 42 | yarn add wazum 43 | // or 44 | npm i wazum 45 | ``` 46 | 47 | You'll find all the methods and types under: 48 | ```ts 49 | import { w } from 'wazum'; 50 | w. // let IntelliSense guide you! 51 | ``` 52 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | export default { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: 'src/test/tsconfig.json', 8 | }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wazum", 3 | "version": "0.0.13", 4 | "description": "A human-friendly compiler infrastructure library for WebAssembly", 5 | "repository": "https://github.com/judehunter/wazum", 6 | "author": "judehunter ", 7 | "license": "Apache 2.0", 8 | "type": "module", 9 | "source": "src/index.ts", 10 | "main": "./dist/index.cjs", 11 | "module": "./dist/index.js", 12 | "unpkg": "./dist/index.umd.js", 13 | "exports": { 14 | "types": "./dist/index.d.ts", 15 | "require": "./dist/index.js", 16 | "default": "./dist/index.modern.mjs" 17 | }, 18 | "types": "./dist/index.d.ts", 19 | "files": [ 20 | "dist/**.*" 21 | ], 22 | "scripts": { 23 | "test": "jest", 24 | "build": "microbundle" 25 | }, 26 | "devDependencies": { 27 | "@types/jest": "^28.1.8", 28 | "jest": "29.3.0", 29 | "microbundle": "^0.15.1", 30 | "ts-jest": "^28.0.8", 31 | "typescript": "^4.9.4", 32 | "wabt": "^1.0.32" 33 | }, 34 | "dependencies": { 35 | "ts-pattern": "^4.0.5" 36 | }, 37 | "volta": { 38 | "node": "19.4.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/compiler.ts: -------------------------------------------------------------------------------- 1 | import { Module } from './module'; 2 | import { 3 | Clz, 4 | Ctz, 5 | Popcnt, 6 | Add, 7 | Block, 8 | Call, 9 | CallIndirect, 10 | Const, 11 | DataType, 12 | DivSigned, 13 | DivUnsigned, 14 | Drop, 15 | IntegerDataType, 16 | Load, 17 | LocalGet, 18 | LocalSet, 19 | LocalTee, 20 | Mul, 21 | NumericDataType, 22 | RemSigned, 23 | RemUnsigned, 24 | And, 25 | Or, 26 | Xor, 27 | ShiftLeft, 28 | ShiftRightSigned, 29 | ShiftRightUnsigned, 30 | RotateLeft, 31 | RotateRight, 32 | Store, 33 | Sub, 34 | Instr, 35 | GlobalGet, 36 | GlobalSet, 37 | GlobalTee, 38 | PresentDataType, 39 | Loop, 40 | BranchIf, 41 | Branch, 42 | EqualZero, 43 | Load8ZeroExt, 44 | Load8SignExt, 45 | Load16SignExt, 46 | Load16ZeroExt, 47 | Load32SignExt, 48 | Load32ZeroExt, 49 | Store8, 50 | Store16, 51 | Store32, 52 | Equal, 53 | GreaterEqualSigned, 54 | GreaterEqualUnsigned, 55 | LessEqualSigned, 56 | LessEqualUnsigned, 57 | GreaterThanSigned, 58 | GreaterThanUnsigned, 59 | } from './nodes'; 60 | import { match } from 'ts-pattern'; 61 | 62 | // const trimdent = (str: string) => { 63 | // const trimmed = str.trim(); 64 | // const spaces = 65 | // return trimmed.replace(/^\s*(.*)$/gm, ' '.repeat(spaces) + '$1'); 66 | // }; 67 | 68 | const compileSExpression = ( 69 | sExpr: { 70 | fn: string; 71 | inlineArgs?: (string | null)[]; 72 | blockArgs?: (string | null)[]; 73 | }, 74 | indent: number, 75 | ) => { 76 | let ret = space(indent) + `(${sExpr.fn}`; 77 | const filteredInlineArgs = sExpr.inlineArgs?.filter((x) => x !== null); 78 | const filteredBlockArgs = sExpr.blockArgs?.filter((x) => x !== null); 79 | if (filteredInlineArgs?.length) { 80 | ret += ` `; 81 | ret += filteredInlineArgs.join(' '); 82 | } 83 | if (filteredBlockArgs?.length) { 84 | ret += `\n`; 85 | ret += filteredBlockArgs.join('\n'); 86 | ret += `\n` + space(indent) + `)`; 87 | } else { 88 | ret += `)`; 89 | } 90 | return ret; 91 | }; 92 | 93 | const space = (indent: number) => ' '.repeat(indent); 94 | 95 | const localGet = (node: LocalGet, indent = 0) => { 96 | return compileSExpression( 97 | { fn: 'local.get', inlineArgs: [`$${node.name}`] }, 98 | indent, 99 | ); 100 | }; 101 | 102 | const localSet = (node: LocalSet, indent = 0) => { 103 | return compileSExpression( 104 | { 105 | fn: 'local.set', 106 | inlineArgs: [`$${node.name}`], 107 | blockArgs: [instr(node.value, indent + 1)], 108 | }, 109 | indent, 110 | ); 111 | }; 112 | 113 | const localTee = (node: LocalTee, indent = 0) => { 114 | return compileSExpression( 115 | { 116 | fn: 'local.tee', 117 | inlineArgs: [`$${node.name}`], 118 | blockArgs: [instr(node.value, indent + 1)], 119 | }, 120 | indent, 121 | ); 122 | }; 123 | 124 | const globalGet = (node: GlobalGet, indent = 0) => { 125 | return compileSExpression( 126 | { fn: 'global.get', inlineArgs: [`$${node.name}`] }, 127 | indent, 128 | ); 129 | }; 130 | 131 | const globalSet = (node: GlobalSet, indent = 0) => { 132 | return compileSExpression( 133 | { 134 | fn: 'global.set', 135 | inlineArgs: [`$${node.name}`], 136 | blockArgs: [instr(node.value, indent + 1)], 137 | }, 138 | indent, 139 | ); 140 | }; 141 | 142 | const globalTee = (node: GlobalTee, indent = 0) => { 143 | return compileSExpression( 144 | { 145 | fn: 'global.tee', 146 | inlineArgs: [`$${node.name}`], 147 | blockArgs: [instr(node.value, indent + 1)], 148 | }, 149 | indent, 150 | ); 151 | }; 152 | 153 | const makeBinaryCompiler = 154 | (op: string) => 155 | (node: T, indent = 0) => { 156 | return compileSExpression( 157 | { 158 | fn: `${node.dataType}.${op}`, 159 | blockArgs: [ 160 | instr(node.left, indent + 1), 161 | instr(node.right, indent + 1), 162 | ], 163 | }, 164 | indent, 165 | ); 166 | }; 167 | const clz = makeBinaryCompiler>('clz'); 168 | const ctz = makeBinaryCompiler>('ctz'); 169 | const popcnt = makeBinaryCompiler>('popcnt'); 170 | const add = makeBinaryCompiler>('add'); 171 | const sub = makeBinaryCompiler>('sub'); 172 | const mul = makeBinaryCompiler>('mul'); 173 | const divSigned = makeBinaryCompiler>('div_s'); 174 | const divUnsigned = makeBinaryCompiler>('div_u'); 175 | const remSigned = makeBinaryCompiler>('rem_s'); 176 | const remUnsigned = makeBinaryCompiler>('rem_u'); 177 | const and = makeBinaryCompiler>('and'); 178 | const or = makeBinaryCompiler>('or'); 179 | const xor = makeBinaryCompiler>('xor'); 180 | const shl = makeBinaryCompiler>('shl'); 181 | const shrSigned = makeBinaryCompiler>('shr_s'); 182 | const shrUnsigned = makeBinaryCompiler>('shr_u'); 183 | const rotl = makeBinaryCompiler>('rotl'); 184 | const rotr = makeBinaryCompiler>('rotr'); 185 | const equal = makeBinaryCompiler>('eq'); 186 | const greaterThanSigned = 187 | makeBinaryCompiler>('gt_s'); 188 | const greaterThanUnsigned = 189 | makeBinaryCompiler>('gt_u'); 190 | const lessThanSigned = 191 | makeBinaryCompiler>('lt_s'); 192 | const lessThanUnsigned = 193 | makeBinaryCompiler>('lt_u'); 194 | const greaterEqualSigned = 195 | makeBinaryCompiler>('ge_s'); 196 | const greaterEqualUnsigned = 197 | makeBinaryCompiler>('ge_u'); 198 | const lessEqualSigned = 199 | makeBinaryCompiler>('le_s'); 200 | const lessEqualUnsigned = 201 | makeBinaryCompiler>('le_u'); 202 | 203 | const constant = (node: Const, indent = 0) => { 204 | return compileSExpression( 205 | { fn: `${node.dataType}.const`, inlineArgs: [`${node.value}`] }, 206 | indent, 207 | ); 208 | }; 209 | 210 | const call = (node: Call, indent = 0) => { 211 | return compileSExpression( 212 | { 213 | fn: 'call', 214 | inlineArgs: [`$${node.name}`], 215 | blockArgs: node.args.map((arg) => instr(arg, indent + 1)), 216 | }, 217 | indent, 218 | ); 219 | }; 220 | 221 | const callIndirect = (node: CallIndirect, indent = 0) => { 222 | return compileSExpression( 223 | { 224 | fn: 'call_indirect', 225 | blockArgs: [ 226 | space(indent + 1) + `$${node.tableName}`, 227 | ...node.params.map( 228 | (param) => space(indent + 1) + `(param ${param[0]})`, 229 | ), 230 | space(indent + 1) + `(result ${node.dataType})`, 231 | ...node.args.map((arg) => instr(arg, indent + 1)), 232 | instr(node.address, indent + 1), 233 | ], 234 | }, 235 | indent, 236 | ); 237 | }; 238 | 239 | const block = (node: Block, indent = 0) => { 240 | return compileSExpression( 241 | { 242 | fn: 'block', 243 | blockArgs: [ 244 | node.name ? space(indent + 1) + `$${node.name}` : null, 245 | node.returnType !== 'none' 246 | ? space(indent + 1) + `(result ${node.returnType})` 247 | : null, 248 | ...node.body.map((x) => instr(x as any, indent + 1)), 249 | ], 250 | }, 251 | indent, 252 | ); 253 | }; 254 | 255 | const drop = (node: Drop, indent = 0) => { 256 | return compileSExpression( 257 | { fn: 'drop', blockArgs: [instr(node.value, indent + 1)] }, 258 | indent, 259 | ); 260 | }; 261 | 262 | const makeStoreCompiler = 263 | (op: string) => 264 | (node: T, indent = 0) => { 265 | return compileSExpression( 266 | { 267 | fn: `${node.dataType}.${op}`, 268 | blockArgs: [ 269 | node.offset ? space(indent + 1) + `offset=${node.offset}` : null, 270 | node.align ? space(indent + 1) + `align=${node.align}` : null, 271 | instr(node.base, indent + 1), 272 | instr(node.value, indent + 1), 273 | ], 274 | }, 275 | indent, 276 | ); 277 | }; 278 | 279 | const store = makeStoreCompiler('store'); 280 | const store8 = makeStoreCompiler('store8'); 281 | const store16 = makeStoreCompiler('store16'); 282 | const store32 = makeStoreCompiler('store32'); 283 | 284 | const makeLoadCompiler = 285 | < 286 | T extends 287 | | Load 288 | | Load8SignExt 289 | | Load8ZeroExt 290 | | Load16SignExt 291 | | Load16ZeroExt 292 | | Load32SignExt 293 | | Load32ZeroExt, 294 | >( 295 | op: string, 296 | ) => 297 | (node: T, indent = 0) => { 298 | return compileSExpression( 299 | { 300 | fn: `${node.dataType}.${op}`, 301 | blockArgs: [ 302 | node.offset ? space(indent + 1) + `offset=${node.offset}` : null, 303 | node.align !== null 304 | ? space(indent + 1) + `align=${node.align}` 305 | : null, 306 | instr(node.base, indent + 1), 307 | ], 308 | }, 309 | indent, 310 | ); 311 | }; 312 | 313 | const load = makeLoadCompiler('load'); 314 | const load8SignExt = makeLoadCompiler('load8_s'); 315 | const load8ZeroExt = makeLoadCompiler('load8_u'); 316 | const load16SignExt = makeLoadCompiler('load16_s'); 317 | const load16ZeroExt = makeLoadCompiler('load16_u'); 318 | const load32SignExt = makeLoadCompiler('load32_s'); 319 | const load32ZeroExt = makeLoadCompiler('load32_u'); 320 | 321 | const loop = (node: Loop, indent = 0) => { 322 | return compileSExpression( 323 | { 324 | fn: 'loop', 325 | inlineArgs: [node.name !== null ? `$${node.name}` : null], 326 | blockArgs: node.body.map((x) => instr(x, indent + 1)), 327 | }, 328 | indent, 329 | ); 330 | }; 331 | 332 | const branchIf = (node: BranchIf, indent = 0) => { 333 | return compileSExpression( 334 | { 335 | fn: 'br_if', 336 | inlineArgs: [ 337 | typeof node.branchTo === 'string' 338 | ? `$${node.branchTo}` 339 | : `${node.branchTo}`, 340 | ], 341 | blockArgs: [instr(node.cond, indent + 1)], 342 | }, 343 | indent, 344 | ); 345 | }; 346 | 347 | const branch = (node: Branch, indent = 0) => { 348 | return compileSExpression( 349 | { 350 | fn: 'br', 351 | inlineArgs: [ 352 | typeof node.branchTo === 'string' 353 | ? `$${node.branchTo}` 354 | : `${node.branchTo}`, 355 | ], 356 | }, 357 | indent, 358 | ); 359 | }; 360 | 361 | const equalZero = (node: EqualZero, indent = 0) => { 362 | return compileSExpression( 363 | { fn: `${node.dataType}.eqz`, blockArgs: [instr(node.right, indent + 1)] }, 364 | indent, 365 | ); 366 | }; 367 | 368 | const instrTypesCompilers = { 369 | localGet, 370 | localSet, 371 | localTee, 372 | clz, 373 | ctz, 374 | popcnt, 375 | add, 376 | sub, 377 | mul, 378 | divSigned, 379 | divUnsigned, 380 | remSigned, 381 | remUnsigned, 382 | and, 383 | or, 384 | xor, 385 | shl, 386 | shrSigned, 387 | shrUnsigned, 388 | rotl, 389 | rotr, 390 | constant, 391 | call, 392 | callIndirect, 393 | load, 394 | load8SignExt, 395 | load8ZeroExt, 396 | load16SignExt, 397 | load16ZeroExt, 398 | load32SignExt, 399 | load32ZeroExt, 400 | store, 401 | store8, 402 | store16, 403 | store32, 404 | globalGet, 405 | globalSet, 406 | globalTee, 407 | loop, 408 | branchIf, 409 | branch, 410 | equalZero, 411 | equal, 412 | drop, 413 | block, 414 | greaterThanSigned, 415 | greaterThanUnsigned, 416 | lessThanSigned, 417 | lessThanUnsigned, 418 | greaterEqualSigned, 419 | greaterEqualUnsigned, 420 | lessEqualSigned, 421 | lessEqualUnsigned, 422 | }; 423 | 424 | const instr = (node: Instr, indent = 0): string => { 425 | const compiler = instrTypesCompilers[node.__nodeType]; 426 | if (!compiler) { 427 | throw new Error(`Unexpected ${node.__nodeType} node`); 428 | } 429 | return compiler(node as any, indent); 430 | }; 431 | 432 | export const compile = (m: Module) => { 433 | return compileSExpression( 434 | { 435 | fn: 'module', 436 | blockArgs: [ 437 | ...m.memories.flatMap((mem) => [ 438 | compileSExpression( 439 | { 440 | fn: 'memory', 441 | inlineArgs: [`$${mem.name}`, `${mem.initSize}`, `${mem.maxSize}`], 442 | }, 443 | 1, 444 | ), 445 | compileSExpression( 446 | { 447 | fn: 'export', 448 | inlineArgs: [`"${mem.name}"`, `(memory $${mem.name})`], 449 | }, 450 | 1, 451 | ), 452 | ...mem.segments.flatMap((seg) => 453 | compileSExpression( 454 | { 455 | fn: 'data', 456 | blockArgs: [ 457 | instr(seg.offset, 2), 458 | space(2) + 459 | `"${[...seg.data] 460 | .map((d) => '\\' + d.toString(16)) 461 | .join('')}"`, 462 | ], 463 | }, 464 | 1, 465 | ), 466 | ), 467 | ]), 468 | ...m.tables.flatMap((table) => [ 469 | compileSExpression( 470 | { 471 | fn: 'table', 472 | inlineArgs: [ 473 | `$${table.name}`, 474 | `${table.initSize}`, 475 | `${table.maxSize}`, 476 | `${table.type}`, 477 | ], 478 | }, 479 | 1, 480 | ), 481 | ...table.segments.flatMap((seg) => 482 | compileSExpression( 483 | { 484 | fn: 'elem', 485 | blockArgs: [ 486 | // space(2) + `(table $${table.name})`, 487 | instr(seg.offset, 2), 488 | space(2) + seg.elems.map((x) => `$${x}`).join(' '), 489 | ], 490 | }, 491 | 1, 492 | ), 493 | ), 494 | ]), 495 | ...m.globals.map((global) => 496 | compileSExpression( 497 | { 498 | fn: 'global', 499 | inlineArgs: [ 500 | `$${global.name}`, 501 | `(${global.mutable ? 'mut ' : ''}${global.dataType})`, 502 | ], 503 | blockArgs: [instr(global.initVal, 2)], 504 | }, 505 | 1, 506 | ), 507 | ), 508 | ...m.funcs 509 | .filter((func) => func.exportName) 510 | .map((func) => 511 | compileSExpression( 512 | { 513 | fn: 'export', 514 | inlineArgs: [`"${func.exportName}"`, `(func $${func.name})`], 515 | }, 516 | 1, 517 | ), 518 | ), 519 | ...m.funcs.map((func) => 520 | compileSExpression( 521 | { 522 | fn: 'func', 523 | inlineArgs: [`$${func.name}`], 524 | blockArgs: [ 525 | ...func.params.map( 526 | ([dt, name]) => space(2) + `(param $${name} ${dt})`, 527 | ), 528 | func.dataType !== 'none' 529 | ? space(2) + `(result ${func.dataType})` 530 | : null, 531 | ...func.locals.map( 532 | ([dt, name]) => space(2) + `(local $${name} ${dt})`, 533 | ), 534 | instr(func.body, 2), 535 | ], 536 | }, 537 | 1, 538 | ), 539 | ), 540 | m.start !== null 541 | ? compileSExpression( 542 | { 543 | fn: 'start', 544 | inlineArgs: [`$${m.start}`], 545 | }, 546 | 1, 547 | ) 548 | : null, 549 | ], 550 | }, 551 | 0, 552 | ); 553 | }; 554 | 555 | export const compilers = { 556 | instr, 557 | module: compile, 558 | ...instrTypesCompilers, 559 | }; 560 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as w from './reexport'; 2 | export { w }; 3 | -------------------------------------------------------------------------------- /src/methods.ts: -------------------------------------------------------------------------------- 1 | import { NoInfer } from './utils'; 2 | import { 3 | Clz, 4 | Ctz, 5 | Popcnt, 6 | Add, 7 | Block, 8 | Call, 9 | CallIndirect, 10 | Const, 11 | DataType, 12 | DivSigned, 13 | DivUnsigned, 14 | Drop, 15 | Func, 16 | IntegerDataType, 17 | Load, 18 | LocalGet, 19 | LocalSet, 20 | LocalTee, 21 | Mul, 22 | NumericDataType, 23 | RemSigned, 24 | RemUnsigned, 25 | And, 26 | Or, 27 | Xor, 28 | ShiftLeft, 29 | ShiftRightSigned, 30 | ShiftRightUnsigned, 31 | RotateLeft, 32 | RotateRight, 33 | Store, 34 | Sub, 35 | Instr, 36 | GlobalGet, 37 | GlobalSet, 38 | GlobalTee, 39 | Loop, 40 | BranchIf, 41 | Branch, 42 | EqualZero, 43 | Load8SignExt, 44 | Load8ZeroExt, 45 | Load16SignExt, 46 | Load32SignExt, 47 | Load32ZeroExt, 48 | Store8, 49 | Store16, 50 | Store32, 51 | Equal, 52 | NotEqual, 53 | GreaterThanSigned, 54 | GreaterThanUnsigned, 55 | LessThanSigned, 56 | LessThanUnsigned, 57 | GreaterEqualSigned, 58 | GreaterEqualUnsigned, 59 | LessEqualSigned, 60 | LessEqualUnsigned, 61 | } from './nodes'; 62 | 63 | /** 64 | * Methods for the `local.get`, `local.set`, and `local.tee` instructions. 65 | */ 66 | export const local = { 67 | /** 68 | * Creates a node for the `local.get` instruction. 69 | * The local has to be defined in the function. 70 | * 71 | * When compiled, results in 72 | * ```wasm 73 | * (local.get $[name]) 74 | * ``` 75 | * @param dataType the data type of the local. 76 | * @param name the name of the local (without the prefixing dollar sign). 77 | */ 78 | get: (dataType: T, name: string): LocalGet => ({ 79 | __nodeType: 'localGet', 80 | dataType, 81 | name, 82 | returnType: dataType, 83 | }), 84 | /** 85 | * Creates a node for the `local.set` instruction. 86 | * The local has to be defined in the function. 87 | * 88 | * When compiled, results in 89 | * ```wasm 90 | * (local.set $[name] [value]) 91 | * ``` 92 | * @param dataType the data type of the local. 93 | * @param name the name of the local (without the prefixing dollar sign). 94 | * @param value the value `Instr` node. 95 | */ 96 | set: ( 97 | dataType: T, 98 | name: string, 99 | value: Instr>, 100 | ): LocalSet => ({ 101 | __nodeType: 'localSet', 102 | dataType, 103 | name, 104 | value, 105 | returnType: 'none', 106 | }), 107 | /** 108 | * Creates a node for the `local.tee` instruction. 109 | * The local has to be defined in the function. 110 | * 111 | * When compiled, results in 112 | * ```wasm 113 | * (local.tee $[name] [value]) 114 | * ``` 115 | * @param dataType the data type of the local. 116 | * @param name the name of the local (without the prefixing dollar sign). 117 | * @param value the value `Instr` node. 118 | */ 119 | tee: ( 120 | dataType: T, 121 | name: string, 122 | value: Instr>, 123 | ): LocalTee => ({ 124 | __nodeType: 'localTee', 125 | dataType, 126 | name, 127 | returnType: dataType, 128 | value, 129 | }), 130 | }; 131 | 132 | /** 133 | * Methods for the `global.get`, `global.set`, and `global.tee` instructions. 134 | */ 135 | export const global = { 136 | /** 137 | * Creates a node for the `global.get` instruction. 138 | * The global has to be defined in the module. 139 | * 140 | * When compiled, results in 141 | * ```wasm 142 | * (global.get $[name]) 143 | * ``` 144 | * @param dataType the data type of the global. 145 | * @param name the name of the global (without the prefixing dollar sign). 146 | */ 147 | get: ( 148 | dataType: T, 149 | name: string, 150 | ): GlobalGet => ({ 151 | __nodeType: 'globalGet', 152 | dataType, 153 | name, 154 | returnType: dataType, 155 | }), 156 | /** 157 | * Creates a node for the `global.set` instruction. 158 | * The global has to be defined in the module. 159 | * 160 | * When compiled, results in 161 | * ```wasm 162 | * (global.set $[name] [value]) 163 | * ``` 164 | * @param dataType the data type of the global. 165 | * @param name the name of the global (without the prefixing dollar sign). 166 | * @param value the value `Instr` node. 167 | */ 168 | set: ( 169 | dataType: T, 170 | name: string, 171 | value: Instr>, 172 | ): GlobalSet => ({ 173 | __nodeType: 'globalSet', 174 | dataType, 175 | name, 176 | value, 177 | returnType: 'none', 178 | }), 179 | /** 180 | * Creates a node for the `global.tee` instruction. 181 | * The global has to be defined in the module. 182 | * 183 | * When compiled, results in 184 | * ```wasm 185 | * (global.tee $[name] [value]) 186 | * ``` 187 | * @param dataType the data type of the global. 188 | * @param name the name of the global (without the prefixing dollar sign). 189 | * @param value the value `Instr` node. 190 | */ 191 | tee: ( 192 | dataType: T, 193 | name: string, 194 | value: Instr>, 195 | ): GlobalTee => ({ 196 | __nodeType: 'globalTee', 197 | dataType, 198 | name, 199 | returnType: dataType, 200 | value, 201 | }), 202 | }; 203 | 204 | /** 205 | * Creates a node for the `clz` instruction. 206 | * 207 | * When compiled, results in 208 | * ```wasm 209 | * ([dataType].clz [left] [right]) 210 | * ``` 211 | * @param dataType the data type of both operands and of the result type. 212 | * @param left the value `Instr` node of the left operand. 213 | * @param right the value `Instr` node of the right operand. 214 | */ 215 | export const clz = ( 216 | dataType: T, 217 | left: Instr>, 218 | right: Instr>, 219 | ): Clz => ({ 220 | __nodeType: 'clz', 221 | dataType, 222 | left, 223 | right, 224 | returnType: dataType, 225 | }); 226 | 227 | /** 228 | * Creates a node for the `ctz` instruction. 229 | * 230 | * When compiled, results in 231 | * ```wasm 232 | * ([dataType].ctz [left] [right]) 233 | * ``` 234 | * @param dataType the data type of both operands and of the result type. 235 | * @param left the value `Instr` node of the left operand. 236 | * @param right the value `Instr` node of the right operand. 237 | */ 238 | export const ctz = ( 239 | dataType: T, 240 | left: Instr>, 241 | right: Instr>, 242 | ): Ctz => ({ 243 | __nodeType: 'ctz', 244 | dataType, 245 | left, 246 | right, 247 | returnType: dataType, 248 | }); 249 | 250 | /** 251 | * Creates a node for the `popcnt` instruction. 252 | * 253 | * When compiled, results in 254 | * ```wasm 255 | * ([dataType].popcnt [left] [right]) 256 | * ``` 257 | * @param dataType the data type of both operands and of the result type. 258 | * @param left the value `Instr` node of the left operand. 259 | * @param right the value `Instr` node of the right operand. 260 | */ 261 | export const popcnt = ( 262 | dataType: T, 263 | left: Instr>, 264 | right: Instr>, 265 | ): Popcnt => ({ 266 | __nodeType: 'popcnt', 267 | dataType, 268 | left, 269 | right, 270 | returnType: dataType, 271 | }); 272 | 273 | /** 274 | * Creates a node for the `add` instruction. 275 | * 276 | * When compiled, results in 277 | * ```wasm 278 | * ([dataType].add [left] [right]) 279 | * ``` 280 | * @param dataType the data type of both operands and of the result type. 281 | * @param left the value `Instr` node of the left operand. 282 | * @param right the value `Instr` node of the right operand. 283 | */ 284 | export const add = ( 285 | dataType: T, 286 | left: Instr>, 287 | right: Instr>, 288 | ): Add => ({ 289 | __nodeType: 'add', 290 | dataType, 291 | left, 292 | right, 293 | returnType: dataType, 294 | }); 295 | 296 | /** 297 | * Creates a node for the `sub` instruction. 298 | * 299 | * When compiled, results in 300 | * ```wasm 301 | * ([dataType].sub [left] [right]) 302 | * ``` 303 | * @param dataType the data type of both operands and of the result type. 304 | * @param left the value `Instr` node of the left operand. 305 | * @param right the value `Instr` node of the right operand. 306 | */ 307 | export const sub = ( 308 | dataType: T, 309 | left: Instr>, 310 | right: Instr>, 311 | ): Sub => ({ 312 | __nodeType: 'sub', 313 | dataType, 314 | left, 315 | right, 316 | returnType: dataType, 317 | }); 318 | 319 | /** 320 | * Creates a node for the `mul` instruction. 321 | * 322 | * When compiled, results in 323 | * ```wasm 324 | * ([dataType].mul [left] [right]) 325 | * ``` 326 | * @param dataType the data type of both operands and of the result type. 327 | * @param left the value `Instr` node of the left operand. 328 | * @param right the value `Instr` node of the right operand. 329 | */ 330 | export const mul = ( 331 | dataType: T, 332 | left: Instr>, 333 | right: Instr>, 334 | ): Mul => ({ 335 | __nodeType: 'mul', 336 | dataType, 337 | left, 338 | right, 339 | returnType: dataType, 340 | }); 341 | 342 | /** 343 | * Creates a node for the `div_s` instruction. 344 | * 345 | * When compiled, results in 346 | * ```wasm 347 | * ([dataType].div_s [left] [right]) 348 | * ``` 349 | * @param dataType the data type of both operands and of the result type. 350 | * @param left the value `Instr` node of the left operand. 351 | * @param right the value `Instr` node of the right operand. 352 | */ 353 | export const divSigned = ( 354 | dataType: T, 355 | left: Instr>, 356 | right: Instr>, 357 | ): DivSigned => ({ 358 | __nodeType: 'divSigned', 359 | dataType, 360 | left, 361 | right, 362 | returnType: dataType, 363 | }); 364 | 365 | /** 366 | * Creates a node for the `div_u` instruction. 367 | * 368 | * When compiled, results in 369 | * ```wasm 370 | * ([dataType].div_u [left] [right]) 371 | * ``` 372 | * @param dataType the data type of both operands and of the result type. 373 | * @param left the value `Instr` node of the left operand. 374 | * @param right the value `Instr` node of the right operand. 375 | */ 376 | export const divUnsigned = ( 377 | dataType: T, 378 | left: Instr>, 379 | right: Instr>, 380 | ): DivUnsigned => ({ 381 | __nodeType: 'divUnsigned', 382 | dataType, 383 | left, 384 | right, 385 | returnType: dataType, 386 | }); 387 | 388 | /** 389 | * Creates a node for the `rem_s` instruction. 390 | * 391 | * When compiled, results in 392 | * ```wasm 393 | * ([dataType].rem_s [left] [right]) 394 | * ``` 395 | * @param dataType the data type of both operands and of the result type. 396 | * @param left the value `Instr` node of the left operand. 397 | * @param right the value `Instr` node of the right operand. 398 | */ 399 | export const remSigned = ( 400 | dataType: T, 401 | left: Instr>, 402 | right: Instr>, 403 | ): RemSigned => ({ 404 | __nodeType: 'remSigned', 405 | dataType, 406 | left, 407 | right, 408 | returnType: dataType, 409 | }); 410 | 411 | /** 412 | * Creates a node for the `rem_u` instruction. 413 | * 414 | * When compiled, results in 415 | * ```wasm 416 | * ([dataType].rem_u [left] [right]) 417 | * ``` 418 | * @param dataType the data type of both operands and of the result type. 419 | * @param left the value `Instr` node of the left operand. 420 | * @param right the value `Instr` node of the right operand. 421 | */ 422 | export const remUnsigned = ( 423 | dataType: T, 424 | left: Instr>, 425 | right: Instr>, 426 | ): RemUnsigned => ({ 427 | __nodeType: 'remUnsigned', 428 | dataType, 429 | left, 430 | right, 431 | returnType: dataType, 432 | }); 433 | 434 | /** 435 | * Creates a node for the `and` instruction. 436 | * 437 | * When compiled, results in 438 | * ```wasm 439 | * ([dataType].and [left] [right]) 440 | * ``` 441 | * @param dataType the data type of both operands and of the result type. 442 | * @param left the value `Instr` node of the left operand. 443 | * @param right the value `Instr` node of the right operand. 444 | */ 445 | export const and = ( 446 | dataType: T, 447 | left: Instr>, 448 | right: Instr>, 449 | ): And => ({ 450 | __nodeType: 'and', 451 | dataType, 452 | left, 453 | right, 454 | returnType: dataType, 455 | }); 456 | 457 | /** 458 | * Creates a node for the `or` instruction. 459 | * 460 | * When compiled, results in 461 | * ```wasm 462 | * ([dataType].and [left] [right]) 463 | * ``` 464 | * @param dataType the data type of both operands and of the result type. 465 | * @param left the value `Instr` node of the left operand. 466 | * @param right the value `Instr` node of the right operand. 467 | */ 468 | export const or = ( 469 | dataType: T, 470 | left: Instr>, 471 | right: Instr>, 472 | ): Or => ({ 473 | __nodeType: 'or', 474 | dataType, 475 | left, 476 | right, 477 | returnType: dataType, 478 | }); 479 | 480 | /** 481 | * Creates a node for the `xor` instruction. 482 | * 483 | * When compiled, results in 484 | * ```wasm 485 | * ([dataType].xor [left] [right]) 486 | * ``` 487 | * @param dataType the data type of both operands and of the result type. 488 | * @param left the value `Instr` node of the left operand. 489 | * @param right the value `Instr` node of the right operand. 490 | */ 491 | export const xor = ( 492 | dataType: T, 493 | left: Instr>, 494 | right: Instr>, 495 | ): Xor => ({ 496 | __nodeType: 'xor', 497 | dataType, 498 | left, 499 | right, 500 | returnType: dataType, 501 | }); 502 | 503 | /** 504 | * Creates a node for the `shl` instruction. 505 | * 506 | * When compiled, results in 507 | * ```wasm 508 | * ([dataType].shl [left] [right]) 509 | * ``` 510 | * @param dataType the data type of both operands and of the result type. 511 | * @param left the value `Instr` node of the left operand. 512 | * @param right the value `Instr` node of the right operand. 513 | */ 514 | export const shl = ( 515 | dataType: T, 516 | left: Instr>, 517 | right: Instr>, 518 | ): ShiftLeft => ({ 519 | __nodeType: 'shl', 520 | dataType, 521 | left, 522 | right, 523 | returnType: dataType, 524 | }); 525 | 526 | /** 527 | * Creates a node for the `shr_s` instruction. 528 | * 529 | * When compiled, results in 530 | * ```wasm 531 | * ([dataType].shr_s [left] [right]) 532 | * ``` 533 | * @param dataType the data type of both operands and of the result type. 534 | * @param left the value `Instr` node of the left operand. 535 | * @param right the value `Instr` node of the right operand. 536 | */ 537 | export const shrSigned = ( 538 | dataType: T, 539 | left: Instr>, 540 | right: Instr>, 541 | ): ShiftRightSigned => ({ 542 | __nodeType: 'shrSigned', 543 | dataType, 544 | left, 545 | right, 546 | returnType: dataType, 547 | }); 548 | 549 | /** 550 | * Creates a node for the `shr_u` instruction. 551 | * 552 | * When compiled, results in 553 | * ```wasm 554 | * ([dataType].shr_u [left] [right]) 555 | * ``` 556 | * @param dataType the data type of both operands and of the result type. 557 | * @param left the value `Instr` node of the left operand. 558 | * @param right the value `Instr` node of the right operand. 559 | */ 560 | export const shrUnsigned = ( 561 | dataType: T, 562 | left: Instr>, 563 | right: Instr>, 564 | ): ShiftRightUnsigned => ({ 565 | __nodeType: 'shrUnsigned', 566 | dataType, 567 | left, 568 | right, 569 | returnType: dataType, 570 | }); 571 | 572 | /** 573 | * Creates a node for the `rotl` instruction. 574 | * 575 | * When compiled, results in 576 | * ```wasm 577 | * ([dataType].rotl [left] [right]) 578 | * ``` 579 | * @param dataType the data type of both operands and of the result type. 580 | * @param left the value `Instr` node of the left operand. 581 | * @param right the value `Instr` node of the right operand. 582 | */ 583 | export const rotl = ( 584 | dataType: T, 585 | left: Instr>, 586 | right: Instr>, 587 | ): RotateLeft => ({ 588 | __nodeType: 'rotl', 589 | dataType, 590 | left, 591 | right, 592 | returnType: dataType, 593 | }); 594 | 595 | /** 596 | * Creates a node for the `rotr` instruction. 597 | * 598 | * When compiled, results in 599 | * ```wasm 600 | * ([dataType].rotr [left] [right]) 601 | * ``` 602 | * @param dataType the data type of both operands and of the result type. 603 | * @param left the value `Instr` node of the left operand. 604 | * @param right the value `Instr` node of the right operand. 605 | */ 606 | export const rotr = ( 607 | dataType: T, 608 | left: Instr>, 609 | right: Instr>, 610 | ): RotateRight => ({ 611 | __nodeType: 'rotr', 612 | dataType, 613 | left, 614 | right, 615 | returnType: dataType, 616 | }); 617 | 618 | /** 619 | * Creates a node for the `const` instruction. 620 | * 621 | * When compiled, results in 622 | * ```wasm 623 | * ([dataType].const [value]) 624 | * ``` 625 | * @param dataType the data type of the constant. 626 | * @param value the literal value. 627 | */ 628 | export const constant = ( 629 | dataType: T, 630 | value: number, 631 | ): Const => ({ 632 | __nodeType: 'constant', 633 | value, 634 | dataType, 635 | returnType: dataType, 636 | }); 637 | 638 | /** 639 | * Creates a node for the `func` instruction. 640 | * Note: to add the function to your module, use `w.addFunc`. 641 | * 642 | * When compiled, results in 643 | * ```wasm 644 | * (func $[name] 645 | * (param $[param name] [param type]) ;; for each param in signature.params 646 | * (local $[local name] [local type]) ;; for each local in signature.locals 647 | * (result [signature.returnType]) ;; if returnType is not `none` 648 | * [body] 649 | * ) 650 | * ``` 651 | * @param name 652 | * @param signature.params a list of `[type, name]` tuples for each param. 653 | * @param signature.locals a list of `[type, name]` tuples for each local. 654 | * @param signature.returnType the return type of the function. Has to be the same as the `returnType` of `body`. 655 | * @param body the value `Instr` node to be returned. 656 | */ 657 | export const func = ( 658 | name: string, 659 | signature: { 660 | params: [type: NumericDataType, name: string][]; 661 | locals: [type: NumericDataType, name: string][]; 662 | returnType: T; 663 | }, 664 | body: Instr>, 665 | ): Func => ({ 666 | __nodeType: 'func', 667 | name, 668 | params: signature.params, 669 | locals: signature.locals, 670 | body, 671 | dataType: signature.returnType, 672 | exportName: null, 673 | }); 674 | 675 | /** 676 | * Creates a node for the `drop` instruction. 677 | * 678 | * When compiled, results in 679 | * ```wasm 680 | * (drop [value]) 681 | * ``` 682 | * @param value the value `Instr` to be dropped. 683 | */ 684 | export const drop = (value: Instr): Drop => ({ 685 | __nodeType: 'drop', 686 | value, 687 | returnType: 'none', 688 | }); 689 | 690 | /** 691 | * Creates a node for the `block` instruction. 692 | * 693 | * @param name the optional name of the block. `null` if anonymous. 694 | * @param returnType the return type of the block. Has to be the same as the last node's return type in `value`. 695 | * @param value a list of instructions that make up the block. All but the last one must be statements. 696 | */ 697 | export const block = ( 698 | name: string | null, 699 | returnType: T, 700 | value: [...Instr<'none'>[], Instr>] | [Instr>] | [], 701 | ): Block => ({ 702 | __nodeType: 'block', 703 | name, 704 | body: value, 705 | returnType, 706 | }); 707 | 708 | /** 709 | * Creates a node for the `call` instruction. 710 | * 711 | * When compiled, results in 712 | * ```wasm 713 | * (call $[name] 714 | * [arg] ;; for each argument in `args` 715 | * ) 716 | * ``` 717 | * @param name the name of the function to be called. 718 | * @param returnType the return type of the function. 719 | * @param args a list of value `Instr` nodes in the order of the function's params. 720 | */ 721 | export const call = ( 722 | name: string, 723 | returnType: T, 724 | args: Instr[], 725 | ): Call => ({ 726 | __nodeType: 'call', 727 | name, 728 | args, 729 | returnType, 730 | }); 731 | 732 | /** 733 | * Creates a node for the `call_indirect` instruction. 734 | * 735 | * When compiled, results in 736 | * ```wasm 737 | * (call_indirect 738 | * $[tableName] 739 | * (param [param type]) ;; for each param in `signature.params` 740 | * (result [signature.returnType]) ;; if return type is not `none` 741 | * [arg] ;; for each argument in `args` 742 | * [address] 743 | * ) 744 | * ``` 745 | * @param tableName the name of the `funcref` table. 746 | * @param address the value `Instr` node for the address of the function in the `funcref` table. 747 | * @param signature.params a list of `[type, name]` tuples for each param. 748 | * @param signature.returnType the return type of the function. 749 | * @param args a list of value `Instr` nodes in the order of the function's params. 750 | */ 751 | export const callIndirect = ( 752 | tableName: string, 753 | address: Instr, 754 | signature: { 755 | params: [type: NumericDataType, name?: string][]; 756 | returnType: T; 757 | }, 758 | args: Instr[], 759 | ): CallIndirect => ({ 760 | __nodeType: 'callIndirect', 761 | tableName, 762 | address, 763 | params: signature.params, 764 | args: args, 765 | dataType: signature.returnType, 766 | returnType: signature.returnType, 767 | }); 768 | 769 | /** 770 | * Creates a node for the `load` instruction. 771 | * 772 | * When compiled, results in 773 | * ```wasm 774 | * ([dataType].load 775 | * offset=[offset] ;; if not 0 776 | * align=[align] ;; if not null 777 | * [base] 778 | * ) 779 | * ``` 780 | * @param dataType the data type of the value. 781 | * @param offset the literal offset value. 782 | * @param align the optional literal align value. 783 | * @param base the `Instr` node for the base address in memory. 784 | */ 785 | export const load = ( 786 | dataType: T, 787 | offset: number, 788 | align: number | null, 789 | base: Instr, 790 | ): Load => ({ 791 | __nodeType: 'load', 792 | returnType: dataType, 793 | dataType, 794 | align, 795 | base, 796 | offset, 797 | }); 798 | 799 | /** 800 | * Creates a node for the `load8_s` instruction. 801 | * 802 | * When compiled, results in 803 | * ```wasm 804 | * ([dataType].load8_s 805 | * offset=[offset] ;; if not 0 806 | * align=[align] ;; if not null 807 | * [base] 808 | * ) 809 | * ``` 810 | * @param dataType the data type of the value. 811 | * @param offset the literal offset value. 812 | * @param align the optional literal align value. 813 | * @param base the `Instr` node for the base address in memory. 814 | */ 815 | export const load8SignExt = ( 816 | dataType: T, 817 | offset: number, 818 | align: number | null, 819 | base: Instr, 820 | ): Load8SignExt => ({ 821 | __nodeType: 'load8SignExt', 822 | returnType: dataType, 823 | dataType, 824 | align, 825 | base, 826 | offset, 827 | }); 828 | 829 | /** 830 | * Creates a node for the `load8_u` instruction. 831 | * 832 | * When compiled, results in 833 | * ```wasm 834 | * ([dataType].load8_u 835 | * offset=[offset] ;; if not 0 836 | * align=[align] ;; if not null 837 | * [base] 838 | * ) 839 | * ``` 840 | * @param dataType the data type of the value. 841 | * @param offset the literal offset value. 842 | * @param align the optional literal align value. 843 | * @param base the `Instr` node for the base address in memory. 844 | */ 845 | export const load8ZeroExt = ( 846 | dataType: T, 847 | offset: number, 848 | align: number | null, 849 | base: Instr, 850 | ): Load8ZeroExt => ({ 851 | __nodeType: 'load8ZeroExt', 852 | returnType: dataType, 853 | dataType, 854 | align, 855 | base, 856 | offset, 857 | }); 858 | 859 | /** 860 | * Creates a node for the `load16_s` instruction. 861 | * 862 | * When compiled, results in 863 | * ```wasm 864 | * ([dataType].load16_s 865 | * offset=[offset] ;; if not 0 866 | * align=[align] ;; if not null 867 | * [base] 868 | * ) 869 | * ``` 870 | * @param dataType the data type of the value. 871 | * @param offset the literal offset value. 872 | * @param align the optional literal align value. 873 | * @param base the `Instr` node for the base address in memory. 874 | */ 875 | export const load16SignExt = ( 876 | dataType: T, 877 | offset: number, 878 | align: number | null, 879 | base: Instr, 880 | ): Load16SignExt => ({ 881 | __nodeType: 'load16SignExt', 882 | returnType: dataType, 883 | dataType, 884 | align, 885 | base, 886 | offset, 887 | }); 888 | 889 | /** 890 | * Creates a node for the `load16_u` instruction. 891 | * 892 | * When compiled, results in 893 | * ```wasm 894 | * ([dataType].load16_u 895 | * offset=[offset] ;; if not 0 896 | * align=[align] ;; if not null 897 | * [base] 898 | * ) 899 | * ``` 900 | * @param dataType the data type of the value. 901 | * @param offset the literal offset value. 902 | * @param align the optional literal align value. 903 | * @param base the `Instr` node for the base address in memory. 904 | */ 905 | export const load16ZeroExt = ( 906 | dataType: T, 907 | offset: number, 908 | align: number | null, 909 | base: Instr, 910 | ): Load8SignExt => ({ 911 | __nodeType: 'load8SignExt', 912 | returnType: dataType, 913 | dataType, 914 | align, 915 | base, 916 | offset, 917 | }); 918 | 919 | /** 920 | * Creates a node for the `load32_s` instruction. 921 | * 922 | * When compiled, results in 923 | * ```wasm 924 | * (i64.load32_s 925 | * offset=[offset] ;; if not 0 926 | * align=[align] ;; if not null 927 | * [base] 928 | * ) 929 | * ``` 930 | * @param offset the literal offset value. 931 | * @param align the optional literal align value. 932 | * @param base the `Instr` node for the base address in memory. 933 | */ 934 | export const load32SignExt = ( 935 | offset: number, 936 | align: number | null, 937 | base: Instr, 938 | ): Load32SignExt<'i64'> => ({ 939 | __nodeType: 'load32SignExt', 940 | returnType: 'i64', 941 | dataType: 'i64', 942 | align, 943 | base, 944 | offset, 945 | }); 946 | 947 | /** 948 | * Creates a node for the `load32_u` instruction. 949 | * 950 | * When compiled, results in 951 | * ```wasm 952 | * (i64.load32_u 953 | * offset=[offset] ;; if not 0 954 | * align=[align] ;; if not null 955 | * [base] 956 | * ) 957 | * ``` 958 | * @param dataType the data type of the value. 959 | * @param offset the literal offset value. 960 | * @param align the optional literal align value. 961 | * @param base the `Instr` node for the base address in memory. 962 | */ 963 | export const load32ZeroExt = ( 964 | offset: number, 965 | align: number | null, 966 | base: Instr, 967 | ): Load32ZeroExt => ({ 968 | __nodeType: 'load32ZeroExt', 969 | returnType: 'i64', 970 | dataType: 'i64', 971 | align, 972 | base, 973 | offset, 974 | }); 975 | 976 | /** 977 | * Creates a node for the `store` instruction. 978 | * 979 | * When compiled, results in 980 | * ```wasm 981 | * ([dataType].store 982 | * offset=[offset] ;; if not 0 983 | * align=[align] ;; if not null 984 | * [base] 985 | * [value] 986 | * ) 987 | * ``` 988 | * @param dataType the data type of the value. 989 | * @param offset the literal offset value. 990 | * @param align the optional literal align value. 991 | * @param base the `Instr` node for the base address in memory. 992 | * @param value the `Instr` node for the value to be stored. 993 | */ 994 | export const store = ( 995 | dataType: NumericDataType, 996 | offset: number, 997 | align: number | null, 998 | base: Instr, 999 | value: Instr, 1000 | ): Store => ({ 1001 | __nodeType: 'store', 1002 | dataType, 1003 | align, 1004 | base, 1005 | offset, 1006 | value, 1007 | returnType: 'none', 1008 | }); 1009 | 1010 | /** 1011 | * Creates a node for the `store8` instruction. 1012 | * 1013 | * When compiled, results in 1014 | * ```wasm 1015 | * ([dataType].store8 1016 | * offset=[offset] ;; if not 0 1017 | * align=[align] ;; if not null 1018 | * [base] 1019 | * [value] 1020 | * ) 1021 | * ``` 1022 | * @param dataType the data type of the value. 1023 | * @param offset the literal offset value. 1024 | * @param align the optional literal align value. 1025 | * @param base the `Instr` node for the base address in memory. 1026 | * @param value the `Instr` node for the value to be stored. 1027 | */ 1028 | export const store8 = ( 1029 | dataType: IntegerDataType, 1030 | offset: number, 1031 | align: number | null, 1032 | base: Instr, 1033 | value: Instr, 1034 | ): Store8 => ({ 1035 | __nodeType: 'store8', 1036 | returnType: 'none', 1037 | dataType, 1038 | align, 1039 | base, 1040 | offset, 1041 | value, 1042 | }); 1043 | 1044 | /** 1045 | * Creates a node for the `store16` instruction. 1046 | * 1047 | * When compiled, results in 1048 | * ```wasm 1049 | * ([dataType].store16 1050 | * offset=[offset] ;; if not 0 1051 | * align=[align] ;; if not null 1052 | * [base] 1053 | * [value] 1054 | * ) 1055 | * ``` 1056 | * @param dataType the data type of the value. 1057 | * @param offset the literal offset value. 1058 | * @param align the optional literal align value. 1059 | * @param base the `Instr` node for the base address in memory. 1060 | * @param value the `Instr` node for the value to be stored. 1061 | */ 1062 | export const store16 = ( 1063 | dataType: IntegerDataType, 1064 | offset: number, 1065 | align: number | null, 1066 | base: Instr, 1067 | value: Instr, 1068 | ): Store16 => ({ 1069 | __nodeType: 'store16', 1070 | returnType: 'none', 1071 | dataType, 1072 | align, 1073 | base, 1074 | offset, 1075 | value, 1076 | }); 1077 | 1078 | /** 1079 | * Creates a node for the `store32` instruction. 1080 | * 1081 | * When compiled, results in 1082 | * ```wasm 1083 | * (i64.store32 1084 | * offset=[offset] ;; if not 0 1085 | * align=[align] ;; if not null 1086 | * [base] 1087 | * [value] 1088 | * ) 1089 | * ``` 1090 | * @param offset the literal offset value. 1091 | * @param align the optional literal align value. 1092 | * @param base the `Instr` node for the base address in memory. 1093 | * @param value the `Instr` node for the value to be stored. 1094 | */ 1095 | export const store32 = ( 1096 | offset: number, 1097 | align: number | null, 1098 | base: Instr, 1099 | value: Instr, 1100 | ): Store32 => ({ 1101 | __nodeType: 'store32', 1102 | returnType: 'none', 1103 | dataType: 'i64', 1104 | align, 1105 | base, 1106 | offset, 1107 | value, 1108 | }); 1109 | 1110 | /** 1111 | * Creates a node for the `loop` instruction. 1112 | * 1113 | * When compiled, results in 1114 | * ```wasm 1115 | * (loop 1116 | * $[name] ;; if name is not null 1117 | * [...body] ;; for each body Instr node 1118 | * ) 1119 | * ``` 1120 | * @param name The optional name of the loop. 1121 | * @param body The list of `Instr` nodes for the body of the loop. 1122 | */ 1123 | export const loop = (name: string | null, body: Instr<'none'>[]): Loop => ({ 1124 | __nodeType: 'loop', 1125 | returnType: 'none', 1126 | name, 1127 | body, 1128 | }); 1129 | 1130 | /** 1131 | * Creates a node for the `br_if` instruction. 1132 | * 1133 | * When compield, results in 1134 | * ```wasm 1135 | * (br_if 1136 | * $[branchTo] or [branchTo] ;; if branchTo is a string or number 1137 | * [cond] 1138 | * ) 1139 | * ``` 1140 | * @param branchTo The name or distance. 1141 | * @param cond The `Instr` node for the condition to evaluate. 1142 | */ 1143 | export const branchIf = ( 1144 | branchTo: string | number, 1145 | cond: Instr, 1146 | ): BranchIf => ({ 1147 | __nodeType: 'branchIf', 1148 | returnType: 'none', 1149 | branchTo, 1150 | cond, 1151 | }); 1152 | 1153 | /** 1154 | * Creates a node for the `br` instruction. 1155 | * 1156 | * When compield, results in 1157 | * ```wasm 1158 | * (br 1159 | * $[branchTo] or [branchTo] ;; if branchTo is a string or number 1160 | * ) 1161 | * ``` 1162 | * @param branchTo The name or distance. 1163 | */ 1164 | export const branch = (branchTo: string | number): Branch => ({ 1165 | __nodeType: 'branch', 1166 | returnType: 'none', 1167 | branchTo, 1168 | }); 1169 | 1170 | /** 1171 | * Creates a node for the `eqz` instruction. 1172 | * 1173 | * When compiled, results in 1174 | * ```wasm 1175 | * ([dataType].eqz 1176 | * [right] 1177 | * ) 1178 | * ``` 1179 | * @param dataType The data type of the instruction and thus, the right operand. 1180 | * @param right The `Instr` node for the right operand. 1181 | */ 1182 | export const equalZero = ( 1183 | dataType: T, 1184 | right: Instr, 1185 | ): EqualZero => ({ 1186 | __nodeType: 'equalZero', 1187 | returnType: 'i32', 1188 | dataType: dataType, 1189 | right, 1190 | }); 1191 | 1192 | /** 1193 | * Creates a node for the `eq` instruction. 1194 | * 1195 | * When compiled, results in 1196 | * ```wasm 1197 | * ([dataType].eqz 1198 | * [left] 1199 | * [right] 1200 | * ) 1201 | * ``` 1202 | * @param dataType The data type of the instruction and thus, the right operand. 1203 | * @param right The `Instr` node for the right operand. 1204 | */ 1205 | export const equal = ( 1206 | dataType: T, 1207 | left: Instr, 1208 | right: Instr, 1209 | ): Equal => ({ 1210 | __nodeType: 'equal', 1211 | returnType: 'i32', 1212 | dataType: dataType, 1213 | left, 1214 | right, 1215 | }); 1216 | 1217 | /** 1218 | * Creates a node for the `ne` instruction. 1219 | * 1220 | * When compiled, results in 1221 | * ```wasm 1222 | * ([dataType].eqz 1223 | * [left] 1224 | * [right] 1225 | * ) 1226 | * ``` 1227 | * @param dataType The data type of the instruction and thus, the right operand. 1228 | * @param right The `Instr` node for the right operand. 1229 | */ 1230 | export const notEqual = ( 1231 | dataType: T, 1232 | left: Instr, 1233 | right: Instr, 1234 | ): NotEqual => ({ 1235 | __nodeType: 'notEqual', 1236 | returnType: 'i32', 1237 | dataType: dataType, 1238 | left, 1239 | right, 1240 | }); 1241 | 1242 | /** 1243 | * Creates a node for the `gt_s` instruction. 1244 | * 1245 | * When compiled, results in 1246 | * ```wasm 1247 | * ([dataType].gt_s 1248 | * [left] 1249 | * [right] 1250 | * ) 1251 | * ``` 1252 | * @param dataType The data type of the instruction and thus, the right operand. 1253 | * @param right The `Instr` node for the right operand. 1254 | */ 1255 | export const greaterThanSigned = ( 1256 | dataType: T, 1257 | left: Instr, 1258 | right: Instr, 1259 | ): GreaterThanSigned => ({ 1260 | __nodeType: 'greaterThanSigned', 1261 | returnType: 'i32', 1262 | dataType: dataType, 1263 | left, 1264 | right, 1265 | }); 1266 | 1267 | /** 1268 | * Creates a node for the `gt_u` instruction. 1269 | * 1270 | * When compiled, results in 1271 | * ```wasm 1272 | * ([dataType].gt_u 1273 | * [left] 1274 | * [right] 1275 | * ) 1276 | * ``` 1277 | * @param dataType The data type of the instruction and thus, the right operand. 1278 | * @param left The `Instr` node for the left operand. 1279 | * @param right The `Instr` node for the right operand. 1280 | */ 1281 | export const greaterThanUnsigned = ( 1282 | dataType: T, 1283 | left: Instr, 1284 | right: Instr, 1285 | ): GreaterThanUnsigned => ({ 1286 | __nodeType: 'greaterThanUnsigned', 1287 | returnType: 'i32', 1288 | dataType: dataType, 1289 | left, 1290 | right, 1291 | }); 1292 | 1293 | /** 1294 | * Creates a node for the `lt_s` instruction. 1295 | * 1296 | * When compiled, results in 1297 | * ```wasm 1298 | * ([dataType].lt_s 1299 | * [left] 1300 | * [right] 1301 | * ) 1302 | * ``` 1303 | * @param dataType The data type of the instruction and thus, the right operand. 1304 | * @param left The `Instr` node for the left operand. 1305 | * @param right The `Instr` node for the right operand. 1306 | */ 1307 | export const lessThanSigned = ( 1308 | dataType: T, 1309 | left: Instr, 1310 | right: Instr, 1311 | ): LessThanSigned => ({ 1312 | __nodeType: 'lessThanSigned', 1313 | returnType: 'i32', 1314 | dataType: dataType, 1315 | left, 1316 | right, 1317 | }); 1318 | 1319 | /** 1320 | * Creates a node for the `lt_u` instruction. 1321 | * 1322 | * When compiled, results in 1323 | * ```wasm 1324 | * ([dataType].lt_u 1325 | * [left] 1326 | * [right] 1327 | * ) 1328 | * ``` 1329 | * @param dataType The data type of the instruction and thus, the right operand. 1330 | * @param left The `Instr` node for the left operand. 1331 | * @param right The `Instr` node for the right operand. 1332 | */ 1333 | export const lessThanUnsigned = ( 1334 | dataType: T, 1335 | left: Instr, 1336 | right: Instr, 1337 | ): LessThanUnsigned => ({ 1338 | __nodeType: 'lessThanUnsigned', 1339 | returnType: 'i32', 1340 | dataType: dataType, 1341 | left, 1342 | right, 1343 | }); 1344 | 1345 | /** 1346 | * Creates a node for the `ge_s` instruction. 1347 | * 1348 | * When compiled, results in 1349 | * ```wasm 1350 | * ([dataType].ge_s 1351 | * [left] 1352 | * [right] 1353 | * ) 1354 | * ``` 1355 | * @param dataType The data type of the instruction and thus, the right operand. 1356 | * @param left The `Instr` node for the left operand. 1357 | * @param right The `Instr` node for the right operand. 1358 | */ 1359 | export const greaterEqualSigned = ( 1360 | dataType: T, 1361 | left: Instr, 1362 | right: Instr, 1363 | ): GreaterEqualSigned => ({ 1364 | __nodeType: 'greaterEqualSigned', 1365 | returnType: 'i32', 1366 | dataType: dataType, 1367 | left, 1368 | right, 1369 | }); 1370 | 1371 | /** 1372 | * Creates a node for the `ge_u` instruction. 1373 | * 1374 | * When compiled, results in 1375 | * ```wasm 1376 | * ([dataType].ge_u 1377 | * [left] 1378 | * [right] 1379 | * ) 1380 | * ``` 1381 | * @param dataType The data type of the instruction and thus, the right operand. 1382 | * @param left The `Instr` node for the left operand. 1383 | * @param right The `Instr` node for the right operand. 1384 | */ 1385 | export const greaterEqualUnsigned = ( 1386 | dataType: T, 1387 | left: Instr, 1388 | right: Instr, 1389 | ): GreaterEqualUnsigned => ({ 1390 | __nodeType: 'greaterEqualUnsigned', 1391 | returnType: 'i32', 1392 | dataType: dataType, 1393 | left, 1394 | right, 1395 | }); 1396 | 1397 | /** 1398 | * Creates a node for the `le_s` instruction. 1399 | * 1400 | * When compiled, results in 1401 | * ```wasm 1402 | * ([dataType].le_s 1403 | * [left] 1404 | * [right] 1405 | * ) 1406 | * ``` 1407 | * @param dataType The data type of the instruction and thus, the right operand. 1408 | * @param left The `Instr` node for the left operand. 1409 | * @param right The `Instr` node for the right operand. 1410 | */ 1411 | export const lessEqualSigned = ( 1412 | dataType: T, 1413 | left: Instr, 1414 | right: Instr, 1415 | ): LessEqualSigned => ({ 1416 | __nodeType: 'lessEqualSigned', 1417 | returnType: 'i32', 1418 | dataType: dataType, 1419 | left, 1420 | right, 1421 | }); 1422 | 1423 | /** 1424 | * Creates a node for the `le_u` instruction. 1425 | * 1426 | * When compiled, results in 1427 | * ```wasm 1428 | * ([dataType].le_u 1429 | * [left] 1430 | * [right] 1431 | * ) 1432 | * ``` 1433 | * @param dataType The data type of the instruction and thus, the right operand. 1434 | * @param left The `Instr` node for the left operand. 1435 | * @param right The `Instr` node for the right operand. 1436 | */ 1437 | export const lessEqualUnsigned = ( 1438 | dataType: T, 1439 | left: Instr, 1440 | right: Instr, 1441 | ): LessEqualUnsigned => ({ 1442 | __nodeType: 'lessEqualUnsigned', 1443 | returnType: 'i32', 1444 | dataType: dataType, 1445 | left, 1446 | right, 1447 | }); 1448 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { compile } from './compiler'; 2 | import { 3 | DataType, 4 | Func, 5 | Instr, 6 | IntegerDataType, 7 | NumericDataType, 8 | PresentDataType, 9 | } from './nodes'; 10 | 11 | export type Memory = { 12 | name: string; 13 | initSize: number; 14 | maxSize: number; 15 | segments: MemorySegment[]; 16 | }; 17 | 18 | export type MemorySegment = { 19 | data: Uint8Array; 20 | offset: Instr; 21 | }; 22 | 23 | export type Table = { 24 | type: 'funcref'; 25 | name: string; 26 | initSize: number; 27 | maxSize: number; 28 | segments: TableSegment[]; 29 | }; 30 | 31 | export type TableSegment = { 32 | elems: string[]; 33 | offset: Instr; 34 | }; 35 | 36 | export type Global = { 37 | name: string; 38 | dataType: DataType; 39 | initVal: Instr; 40 | mutable: boolean; 41 | }; 42 | 43 | export class Module { 44 | funcs: Func[] = []; 45 | memories: Memory[] = []; 46 | tables: Table[] = []; 47 | globals: Global[] = []; 48 | start: string | null = null; 49 | 50 | constructor() {} 51 | 52 | addFunc = (func: Func, exported?: boolean | string) => { 53 | if (exported) { 54 | func.exportName = typeof exported === 'boolean' ? func.name : exported; 55 | } 56 | this.funcs.push(func); 57 | }; 58 | 59 | addMemory = ( 60 | name: string, 61 | initSize: number, 62 | maxSize: number, 63 | segments: MemorySegment[], 64 | ) => { 65 | if (this.memories.length) 66 | throw new Error('Multiple memories not supported yet'); 67 | this.memories.push({ 68 | name, 69 | initSize, 70 | maxSize, 71 | segments, 72 | }); 73 | }; 74 | 75 | addTable = ( 76 | name: string, 77 | initSize: number, 78 | maxSize: number, 79 | type: 'funcref', 80 | segments: TableSegment[], 81 | ) => { 82 | this.tables.push({ type, name, initSize, maxSize, segments }); 83 | }; 84 | 85 | addGlobal = ( 86 | name: string, 87 | dataType: DataType, 88 | mutable: boolean, 89 | initVal: Instr, 90 | ) => { 91 | this.globals.push({ name, dataType, initVal, mutable }); 92 | }; 93 | 94 | setStart = (name: string | null) => { 95 | this.start = name; 96 | }; 97 | 98 | compile = () => compile(this); 99 | } 100 | -------------------------------------------------------------------------------- /src/nodes.ts: -------------------------------------------------------------------------------- 1 | import { Replace } from './utils'; 2 | 3 | export type IntegerDataType = 'i32' | 'i64'; 4 | export type FloatDataType = 'f32' | 'f64'; 5 | export type NumericDataType = IntegerDataType | FloatDataType; 6 | export type PresentDataType = NumericDataType; 7 | export type DataType = NumericDataType | 'none'; 8 | 9 | /** 10 | * The node representing the `local.get` instruction. 11 | */ 12 | export type LocalGet = { 13 | __nodeType: 'localGet'; 14 | dataType: T; 15 | name: string; 16 | returnType: T; 17 | }; 18 | /** 19 | * The node representing the `local.set` instruction. 20 | */ 21 | export type LocalSet = { 22 | __nodeType: 'localSet'; 23 | dataType: NumericDataType; 24 | name: string; 25 | value: Instr; 26 | returnType: 'none'; 27 | }; 28 | /** 29 | * The node representing the `local.tee` instruction. 30 | */ 31 | export type LocalTee = { 32 | __nodeType: 'localTee'; 33 | dataType: T; 34 | name: string; 35 | value: Instr; 36 | returnType: T; 37 | }; 38 | /** 39 | * The node representing the `global.get` instruction. 40 | */ 41 | export type GlobalGet = { 42 | __nodeType: 'globalGet'; 43 | dataType: T; 44 | name: string; 45 | returnType: T; 46 | }; 47 | /** 48 | * The node representing the `global.set` instruction. 49 | */ 50 | export type GlobalSet = { 51 | __nodeType: 'globalSet'; 52 | dataType: NumericDataType; 53 | name: string; 54 | value: Instr; 55 | returnType: 'none'; 56 | }; 57 | /** 58 | * The node representing the `global.tee` instruction. 59 | */ 60 | export type GlobalTee = { 61 | __nodeType: 'globalTee'; 62 | dataType: T; 63 | name: string; 64 | value: Instr; 65 | returnType: T; 66 | }; 67 | /** 68 | * The node representing the `clz` instruction. 69 | */ 70 | export type Clz = { 71 | __nodeType: 'clz'; 72 | dataType: T; 73 | left: Instr; 74 | right: Instr; 75 | returnType: T; 76 | }; 77 | /** 78 | * The node representing the `ctz` instruction. 79 | */ 80 | export type Ctz = { 81 | __nodeType: 'ctz'; 82 | dataType: T; 83 | left: Instr; 84 | right: Instr; 85 | returnType: T; 86 | }; 87 | /** 88 | * The node representing the `popcnt` instruction. 89 | */ 90 | export type Popcnt = { 91 | __nodeType: 'popcnt'; 92 | dataType: T; 93 | left: Instr; 94 | right: Instr; 95 | returnType: T; 96 | }; 97 | /** 98 | * The node representing the `add` instruction. 99 | */ 100 | export type Add = { 101 | __nodeType: 'add'; 102 | dataType: T; 103 | left: Instr; 104 | right: Instr; 105 | returnType: T; 106 | }; 107 | /** 108 | * The node representing the `sub` instruction. 109 | */ 110 | export type Sub = { 111 | __nodeType: 'sub'; 112 | dataType: T; 113 | left: Instr; 114 | right: Instr; 115 | returnType: T; 116 | }; 117 | /** 118 | * The node representing the `mul` instruction. 119 | */ 120 | export type Mul = { 121 | __nodeType: 'mul'; 122 | dataType: T; 123 | left: Instr; 124 | right: Instr; 125 | returnType: T; 126 | }; 127 | /** 128 | * The node representing the `div_s` instruction. 129 | */ 130 | export type DivSigned = { 131 | __nodeType: 'divSigned'; 132 | dataType: T; 133 | left: Instr; 134 | right: Instr; 135 | returnType: T; 136 | }; 137 | /** 138 | * The node representing the `div_u` instruction. 139 | */ 140 | export type DivUnsigned = { 141 | __nodeType: 'divUnsigned'; 142 | dataType: T; 143 | left: Instr; 144 | right: Instr; 145 | returnType: T; 146 | }; 147 | /** 148 | * The node representing the `rem_s` instruction. 149 | */ 150 | export type RemSigned = { 151 | __nodeType: 'remSigned'; 152 | dataType: T; 153 | left: Instr; 154 | right: Instr; 155 | returnType: T; 156 | }; 157 | /** 158 | * The node representing the `rem_u` instruction. 159 | */ 160 | export type RemUnsigned = { 161 | __nodeType: 'remUnsigned'; 162 | dataType: T; 163 | left: Instr; 164 | right: Instr; 165 | returnType: T; 166 | }; 167 | /** 168 | * The node representing the `and` instruction. 169 | */ 170 | export type And = { 171 | __nodeType: 'and'; 172 | dataType: T; 173 | left: Instr; 174 | right: Instr; 175 | returnType: T; 176 | }; 177 | /** 178 | * The node representing the `or` instruction. 179 | */ 180 | export type Or = { 181 | __nodeType: 'or'; 182 | dataType: T; 183 | left: Instr; 184 | right: Instr; 185 | returnType: T; 186 | }; 187 | /** 188 | * The node representing the `xor` instruction. 189 | */ 190 | export type Xor = { 191 | __nodeType: 'xor'; 192 | dataType: T; 193 | left: Instr; 194 | right: Instr; 195 | returnType: T; 196 | }; 197 | /** 198 | * The node representing the `shl` instruction. 199 | */ 200 | export type ShiftLeft = { 201 | __nodeType: 'shl'; 202 | dataType: T; 203 | left: Instr; 204 | right: Instr; 205 | returnType: T; 206 | }; 207 | /** 208 | * The node representing the `shr_s` instruction. 209 | */ 210 | export type ShiftRightSigned = { 211 | __nodeType: 'shrSigned'; 212 | dataType: T; 213 | left: Instr; 214 | right: Instr; 215 | returnType: T; 216 | }; 217 | /** 218 | * The node representing the `shr_u` instruction. 219 | */ 220 | export type ShiftRightUnsigned = { 221 | __nodeType: 'shrUnsigned'; 222 | dataType: T; 223 | left: Instr; 224 | right: Instr; 225 | returnType: T; 226 | }; 227 | /** 228 | * The node representing the `rotl` instruction. 229 | */ 230 | export type RotateLeft = { 231 | __nodeType: 'rotl'; 232 | dataType: T; 233 | left: Instr; 234 | right: Instr; 235 | returnType: T; 236 | }; 237 | /** 238 | * The node representing the `rotr` instruction. 239 | */ 240 | export type RotateRight = { 241 | __nodeType: 'rotr'; 242 | dataType: T; 243 | left: Instr; 244 | right: Instr; 245 | returnType: T; 246 | }; 247 | /** 248 | * The node representing the `func` declaration. 249 | * Note: This is not an instruction node. 250 | */ 251 | export type Func = { 252 | __nodeType: 'func'; 253 | name: string; 254 | params: [type: NumericDataType, name: string][]; 255 | locals: [type: NumericDataType, name: string][]; 256 | body: Instr; 257 | dataType: T; 258 | exportName: string | null; 259 | }; 260 | /** 261 | * The node representing the `call` instruction. 262 | */ 263 | export type Call = { 264 | __nodeType: 'call'; 265 | name: string; 266 | args: Instr[]; 267 | returnType: T; 268 | }; 269 | /** 270 | * The node representing the `call_indirect` instruction. 271 | */ 272 | export type CallIndirect = { 273 | __nodeType: 'callIndirect'; 274 | dataType: T; 275 | params: [type: DataType, name?: string][]; 276 | args: Instr[]; 277 | tableName: string; 278 | address: Instr; 279 | returnType: T; 280 | }; 281 | /** 282 | * The node representing the `drop` instruction. 283 | */ 284 | export type Drop = { 285 | __nodeType: 'drop'; 286 | value: Instr; 287 | returnType: 'none'; 288 | }; 289 | /** 290 | * The node representing the `block` instruction. 291 | */ 292 | export type Block = { 293 | __nodeType: 'block'; 294 | name: string | null; 295 | body: [...Instr<'none'>[], Instr] | [Instr] | []; 296 | returnType: T; 297 | }; 298 | /** 299 | * The node representing the `const` instruction. 300 | */ 301 | export type Const = { 302 | __nodeType: 'constant'; 303 | value: number; 304 | dataType: T; 305 | returnType: T; 306 | }; 307 | /** 308 | * The node representing the `load` instruction. 309 | */ 310 | export type Load = { 311 | __nodeType: 'load'; 312 | offset: number; 313 | align: number | null; 314 | base: Instr; 315 | dataType: T; 316 | returnType: T; 317 | }; 318 | /** 319 | * The node representing the `load8_s` instruction. 320 | */ 321 | export type Load8SignExt = Replace< 322 | Load, 323 | { __nodeType: 'load8SignExt' } 324 | >; 325 | /** 326 | * The node representing the `load16_s` instruction. 327 | */ 328 | export type Load16SignExt = 329 | Replace, { __nodeType: 'load16SignExt' }>; 330 | /** 331 | * The node representing the `load32_s` instruction. 332 | */ 333 | export type Load32SignExt = 334 | Replace< 335 | Load, 336 | { __nodeType: 'load32SignExt'; dataType: 'i64'; returnType: 'i64' } 337 | >; 338 | /** 339 | * The node representing the `load8_u` instruction. 340 | */ 341 | export type Load8ZeroExt = Replace< 342 | Load, 343 | { __nodeType: 'load8ZeroExt' } 344 | >; 345 | /** 346 | * The node representing the `load16_u` instruction. 347 | */ 348 | export type Load16ZeroExt = 349 | Replace, { __nodeType: 'load16ZeroExt' }>; 350 | /** 351 | * The node representing the `load32_u` instruction. 352 | */ 353 | export type Load32ZeroExt = 354 | Replace< 355 | Load, 356 | { __nodeType: 'load32ZeroExt'; dataType: 'i64'; returnType: 'i64' } 357 | >; 358 | /** 359 | * The node representing the `store` instruction. 360 | */ 361 | export type Store = { 362 | __nodeType: 'store'; 363 | dataType: NumericDataType; 364 | offset: number; 365 | align: number | null; 366 | base: Instr; 367 | value: Instr; 368 | returnType: 'none'; 369 | }; 370 | /** 371 | * The node representing the `store8` instruction. 372 | */ 373 | export type Store8 = Replace; 374 | /** 375 | * The node representing the `store16` instruction. 376 | */ 377 | export type Store16 = Replace; 378 | /** 379 | * The node representing the `store32` instruction. 380 | */ 381 | export type Store32 = Replace< 382 | Store, 383 | { __nodeType: 'store32'; dataType: 'i64' } 384 | >; 385 | 386 | /** 387 | * The node representing the `loop` instruction. 388 | */ 389 | export type Loop = { 390 | __nodeType: 'loop'; 391 | returnType: 'none'; 392 | name: string | null; 393 | body: Instr<'none'>[]; 394 | }; 395 | 396 | /** 397 | * The node representing the `br_if` instruction. 398 | */ 399 | export type BranchIf = { 400 | __nodeType: 'branchIf'; 401 | returnType: 'none'; 402 | branchTo: string | number; 403 | cond: Instr; 404 | }; 405 | 406 | /** 407 | * The node representing the `br_if` instruction. 408 | */ 409 | export type Branch = { 410 | __nodeType: 'branch'; 411 | returnType: 'none'; 412 | branchTo: string | number; 413 | }; 414 | 415 | /** 416 | * The node representing the `eq` instruction. 417 | */ 418 | export type Equal = { 419 | __nodeType: 'equal'; 420 | returnType: 'i32'; 421 | dataType: T; 422 | left: Instr; 423 | right: Instr; 424 | }; 425 | 426 | /** 427 | * The node representing the `eqz` instruction. 428 | */ 429 | export type EqualZero = { 430 | __nodeType: 'equalZero'; 431 | returnType: 'i32'; 432 | dataType: T; 433 | right: Instr; 434 | }; 435 | 436 | /** 437 | * The node representing the `eq` instruction. 438 | */ 439 | export type NotEqual = { 440 | __nodeType: 'notEqual'; 441 | returnType: 'i32'; 442 | dataType: T; 443 | left: Instr; 444 | right: Instr; 445 | }; 446 | 447 | /** 448 | * The node representing the `gt_s` instruction. 449 | */ 450 | export type GreaterThanSigned = { 451 | __nodeType: 'greaterThanSigned'; 452 | returnType: 'i32'; 453 | dataType: T; 454 | left: Instr; 455 | right: Instr; 456 | }; 457 | 458 | /** 459 | * The node representing the `gt_u` instruction. 460 | */ 461 | export type GreaterThanUnsigned = { 462 | __nodeType: 'greaterThanUnsigned'; 463 | returnType: 'i32'; 464 | dataType: T; 465 | left: Instr; 466 | right: Instr; 467 | }; 468 | 469 | /** 470 | * The node representing the `lt_s` instruction. 471 | */ 472 | export type LessThanSigned = { 473 | __nodeType: 'lessThanSigned'; 474 | returnType: 'i32'; 475 | dataType: T; 476 | left: Instr; 477 | right: Instr; 478 | }; 479 | 480 | /** 481 | * The node representing the `lt_u` instruction. 482 | */ 483 | export type LessThanUnsigned = { 484 | __nodeType: 'lessThanUnsigned'; 485 | returnType: 'i32'; 486 | dataType: T; 487 | left: Instr; 488 | right: Instr; 489 | }; 490 | 491 | /** 492 | * The node representing the `ge_s` instruction. 493 | */ 494 | export type GreaterEqualSigned = { 495 | __nodeType: 'greaterEqualSigned'; 496 | returnType: 'i32'; 497 | dataType: T; 498 | left: Instr; 499 | right: Instr; 500 | }; 501 | 502 | /** 503 | * The node representing the `ge_u` instruction. 504 | */ 505 | export type GreaterEqualUnsigned = 506 | { 507 | __nodeType: 'greaterEqualUnsigned'; 508 | returnType: 'i32'; 509 | dataType: T; 510 | left: Instr; 511 | right: Instr; 512 | }; 513 | 514 | /** 515 | * The node representing the `le_s` instruction. 516 | */ 517 | export type LessEqualSigned = { 518 | __nodeType: 'lessEqualSigned'; 519 | returnType: 'i32'; 520 | dataType: T; 521 | left: Instr; 522 | right: Instr; 523 | }; 524 | 525 | /** 526 | * The node representing the `le_u` instruction. 527 | */ 528 | export type LessEqualUnsigned = { 529 | __nodeType: 'lessEqualUnsigned'; 530 | returnType: 'i32'; 531 | dataType: T; 532 | left: Instr; 533 | right: Instr; 534 | }; 535 | 536 | /** 537 | * All possible instruction nodes. 538 | */ 539 | type InstrList = 540 | | Drop 541 | | LocalSet 542 | | GlobalSet 543 | | Store 544 | | Store8 545 | | Store16 546 | | Store32 547 | | LocalGet 548 | | LocalTee 549 | | GlobalGet 550 | | GlobalTee 551 | | Block 552 | | Call 553 | | CallIndirect 554 | | Const 555 | | Clz 556 | | Ctz 557 | | Popcnt 558 | | Add 559 | | Sub 560 | | Mul 561 | | DivSigned 562 | | DivUnsigned 563 | | RemSigned 564 | | RemUnsigned 565 | | And 566 | | Or 567 | | Xor 568 | | ShiftLeft 569 | | ShiftRightSigned 570 | | ShiftRightUnsigned 571 | | RotateLeft 572 | | RotateRight 573 | | Load 574 | | Load8SignExt 575 | | Load16SignExt 576 | | Load32SignExt 577 | | Load8ZeroExt 578 | | Load16ZeroExt 579 | | Load32ZeroExt 580 | | Loop 581 | | Branch 582 | | BranchIf 583 | | Equal 584 | | EqualZero 585 | | GreaterThanSigned 586 | | GreaterThanUnsigned 587 | | LessThanSigned 588 | | LessThanUnsigned 589 | | GreaterEqualSigned 590 | | GreaterEqualUnsigned 591 | | LessEqualSigned 592 | | LessEqualUnsigned; 593 | 594 | type FilterInstrByDataType = I extends any 595 | ? I['returnType'] & DT extends never 596 | ? never 597 | : I 598 | : never; 599 | 600 | /** 601 | * This generic type returns a union of all the instruction node types 602 | * that have a compatible return type. 603 | * 604 | * For instance, `Instr<'i32'>` will be the union of all instruction node types 605 | * that can have `returnType === 'i32'`. 606 | */ 607 | export type Instr
= FilterInstrByDataType< 608 | InstrList, 609 | DT 610 | >; 611 | -------------------------------------------------------------------------------- /src/reexport.ts: -------------------------------------------------------------------------------- 1 | export * from './methods'; 2 | export * from './nodes'; 3 | export * from './module'; 4 | export { compilers } from './compiler'; 5 | -------------------------------------------------------------------------------- /src/test/__snapshots__/compiler.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`compiler add function (local.get, i32.add, exported func) 1`] = ` 4 | "(module 5 | (export "add" (func $add)) 6 | (func $add 7 | (param $a i32) 8 | (param $b i32) 9 | (result i32) 10 | (i32.add 11 | (local.get $a) 12 | (local.get $b) 13 | ) 14 | ) 15 | )" 16 | `; 17 | 18 | exports[`compiler add node 1`] = ` 19 | "(i32.add 20 | (i32.const 10) 21 | (i32.const 5) 22 | )" 23 | `; 24 | 25 | exports[`compiler and node 1`] = ` 26 | "(i32.and 27 | (i32.const 15790320) 28 | (i32.const 986895) 29 | )" 30 | `; 31 | 32 | exports[`compiler call node 1`] = ` 33 | "(call $abc 34 | (i32.const 10) 35 | )" 36 | `; 37 | 38 | exports[`compiler call_indirect node 1`] = ` 39 | "(call_indirect 40 | $0 41 | (param i32) 42 | (param i32) 43 | (result i32) 44 | (i32.const 3) 45 | (i32.const 4) 46 | (i32.const 0) 47 | )" 48 | `; 49 | 50 | exports[`compiler clz node 1`] = ` 51 | "(i32.clz 52 | (i32.const 1) 53 | (i32.const 2) 54 | )" 55 | `; 56 | 57 | exports[`compiler const node 1`] = `"(f32.const 1.5)"`; 58 | 59 | exports[`compiler ctz node 1`] = ` 60 | "(i32.ctz 61 | (i32.const 1) 62 | (i32.const 2) 63 | )" 64 | `; 65 | 66 | exports[`compiler div_s node 1`] = ` 67 | "(i32.div_s 68 | (i32.const 10) 69 | (i32.const 5) 70 | )" 71 | `; 72 | 73 | exports[`compiler div_u node 1`] = ` 74 | "(i32.div_u 75 | (i32.const 10) 76 | (i32.const 5) 77 | )" 78 | `; 79 | 80 | exports[`compiler localGet node 1`] = `"(local.get $abc)"`; 81 | 82 | exports[`compiler localSet node 1`] = ` 83 | "(local.set $abc 84 | (f64.const 10) 85 | )" 86 | `; 87 | 88 | exports[`compiler mul node 1`] = ` 89 | "(i32.mul 90 | (i32.const 10) 91 | (i32.const 5) 92 | )" 93 | `; 94 | 95 | exports[`compiler or node 1`] = ` 96 | "(i32.or 97 | (i32.const 15790320) 98 | (i32.const 986895) 99 | )" 100 | `; 101 | 102 | exports[`compiler popcnt node 1`] = ` 103 | "(i32.popcnt 104 | (i32.const 1) 105 | (i32.const 2) 106 | )" 107 | `; 108 | 109 | exports[`compiler rem_s node 1`] = ` 110 | "(i32.rem_s 111 | (i32.const 10) 112 | (i32.const 5) 113 | )" 114 | `; 115 | 116 | exports[`compiler rem_u node 1`] = ` 117 | "(i32.rem_u 118 | (i32.const 10) 119 | (i32.const 5) 120 | )" 121 | `; 122 | 123 | exports[`compiler rotl node 1`] = ` 124 | "(i32.rotl 125 | (i32.const 20) 126 | (i32.const 2) 127 | )" 128 | `; 129 | 130 | exports[`compiler rotr node 1`] = ` 131 | "(i32.rotr 132 | (i32.const 20) 133 | (i32.const 2) 134 | )" 135 | `; 136 | 137 | exports[`compiler shl node 1`] = ` 138 | "(i32.shl 139 | (i32.const 10) 140 | (i32.const 5) 141 | )" 142 | `; 143 | 144 | exports[`compiler shr_s node 1`] = ` 145 | "(i32.shr_s 146 | (i32.const 20) 147 | (i32.const 2) 148 | )" 149 | `; 150 | 151 | exports[`compiler shr_u node 1`] = ` 152 | "(i32.shr_u 153 | (i32.const 20) 154 | (i32.const 2) 155 | )" 156 | `; 157 | 158 | exports[`compiler sub node 1`] = ` 159 | "(i32.sub 160 | (i32.const 10) 161 | (i32.const 5) 162 | )" 163 | `; 164 | 165 | exports[`compiler xor node 1`] = ` 166 | "(i32.xor 167 | (i32.const 15790320) 168 | (i32.const 986895) 169 | )" 170 | `; 171 | -------------------------------------------------------------------------------- /src/test/__snapshots__/tree.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`tree creation add constant 1`] = ` 4 | { 5 | "__nodeType": "add", 6 | "dataType": "i32", 7 | "left": { 8 | "__nodeType": "constant", 9 | "dataType": "i32", 10 | "returnType": "i32", 11 | "value": 1, 12 | }, 13 | "returnType": "i32", 14 | "right": { 15 | "__nodeType": "constant", 16 | "dataType": "i32", 17 | "returnType": "i32", 18 | "value": 2, 19 | }, 20 | } 21 | `; 22 | 23 | exports[`tree creation addFunc 1`] = ` 24 | [ 25 | { 26 | "__nodeType": "func", 27 | "body": { 28 | "__nodeType": "add", 29 | "dataType": "i32", 30 | "left": { 31 | "__nodeType": "localGet", 32 | "dataType": "i32", 33 | "name": "a", 34 | "returnType": "i32", 35 | }, 36 | "returnType": "i32", 37 | "right": { 38 | "__nodeType": "localGet", 39 | "dataType": "i32", 40 | "name": "b", 41 | "returnType": "i32", 42 | }, 43 | }, 44 | "dataType": "i32", 45 | "exportName": null, 46 | "locals": [], 47 | "name": "add", 48 | "params": [ 49 | [ 50 | "i32", 51 | "a", 52 | ], 53 | [ 54 | "i32", 55 | "b", 56 | ], 57 | ], 58 | }, 59 | { 60 | "__nodeType": "func", 61 | "body": { 62 | "__nodeType": "add", 63 | "dataType": "i32", 64 | "left": { 65 | "__nodeType": "localGet", 66 | "dataType": "i32", 67 | "name": "a", 68 | "returnType": "i32", 69 | }, 70 | "returnType": "i32", 71 | "right": { 72 | "__nodeType": "localGet", 73 | "dataType": "i32", 74 | "name": "b", 75 | "returnType": "i32", 76 | }, 77 | }, 78 | "dataType": "i32", 79 | "exportName": "add", 80 | "locals": [], 81 | "name": "add", 82 | "params": [ 83 | [ 84 | "i32", 85 | "a", 86 | ], 87 | [ 88 | "i32", 89 | "b", 90 | ], 91 | ], 92 | }, 93 | { 94 | "__nodeType": "func", 95 | "body": { 96 | "__nodeType": "add", 97 | "dataType": "i32", 98 | "left": { 99 | "__nodeType": "localGet", 100 | "dataType": "i32", 101 | "name": "a", 102 | "returnType": "i32", 103 | }, 104 | "returnType": "i32", 105 | "right": { 106 | "__nodeType": "localGet", 107 | "dataType": "i32", 108 | "name": "b", 109 | "returnType": "i32", 110 | }, 111 | }, 112 | "dataType": "i32", 113 | "exportName": "addi32", 114 | "locals": [], 115 | "name": "add", 116 | "params": [ 117 | [ 118 | "i32", 119 | "a", 120 | ], 121 | [ 122 | "i32", 123 | "b", 124 | ], 125 | ], 126 | }, 127 | ] 128 | `; 129 | 130 | exports[`tree creation function 1`] = ` 131 | { 132 | "__nodeType": "func", 133 | "body": { 134 | "__nodeType": "block", 135 | "body": [ 136 | { 137 | "__nodeType": "drop", 138 | "returnType": "none", 139 | "value": { 140 | "__nodeType": "add", 141 | "dataType": "i32", 142 | "left": { 143 | "__nodeType": "localGet", 144 | "dataType": "i32", 145 | "name": "a", 146 | "returnType": "i32", 147 | }, 148 | "returnType": "i32", 149 | "right": { 150 | "__nodeType": "localGet", 151 | "dataType": "i32", 152 | "name": "b", 153 | "returnType": "i32", 154 | }, 155 | }, 156 | }, 157 | { 158 | "__nodeType": "localSet", 159 | "dataType": "i32", 160 | "name": "ret", 161 | "returnType": "none", 162 | "value": { 163 | "__nodeType": "add", 164 | "dataType": "i32", 165 | "left": { 166 | "__nodeType": "localGet", 167 | "dataType": "i32", 168 | "name": "a", 169 | "returnType": "i32", 170 | }, 171 | "returnType": "i32", 172 | "right": { 173 | "__nodeType": "localGet", 174 | "dataType": "i32", 175 | "name": "b", 176 | "returnType": "i32", 177 | }, 178 | }, 179 | }, 180 | { 181 | "__nodeType": "localGet", 182 | "dataType": "i32", 183 | "name": "ret", 184 | "returnType": "i32", 185 | }, 186 | ], 187 | "name": null, 188 | "returnType": "i32", 189 | }, 190 | "dataType": "i32", 191 | "exportName": null, 192 | "locals": [ 193 | [ 194 | "i32", 195 | "ret", 196 | ], 197 | ], 198 | "name": "add", 199 | "params": [ 200 | [ 201 | "i32", 202 | "a", 203 | ], 204 | [ 205 | "i32", 206 | "b", 207 | ], 208 | ], 209 | } 210 | `; 211 | 212 | exports[`tree creation function call 1`] = ` 213 | { 214 | "__nodeType": "call", 215 | "args": [ 216 | { 217 | "__nodeType": "localGet", 218 | "dataType": "i32", 219 | "name": "a", 220 | "returnType": "i32", 221 | }, 222 | ], 223 | "name": "add", 224 | "returnType": "i32", 225 | } 226 | `; 227 | -------------------------------------------------------------------------------- /src/test/brainfuck.test.ts: -------------------------------------------------------------------------------- 1 | import { w } from '../index'; 2 | 3 | // define where the data part starts 4 | const DATA_START = 64; 5 | 6 | // our switch case 7 | const instrMap = { 8 | '>': [ 9 | // C equivalent: 10 | // dataPtr = dataPtr + 1 11 | // 12 | // compiles to: 13 | // (local.set $dataPtr 14 | // (i32.add 15 | // (local.get $dataPtr) 16 | // (i32.const 1) 17 | // ) 18 | // ) 19 | w.local.set( 20 | 'i32', 21 | 'dataPtr', 22 | w.add('i32', w.local.get('i32', 'dataPtr'), w.constant('i32', 1)), 23 | ), 24 | ], 25 | '<': [ 26 | // C equivalent: 27 | // dataPtr = dataPtr - 1 28 | // 29 | // compiles to: 30 | // (local.set $dataPtr 31 | // (i32.sub 32 | // (local.get $dataPtr) 33 | // (i32.const 1) 34 | // ) 35 | // ) 36 | w.local.set( 37 | 'i32', 38 | 'dataPtr', 39 | w.sub('i32', w.local.get('i32', 'dataPtr'), w.constant('i32', 1)), 40 | ), 41 | ], 42 | '+': [ 43 | // C equivalent: 44 | // *dataPtr = *dataPtr + 1 45 | // 46 | // compiles to: 47 | // (i32.store8 48 | // (local.get $dataPtr) 49 | // (i32.add 50 | // (i32.load8_u 51 | // (local.get $dataPtr) 52 | // ) 53 | // (i32.const 1) 54 | // ) 55 | // ) 56 | w.store8( 57 | 'i32', 58 | 0, 59 | null, 60 | w.local.get('i32', 'dataPtr'), 61 | w.add( 62 | 'i32', 63 | w.load8ZeroExt('i32', 0, null, w.local.get('i32', 'dataPtr')), 64 | w.constant('i32', 1), 65 | ), 66 | ), 67 | ], 68 | '-': [ 69 | // C equivalent: 70 | // *dataPtr = *dataPtr - 1 71 | // 72 | // compiles to: 73 | // (i32.store8 74 | // (local.get $dataPtr) 75 | // (i32.sub 76 | // (i32.load8_u 77 | // (local.get $dataPtr) 78 | // ) 79 | // (i32.const 1) 80 | // ) 81 | // ) 82 | w.store8( 83 | 'i32', 84 | 0, 85 | null, 86 | w.local.get('i32', 'dataPtr'), 87 | w.sub( 88 | 'i32', 89 | w.load8ZeroExt('i32', 0, null, w.local.get('i32', 'dataPtr')), 90 | w.constant('i32', 1), 91 | ), 92 | ), 93 | ], 94 | '.': [ 95 | // C equivalent: 96 | // *outputPtr = *dataPtr 97 | // outputPtr = outputPtr + 1 98 | // 99 | // compiles to: 100 | // (i32.store8 101 | // (local.get $outputPtr) 102 | // (i32.load8_u 103 | // (local.get $dataPtr) 104 | // ) 105 | // ) 106 | // (local.set $outputPtr 107 | // (i32.add 108 | // (local.get $outputPtr) 109 | // (i32.const 1) 110 | // ) 111 | // ) 112 | w.store8( 113 | 'i32', 114 | 0, 115 | null, 116 | w.local.get('i32', 'outputPtr'), 117 | w.load8ZeroExt('i32', 0, null, w.local.get('i32', 'dataPtr')), 118 | ), 119 | w.local.set( 120 | 'i32', 121 | 'outputPtr', 122 | w.add('i32', w.local.get('i32', 'outputPtr'), w.constant('i32', 1)), 123 | ), 124 | ], 125 | }; 126 | 127 | const compile = (input: string) => { 128 | // track which character in the input we're currently on 129 | let idx = 0; 130 | 131 | // convert input string to array of chars 132 | const chars = [...input]; 133 | 134 | // this function either parses our whole program, or a loop. 135 | // the difference is that parsing a loop should end on a `]`, 136 | // while parsing the whole program should end at the end of our input string 137 | const compileBlock = (isLoop: boolean): w.Instr<'none'>[] => { 138 | // we will assemble a list of compiled instructions for this block (loop or program) 139 | const instrs: w.Instr<'none'>[] = []; 140 | // loop through the whole program 141 | while (idx < input.length) { 142 | // get the current character 143 | const ch = chars[idx]; 144 | idx++; 145 | 146 | // if the character is a `[`, 147 | // we start a new loop inside of the current block 148 | if (ch === '[') { 149 | instrs.push( 150 | // to mimic a while loop, 151 | // we need to wrap the loop in a block 152 | // so that we can break out of the loop early 153 | w.block(null, 'none', [ 154 | w.loop(null, [ 155 | // if the value at the data pointer is zero, 156 | // break out of the loop by branching to the block 157 | w.branchIf( 158 | 1, 159 | w.equalZero( 160 | 'i32', 161 | w.load8ZeroExt('i32', 0, null, w.local.get('i32', 'dataPtr')), 162 | ), 163 | ), 164 | // recursively compile the loop instructions 165 | ...compileBlock(true), 166 | // go to the next iteration by branching to the loop 167 | w.branch(0), 168 | ]), 169 | ]), 170 | ); 171 | continue; 172 | } 173 | // end the block if `]` is found 174 | else if (ch === ']') { 175 | if (!isLoop) { 176 | throw new Error('Unexpected ]'); 177 | } 178 | return instrs; 179 | } 180 | // otherwise, get the nodes from the instruction map 181 | const instr = instrMap[ch as keyof typeof instrMap] ?? null; 182 | // if no matching character, ignore 183 | if (!instr) { 184 | continue; 185 | } 186 | instrs.push(...instr); 187 | } 188 | if (isLoop) { 189 | throw new Error('Unclosed loop'); 190 | } else { 191 | return instrs; 192 | } 193 | }; 194 | 195 | const instrs = [ 196 | // initialize the data pointer 197 | w.local.set('i32', 'dataPtr', w.constant('i32', DATA_START)), 198 | // and then add all the nodes 199 | ...compileBlock(false), 200 | ]; 201 | 202 | // create our main function 203 | const main = w.func( 204 | 'main', 205 | { 206 | params: [], 207 | returnType: 'none', 208 | locals: [ 209 | ['i32', 'dataPtr'], 210 | ['i32', 'outputPtr'], 211 | ], 212 | }, 213 | w.block(null, 'none', instrs as [...w.Instr<'none'>[], w.Instr<'none'>]), 214 | ); 215 | const m = new w.Module(); 216 | m.addMemory('0', 1, 1, []); 217 | // add the function to the module 218 | m.addFunc(main); 219 | m.setStart('main'); 220 | // compile to WAT string 221 | return m.compile(); 222 | }; 223 | 224 | const runProgram = async (program: string) => { 225 | const wabt = await (require('wabt') as typeof import('wabt'))(); 226 | const compiled = compile(program); 227 | // console.log(compiled); 228 | const m = wabt.parseWat('', compiled); 229 | const inst = await WebAssembly.instantiate(m.toBinary({}).buffer); 230 | const memory = inst.instance.exports['0'] as any; 231 | const dataView = new DataView(memory.buffer); 232 | return { dataView }; 233 | }; 234 | 235 | const getMemoryArray = (dataView: DataView) => { 236 | const arr: number[] = []; 237 | for (let i = 0; i < DATA_START; i += 1) { 238 | arr.push(dataView.getInt8(i)); 239 | } 240 | return arr; 241 | }; 242 | 243 | const getOutputString = (dataView: DataView) => { 244 | const memArr = getMemoryArray(dataView); 245 | const outputStr = memArr 246 | .slice(0, memArr.indexOf(0)) 247 | .reduce((acc, cur) => acc + String.fromCharCode(cur), ''); 248 | return outputStr; 249 | }; 250 | 251 | test('Basic loop', async () => { 252 | const { dataView } = await runProgram(` 253 | ++++++[-->+<]>. 254 | `); 255 | expect(dataView.getInt8(0)).toBe(3); 256 | }); 257 | 258 | test('Hello world', async () => { 259 | const { dataView } = await runProgram(` 260 | >++++++++[<+++++++++>-]<.>++++[<+++++++>-]<+.+++++++..+++.>>++++++[<+++++++>-]<+ 261 | +.------------.>++++++[<+++++++++>-]<+.<.+++.------.--------.>>>++++[<++++++++>- 262 | ]<+. 263 | `); 264 | const outputStr = getOutputString(dataView); 265 | expect(outputStr).toBe('Hello, World!'); 266 | }); 267 | -------------------------------------------------------------------------------- /src/test/compiler.test.ts: -------------------------------------------------------------------------------- 1 | import { compilers, compile } from '../compiler'; 2 | import * as w from '../methods'; 3 | import { Module } from '../module'; 4 | 5 | describe('compiler', () => { 6 | const addFunc = () => 7 | w.func( 8 | 'add', 9 | { 10 | params: [ 11 | ['i32', 'a'], 12 | ['i32', 'b'], 13 | ], 14 | locals: [], 15 | returnType: 'i32', 16 | }, 17 | w.add('i32', w.local.get('i32', 'a'), w.local.get('i32', 'b')), 18 | ); 19 | 20 | test('clz node', () => { 21 | expect( 22 | compilers.clz( 23 | w.clz('i32', w.constant('i32', 1), w.constant('i32', 2)), 24 | ), 25 | ).toMatchSnapshot(); 26 | }); 27 | 28 | test('ctz node', () => { 29 | expect( 30 | compilers.ctz( 31 | w.ctz('i32', w.constant('i32', 1), w.constant('i32', 2)), 32 | ), 33 | ).toMatchSnapshot(); 34 | }); 35 | 36 | test('popcnt node', () => { 37 | expect( 38 | compilers.popcnt( 39 | w.popcnt('i32', w.constant('i32', 1), w.constant('i32', 2)), 40 | ), 41 | ).toMatchSnapshot(); 42 | }); 43 | 44 | test('add function (local.get, i32.add, exported func)', () => { 45 | const m = new Module(); 46 | m.addFunc(addFunc(), true); 47 | 48 | expect(compile(m)).toMatchSnapshot(); 49 | }); 50 | 51 | test('localGet node', () => { 52 | expect(compilers.localGet(w.local.get('f64', 'abc'))).toMatchSnapshot(); 53 | }); 54 | 55 | test('localSet node', () => { 56 | expect( 57 | compilers.localSet(w.local.set('f64', 'abc', w.constant('f64', 10))), 58 | ).toMatchSnapshot(); 59 | }); 60 | 61 | test('const node', () => { 62 | expect(compilers.constant(w.constant('f32', 1.5))).toMatchSnapshot(); 63 | }); 64 | 65 | test('add node', () => { 66 | expect( 67 | compilers.add(w.add('i32', w.constant('i32', 10), w.constant('i32', 5))), 68 | ).toMatchSnapshot(); 69 | }); 70 | 71 | test('sub node', () => { 72 | expect( 73 | compilers.sub(w.sub('i32', w.constant('i32', 10), w.constant('i32', 5))), 74 | ).toMatchSnapshot(); 75 | }); 76 | 77 | test('mul node', () => { 78 | expect( 79 | compilers.mul(w.mul('i32', w.constant('i32', 10), w.constant('i32', 5))), 80 | ).toMatchSnapshot(); 81 | }); 82 | 83 | test('div_s node', () => { 84 | expect( 85 | compilers.divSigned( 86 | w.divSigned('i32', w.constant('i32', 10), w.constant('i32', 5)), 87 | ), 88 | ).toMatchSnapshot(); 89 | }); 90 | 91 | test('div_u node', () => { 92 | expect( 93 | compilers.divUnsigned( 94 | w.divUnsigned('i32', w.constant('i32', 10), w.constant('i32', 5)), 95 | ), 96 | ).toMatchSnapshot(); 97 | }); 98 | 99 | test('rem_s node', () => { 100 | expect( 101 | compilers.remSigned( 102 | w.remSigned('i32', w.constant('i32', 10), w.constant('i32', 5)), 103 | ), 104 | ).toMatchSnapshot(); 105 | }); 106 | 107 | test('rem_u node', () => { 108 | expect( 109 | compilers.remUnsigned( 110 | w.remUnsigned('i32', w.constant('i32', 10), w.constant('i32', 5)), 111 | ), 112 | ).toMatchSnapshot(); 113 | }); 114 | 115 | test('and node', () => { 116 | expect( 117 | compilers.and( 118 | w.and('i32', w.constant('i32', 0xf0f0f0), w.constant('i32', 0x0f0f0f)), 119 | ), 120 | ).toMatchSnapshot(); 121 | }); 122 | 123 | test('or node', () => { 124 | expect( 125 | compilers.or( 126 | w.or('i32', w.constant('i32', 0xf0f0f0), w.constant('i32', 0x0f0f0f)), 127 | ), 128 | ).toMatchSnapshot(); 129 | }); 130 | 131 | test('xor node', () => { 132 | expect( 133 | compilers.xor( 134 | w.xor('i32', w.constant('i32', 0xf0f0f0), w.constant('i32', 0x0f0f0f)), 135 | ), 136 | ).toMatchSnapshot(); 137 | }); 138 | 139 | test('shl node', () => { 140 | expect( 141 | compilers.shl( 142 | w.shl('i32', w.constant('i32', 10), w.constant('i32', 5)), 143 | ), 144 | ).toMatchSnapshot(); 145 | }); 146 | 147 | test('shr_s node', () => { 148 | expect( 149 | compilers.shrSigned( 150 | w.shrSigned('i32', w.constant('i32', 20), w.constant('i32', 2)), 151 | ), 152 | ).toMatchSnapshot(); 153 | }); 154 | 155 | test('shr_u node', () => { 156 | expect( 157 | compilers.shrUnsigned( 158 | w.shrUnsigned('i32', w.constant('i32', 20), w.constant('i32', 2)), 159 | ), 160 | ).toMatchSnapshot(); 161 | }); 162 | 163 | test('rotr node', () => { 164 | expect( 165 | compilers.rotr( 166 | w.rotr('i32', w.constant('i32', 20), w.constant('i32', 2)), 167 | ), 168 | ).toMatchSnapshot(); 169 | }); 170 | 171 | test('rotl node', () => { 172 | expect( 173 | compilers.rotl( 174 | w.rotl('i32', w.constant('i32', 20), w.constant('i32', 2)), 175 | ), 176 | ).toMatchSnapshot(); 177 | }); 178 | 179 | test('call node', () => { 180 | expect( 181 | compilers.call(w.call('abc', 'i32', [w.constant('i32', 10)])), 182 | ).toMatchSnapshot(); 183 | }); 184 | 185 | test('call_indirect node', () => { 186 | expect( 187 | compilers.callIndirect( 188 | w.callIndirect( 189 | '0', 190 | w.constant('i32', 0), 191 | { params: addFunc().params, returnType: addFunc().dataType }, 192 | [w.constant('i32', 3), w.constant('i32', 4)], 193 | ), 194 | ), 195 | ).toMatchSnapshot(); 196 | }); 197 | }); 198 | -------------------------------------------------------------------------------- /src/test/tree.test.ts: -------------------------------------------------------------------------------- 1 | import * as w from '../methods'; 2 | import { Module } from '../module'; 3 | 4 | describe('tree creation', () => { 5 | test('add constant', () => { 6 | const x = w.add('i32', w.constant('i32', 1), w.constant('i32', 2)); 7 | 8 | expect(x).toMatchSnapshot(); 9 | }); 10 | 11 | test('function', () => { 12 | const f = w.func( 13 | 'add', 14 | { 15 | params: [ 16 | ['i32', 'a'], 17 | ['i32', 'b'], 18 | ], 19 | locals: [['i32', 'ret']], 20 | returnType: 'i32', 21 | }, 22 | w.block(null, 'i32', [ 23 | w.drop(w.add('i32', w.local.get('i32', 'a'), w.local.get('i32', 'b'))), 24 | w.local.set( 25 | 'i32', 26 | 'ret', 27 | w.add('i32', w.local.get('i32', 'a'), w.local.get('i32', 'b')), 28 | ), 29 | w.local.get('i32', 'ret'), 30 | ]), 31 | ); 32 | 33 | expect(f).toMatchSnapshot(); 34 | }); 35 | 36 | test('function call', () => { 37 | const x = w.call('add', 'i32', [w.local.get('i32', 'a')]); 38 | 39 | expect(x).toMatchSnapshot(); 40 | }); 41 | 42 | const addFunc = () => 43 | w.func( 44 | 'add', 45 | { 46 | params: [ 47 | ['i32', 'a'], 48 | ['i32', 'b'], 49 | ], 50 | locals: [], 51 | returnType: 'i32', 52 | }, 53 | w.add('i32', w.local.get('i32', 'a'), w.local.get('i32', 'b')), 54 | ); 55 | 56 | test('addFunc', () => { 57 | const m = new Module(); 58 | m.addFunc(addFunc()); 59 | m.addFunc(addFunc(), true); 60 | m.addFunc(addFunc(), 'addi32'); 61 | expect(m.funcs).toMatchSnapshot(); 62 | }); 63 | }); 64 | // block(null, [5, i64.local.get('ret')], 'i32'); 65 | 66 | // i32.local.set('ret', i32.add(i32.local.get('a'), i32.local.get('b'))), 67 | // i32.add(i64.local.get('a'), i32.local.get('b')); 68 | 69 | // type Test = [...number[], string]; 70 | // const a: Test = [5, '', 4]; 71 | -------------------------------------------------------------------------------- /src/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "moduleResolution": "Node", 5 | "target": "ES2020", 6 | "jsx": "react", 7 | "strictNullChecks": true, 8 | "strictFunctionTypes": true, 9 | "allowSyntheticDefaultImports": true 10 | }, 11 | "exclude": ["node_modules", "**/node_modules/*"] 12 | } 13 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export type NoInfer = [T][T extends any ? 0 : never]; 2 | 3 | export type IntegerLiteral = `${T}` extends `${ 4 | | '-' 5 | | ''}${string}.${string}` 6 | ? never 7 | : T; 8 | 9 | export type Replace< 10 | T extends Record, 11 | S extends Record, 12 | > = Omit & S; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "diagnostics": true, 4 | "esModuleInterop": true, 5 | "extendedDiagnostics": true, 6 | "skipLibCheck": false, 7 | "jsx": "preserve", 8 | "lib": ["es2020", "ESNext"], 9 | "moduleResolution": "node", 10 | "strict": true, 11 | "strictNullChecks": true, 12 | "strictFunctionTypes": true, 13 | "strictPropertyInitialization": true, 14 | "outDir": "./build", 15 | "module": "ESNext", 16 | "target": "ESNext" 17 | }, 18 | "include": ["src/index.ts", "src/**/*.ts"], 19 | "exclude": ["src/test/**/*.*"] 20 | } 21 | --------------------------------------------------------------------------------