├── .gitignore ├── LICENSE ├── README.md ├── compile.md ├── package-lock.json ├── package.json ├── runtime.wat ├── src ├── AST.ts ├── TaggedRecord.ts ├── absurd.ts ├── compile.ts ├── compiler.ts ├── execute.ts ├── parser.ts ├── prettyError.ts ├── refineSyntaxTree.ts └── typeCheck.ts ├── syntax.ebnf ├── syntax.peg └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jaewon Seo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lambda2wasm 2 | A Lambda expression compiler targeting web assembly. 3 | 4 | ## Lambda Expression Syntax 5 | 6 | > you can find formal definition of syntax in 'syntax.peg' file. 7 | 8 | ``` 9 | binding = 1#I32. 10 | 11 | int32 = 0#I32. 12 | int64 = 0#I64. 13 | float32 = 0#F32. 14 | float64 = 0#F64. 15 | 16 | inc = x:I32. x + 1#I32. 17 | dec = x:I32. x - 1#I32. 18 | square = x:I32. x * x. 19 | 20 | id = x:a. x. 21 | apply = f:a->a. x:a. f x. 22 | 23 | main = apply id (inc 6#I32). 24 | ``` 25 | 26 | ## Commands 27 | 28 | ### `compile` 29 | 30 | ``` 31 | npm run compile [path] 32 | ``` 33 | 34 | prints wat(webassembly text format) output of a given source. 35 | 36 | ### `execute` 37 | 38 | ``` 39 | npm run execute [path] 40 | ``` 41 | 42 | compiles and execute a given code. (`main` is the entry point, value of `main` will be printed) 43 | 44 | ## Known bugs 45 | 46 | - monomorphic function passed to universally quantified parameter makes function signature mismatch error. -------------------------------------------------------------------------------- /compile.md: -------------------------------------------------------------------------------- 1 | bindings -> global function 2 | 3 | constant as 0-arity func (unary) 4 | multiparam? 5 | 6 | autocurrying support / polyfunc support 7 | other type support 8 | 9 | indirect function call 10 | 11 | 12 | func creation: 13 | 14 | global func def(with inner index) -> upload in table 15 | 16 | use table idx instead of variable (and... typesearch?) 17 | 18 | // 19 | 20 | global vs local comparsion. 21 | 22 | 23 | 24 | lexical environment 25 | 26 | \x. (\y. x) 27 | 28 | \x. i32 29 | \x y. x. 30 | 31 | but.. 32 | 33 | twice = \a. (\f. \x. f (f x))(\x. a). 34 | 35 | // problems i've ever met 36 | 37 | - parser comb (hanwritten) vs peg(generated) 38 | - ast def(mvar, pvar) - how precise 39 | - ast def - i need macros(cons-adt) (editor level macro) 40 | - tip: Naming things(Prefix for keywords) 41 | - typecheck: WeakMap use case 42 | - type equation 43 | - variable-with-init-exp 44 | - currying - HOF - closure 45 | - polyfuncs 46 | //// v2 47 | 48 | Numeric/Arithmetic -> just as usual 49 | 50 | Abs / App / Variable / Binding / Global 51 | 52 | 0. naming convention. 53 | 54 | (original name)_(lambda depth)_(parameter_type) 55 | 56 | '->' translated to '2' 57 | 58 | 1. constant bindings 59 | 60 | with init exp -> 0-arity func wrap * 61 | 62 | constant -> just constant (opt) 63 | 64 | 2. functions 65 | 66 | issues : currying / HoF / PolyF 67 | 68 | => indirect call / environments 69 | 70 | func val -> funcref, saturated in call site. 71 | 72 | ``` 73 | \x. x 74 | 75 | funcref -> \x. x 76 | 77 | \y. \x. x 78 | 79 | \y. (funcref -> \x. x) 80 | 81 | \f. f (f 1) 82 | 83 | context.create 84 | ~ 85 | context.allocVar 86 | indirect_call sig ...args env funcref 87 | 88 | sig infer -> just like type check 89 | 90 | // 91 | 92 | func def : arg context#i32(pointer) ret 93 | 94 | free var -> use env 95 | bound -> use arg (local.get) 96 | 97 | context.get envref string <-- first, depend on js api. next, use standalone api. 98 | ---- 99 | context.alloc[type] envref string val[type] ~ envref 100 | context.start ~ envref. 101 | ``` 102 | 103 | 104 | context as linked list 105 | 106 | vec128 107 | 108 | [64bit] for data -> i32 (index) -> i32(next) 109 | 110 | funcPointer : i32(f table pointer) / i32(context pointer) 111 | 112 | context : vec128 (val i64 / before(outer) i32 / reserved i32) 113 | 114 | ;; polyfunc compile 115 | 116 | 117 | if, def, then 118 | 119 | abst -> type split 120 | typevar as constant per stage, n * m 121 | 122 | else, exp, withapp -> getFullySaturatedType ~ free type var? error! 123 | 124 | a -> a 125 | 126 | diff a initiate? 127 | 128 | a unboxed to... 129 | 130 | parametric poly only... 131 | boxed? <-- memory loss? 132 | 133 | boxedval -> boxedval... (like funcref) 134 | poly call -> boxed 135 | poly out -> unboxed 136 | 137 | poly func impl : poly var as boxed 138 | a -> a 139 | 140 | | functype | argtype | rettype | 141 | |----------|---------|---------| 142 | | mono | mono | mono | 143 | | poly | mono | mono | 144 | | poly | mono | poly | 145 | | poly | poly | poly | 146 | | poly | poly | mono | 147 | 148 | polytypevar stays polytype in it's scope 149 | monotypevar boxed polytype when enter polyfunc, unboxed when exit. 150 | 151 | 152 | M -> a (i64 conv) 153 | a -> M (i64 conv) 154 | a -> a (both conv) 155 | 156 | convkit with specialization 157 | 158 | ((a -> a) -> a) -> a -> a -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda2wasm", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "lambda2wasm", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "dedent": "^0.7.0", 13 | "wabt": "^1.0.36" 14 | }, 15 | "devDependencies": { 16 | "@types/dedent": "^0.7.0", 17 | "@types/node": "^18.0.3", 18 | "ts-node": "^10.8.2", 19 | "tspeg": "^3.2.1", 20 | "typescript": "^4.6.4" 21 | } 22 | }, 23 | "node_modules/@cspotcode/source-map-support": { 24 | "version": "0.8.1", 25 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 26 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 27 | "dev": true, 28 | "dependencies": { 29 | "@jridgewell/trace-mapping": "0.3.9" 30 | }, 31 | "engines": { 32 | "node": ">=12" 33 | } 34 | }, 35 | "node_modules/@jridgewell/resolve-uri": { 36 | "version": "3.1.0", 37 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 38 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 39 | "dev": true, 40 | "engines": { 41 | "node": ">=6.0.0" 42 | } 43 | }, 44 | "node_modules/@jridgewell/sourcemap-codec": { 45 | "version": "1.4.14", 46 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 47 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", 48 | "dev": true 49 | }, 50 | "node_modules/@jridgewell/trace-mapping": { 51 | "version": "0.3.9", 52 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 53 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 54 | "dev": true, 55 | "dependencies": { 56 | "@jridgewell/resolve-uri": "^3.0.3", 57 | "@jridgewell/sourcemap-codec": "^1.4.10" 58 | } 59 | }, 60 | "node_modules/@tsconfig/node10": { 61 | "version": "1.0.9", 62 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 63 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 64 | "dev": true 65 | }, 66 | "node_modules/@tsconfig/node12": { 67 | "version": "1.0.11", 68 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 69 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 70 | "dev": true 71 | }, 72 | "node_modules/@tsconfig/node14": { 73 | "version": "1.0.3", 74 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 75 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 76 | "dev": true 77 | }, 78 | "node_modules/@tsconfig/node16": { 79 | "version": "1.0.3", 80 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 81 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 82 | "dev": true 83 | }, 84 | "node_modules/@types/dedent": { 85 | "version": "0.7.0", 86 | "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.0.tgz", 87 | "integrity": "sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==", 88 | "dev": true 89 | }, 90 | "node_modules/@types/node": { 91 | "version": "18.0.3", 92 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", 93 | "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==", 94 | "dev": true 95 | }, 96 | "node_modules/acorn": { 97 | "version": "8.7.1", 98 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", 99 | "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", 100 | "dev": true, 101 | "bin": { 102 | "acorn": "bin/acorn" 103 | }, 104 | "engines": { 105 | "node": ">=0.4.0" 106 | } 107 | }, 108 | "node_modules/acorn-walk": { 109 | "version": "8.2.0", 110 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 111 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 112 | "dev": true, 113 | "engines": { 114 | "node": ">=0.4.0" 115 | } 116 | }, 117 | "node_modules/ansi-regex": { 118 | "version": "5.0.1", 119 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 120 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 121 | "dev": true, 122 | "engines": { 123 | "node": ">=8" 124 | } 125 | }, 126 | "node_modules/arg": { 127 | "version": "4.1.3", 128 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 129 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 130 | "dev": true 131 | }, 132 | "node_modules/cliui": { 133 | "version": "7.0.4", 134 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 135 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 136 | "dev": true, 137 | "dependencies": { 138 | "string-width": "^4.2.0", 139 | "strip-ansi": "^6.0.0", 140 | "wrap-ansi": "^7.0.0" 141 | } 142 | }, 143 | "node_modules/create-require": { 144 | "version": "1.1.1", 145 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 146 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 147 | "dev": true 148 | }, 149 | "node_modules/dedent": { 150 | "version": "0.7.0", 151 | "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", 152 | "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" 153 | }, 154 | "node_modules/diff": { 155 | "version": "4.0.2", 156 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 157 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 158 | "dev": true, 159 | "engines": { 160 | "node": ">=0.3.1" 161 | } 162 | }, 163 | "node_modules/emoji-regex": { 164 | "version": "8.0.0", 165 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 166 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 167 | "dev": true 168 | }, 169 | "node_modules/escalade": { 170 | "version": "3.1.1", 171 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 172 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 173 | "dev": true, 174 | "engines": { 175 | "node": ">=6" 176 | } 177 | }, 178 | "node_modules/get-caller-file": { 179 | "version": "2.0.5", 180 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 181 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 182 | "dev": true, 183 | "engines": { 184 | "node": "6.* || 8.* || >= 10.*" 185 | } 186 | }, 187 | "node_modules/is-fullwidth-code-point": { 188 | "version": "3.0.0", 189 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 190 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 191 | "dev": true, 192 | "engines": { 193 | "node": ">=8" 194 | } 195 | }, 196 | "node_modules/make-error": { 197 | "version": "1.3.6", 198 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 199 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 200 | "dev": true 201 | }, 202 | "node_modules/require-directory": { 203 | "version": "2.1.1", 204 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 205 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 206 | "dev": true, 207 | "engines": { 208 | "node": ">=0.10.0" 209 | } 210 | }, 211 | "node_modules/string-width": { 212 | "version": "4.2.3", 213 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 214 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 215 | "dev": true, 216 | "dependencies": { 217 | "emoji-regex": "^8.0.0", 218 | "is-fullwidth-code-point": "^3.0.0", 219 | "strip-ansi": "^6.0.1" 220 | }, 221 | "engines": { 222 | "node": ">=8" 223 | } 224 | }, 225 | "node_modules/strip-ansi": { 226 | "version": "6.0.1", 227 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 228 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 229 | "dev": true, 230 | "dependencies": { 231 | "ansi-regex": "^5.0.1" 232 | }, 233 | "engines": { 234 | "node": ">=8" 235 | } 236 | }, 237 | "node_modules/ts-node": { 238 | "version": "10.8.2", 239 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.2.tgz", 240 | "integrity": "sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==", 241 | "dev": true, 242 | "dependencies": { 243 | "@cspotcode/source-map-support": "^0.8.0", 244 | "@tsconfig/node10": "^1.0.7", 245 | "@tsconfig/node12": "^1.0.7", 246 | "@tsconfig/node14": "^1.0.0", 247 | "@tsconfig/node16": "^1.0.2", 248 | "acorn": "^8.4.1", 249 | "acorn-walk": "^8.1.1", 250 | "arg": "^4.1.0", 251 | "create-require": "^1.1.0", 252 | "diff": "^4.0.1", 253 | "make-error": "^1.1.1", 254 | "v8-compile-cache-lib": "^3.0.1", 255 | "yn": "3.1.1" 256 | }, 257 | "bin": { 258 | "ts-node": "dist/bin.js", 259 | "ts-node-cwd": "dist/bin-cwd.js", 260 | "ts-node-esm": "dist/bin-esm.js", 261 | "ts-node-script": "dist/bin-script.js", 262 | "ts-node-transpile-only": "dist/bin-transpile.js", 263 | "ts-script": "dist/bin-script-deprecated.js" 264 | }, 265 | "peerDependencies": { 266 | "@swc/core": ">=1.2.50", 267 | "@swc/wasm": ">=1.2.50", 268 | "@types/node": "*", 269 | "typescript": ">=2.7" 270 | }, 271 | "peerDependenciesMeta": { 272 | "@swc/core": { 273 | "optional": true 274 | }, 275 | "@swc/wasm": { 276 | "optional": true 277 | } 278 | } 279 | }, 280 | "node_modules/tspeg": { 281 | "version": "3.2.1", 282 | "resolved": "https://registry.npmjs.org/tspeg/-/tspeg-3.2.1.tgz", 283 | "integrity": "sha512-AsiI1MhH4ISQkvpISQK2i8KTEQ8n85a61V5oW/JaPjxMzf/woQS3kgPf2GgYdG/K7fAEKF8aGA25K+kbagI+lg==", 284 | "dev": true, 285 | "dependencies": { 286 | "yargs": "^17.1.1" 287 | }, 288 | "bin": { 289 | "tspeg": "tsbuild/cli.js" 290 | } 291 | }, 292 | "node_modules/typescript": { 293 | "version": "4.9.5", 294 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 295 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 296 | "dev": true, 297 | "bin": { 298 | "tsc": "bin/tsc", 299 | "tsserver": "bin/tsserver" 300 | }, 301 | "engines": { 302 | "node": ">=4.2.0" 303 | } 304 | }, 305 | "node_modules/v8-compile-cache-lib": { 306 | "version": "3.0.1", 307 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 308 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 309 | "dev": true 310 | }, 311 | "node_modules/wabt": { 312 | "version": "1.0.36", 313 | "resolved": "https://registry.npmjs.org/wabt/-/wabt-1.0.36.tgz", 314 | "integrity": "sha512-GAfEcFyvYRZ51xIZKeeCmIKytTz3ejCeEU9uevGNhEnqt9qXp3a8Q2O4ByZr6rKWcd8jV/Oj5cbDJFtmTYdchg==", 315 | "bin": { 316 | "wasm-decompile": "bin/wasm-decompile", 317 | "wasm-interp": "bin/wasm-interp", 318 | "wasm-objdump": "bin/wasm-objdump", 319 | "wasm-stats": "bin/wasm-stats", 320 | "wasm-strip": "bin/wasm-strip", 321 | "wasm-validate": "bin/wasm-validate", 322 | "wasm2c": "bin/wasm2c", 323 | "wasm2wat": "bin/wasm2wat", 324 | "wat2wasm": "bin/wat2wasm" 325 | } 326 | }, 327 | "node_modules/wrap-ansi": { 328 | "version": "7.0.0", 329 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 330 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 331 | "dev": true, 332 | "dependencies": { 333 | "ansi-styles": "^4.0.0", 334 | "string-width": "^4.1.0", 335 | "strip-ansi": "^6.0.0" 336 | }, 337 | "engines": { 338 | "node": ">=10" 339 | }, 340 | "funding": { 341 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 342 | } 343 | }, 344 | "node_modules/wrap-ansi/node_modules/ansi-styles": { 345 | "version": "4.3.0", 346 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 347 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 348 | "dev": true, 349 | "dependencies": { 350 | "color-convert": "^2.0.1" 351 | }, 352 | "engines": { 353 | "node": ">=8" 354 | }, 355 | "funding": { 356 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 357 | } 358 | }, 359 | "node_modules/wrap-ansi/node_modules/color-convert": { 360 | "version": "2.0.1", 361 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 362 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 363 | "dev": true, 364 | "dependencies": { 365 | "color-name": "~1.1.4" 366 | }, 367 | "engines": { 368 | "node": ">=7.0.0" 369 | } 370 | }, 371 | "node_modules/wrap-ansi/node_modules/color-name": { 372 | "version": "1.1.4", 373 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 374 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 375 | "dev": true 376 | }, 377 | "node_modules/y18n": { 378 | "version": "5.0.8", 379 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 380 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 381 | "dev": true, 382 | "engines": { 383 | "node": ">=10" 384 | } 385 | }, 386 | "node_modules/yargs": { 387 | "version": "17.5.1", 388 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", 389 | "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", 390 | "dev": true, 391 | "dependencies": { 392 | "cliui": "^7.0.2", 393 | "escalade": "^3.1.1", 394 | "get-caller-file": "^2.0.5", 395 | "require-directory": "^2.1.1", 396 | "string-width": "^4.2.3", 397 | "y18n": "^5.0.5", 398 | "yargs-parser": "^21.0.0" 399 | }, 400 | "engines": { 401 | "node": ">=12" 402 | } 403 | }, 404 | "node_modules/yargs-parser": { 405 | "version": "21.0.1", 406 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", 407 | "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", 408 | "dev": true, 409 | "engines": { 410 | "node": ">=12" 411 | } 412 | }, 413 | "node_modules/yn": { 414 | "version": "3.1.1", 415 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 416 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 417 | "dev": true, 418 | "engines": { 419 | "node": ">=6" 420 | } 421 | } 422 | }, 423 | "dependencies": { 424 | "@cspotcode/source-map-support": { 425 | "version": "0.8.1", 426 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 427 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 428 | "dev": true, 429 | "requires": { 430 | "@jridgewell/trace-mapping": "0.3.9" 431 | } 432 | }, 433 | "@jridgewell/resolve-uri": { 434 | "version": "3.1.0", 435 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 436 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 437 | "dev": true 438 | }, 439 | "@jridgewell/sourcemap-codec": { 440 | "version": "1.4.14", 441 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 442 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", 443 | "dev": true 444 | }, 445 | "@jridgewell/trace-mapping": { 446 | "version": "0.3.9", 447 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 448 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 449 | "dev": true, 450 | "requires": { 451 | "@jridgewell/resolve-uri": "^3.0.3", 452 | "@jridgewell/sourcemap-codec": "^1.4.10" 453 | } 454 | }, 455 | "@tsconfig/node10": { 456 | "version": "1.0.9", 457 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 458 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 459 | "dev": true 460 | }, 461 | "@tsconfig/node12": { 462 | "version": "1.0.11", 463 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 464 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 465 | "dev": true 466 | }, 467 | "@tsconfig/node14": { 468 | "version": "1.0.3", 469 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 470 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 471 | "dev": true 472 | }, 473 | "@tsconfig/node16": { 474 | "version": "1.0.3", 475 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 476 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 477 | "dev": true 478 | }, 479 | "@types/dedent": { 480 | "version": "0.7.0", 481 | "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.0.tgz", 482 | "integrity": "sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==", 483 | "dev": true 484 | }, 485 | "@types/node": { 486 | "version": "18.0.3", 487 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", 488 | "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==", 489 | "dev": true 490 | }, 491 | "acorn": { 492 | "version": "8.7.1", 493 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", 494 | "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", 495 | "dev": true 496 | }, 497 | "acorn-walk": { 498 | "version": "8.2.0", 499 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 500 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 501 | "dev": true 502 | }, 503 | "ansi-regex": { 504 | "version": "5.0.1", 505 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 506 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 507 | "dev": true 508 | }, 509 | "arg": { 510 | "version": "4.1.3", 511 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 512 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 513 | "dev": true 514 | }, 515 | "cliui": { 516 | "version": "7.0.4", 517 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 518 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 519 | "dev": true, 520 | "requires": { 521 | "string-width": "^4.2.0", 522 | "strip-ansi": "^6.0.0", 523 | "wrap-ansi": "^7.0.0" 524 | } 525 | }, 526 | "create-require": { 527 | "version": "1.1.1", 528 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 529 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 530 | "dev": true 531 | }, 532 | "dedent": { 533 | "version": "0.7.0", 534 | "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", 535 | "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" 536 | }, 537 | "diff": { 538 | "version": "4.0.2", 539 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 540 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 541 | "dev": true 542 | }, 543 | "emoji-regex": { 544 | "version": "8.0.0", 545 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 546 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 547 | "dev": true 548 | }, 549 | "escalade": { 550 | "version": "3.1.1", 551 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 552 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 553 | "dev": true 554 | }, 555 | "get-caller-file": { 556 | "version": "2.0.5", 557 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 558 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 559 | "dev": true 560 | }, 561 | "is-fullwidth-code-point": { 562 | "version": "3.0.0", 563 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 564 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 565 | "dev": true 566 | }, 567 | "make-error": { 568 | "version": "1.3.6", 569 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 570 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 571 | "dev": true 572 | }, 573 | "require-directory": { 574 | "version": "2.1.1", 575 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 576 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 577 | "dev": true 578 | }, 579 | "string-width": { 580 | "version": "4.2.3", 581 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 582 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 583 | "dev": true, 584 | "requires": { 585 | "emoji-regex": "^8.0.0", 586 | "is-fullwidth-code-point": "^3.0.0", 587 | "strip-ansi": "^6.0.1" 588 | } 589 | }, 590 | "strip-ansi": { 591 | "version": "6.0.1", 592 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 593 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 594 | "dev": true, 595 | "requires": { 596 | "ansi-regex": "^5.0.1" 597 | } 598 | }, 599 | "ts-node": { 600 | "version": "10.8.2", 601 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.2.tgz", 602 | "integrity": "sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==", 603 | "dev": true, 604 | "requires": { 605 | "@cspotcode/source-map-support": "^0.8.0", 606 | "@tsconfig/node10": "^1.0.7", 607 | "@tsconfig/node12": "^1.0.7", 608 | "@tsconfig/node14": "^1.0.0", 609 | "@tsconfig/node16": "^1.0.2", 610 | "acorn": "^8.4.1", 611 | "acorn-walk": "^8.1.1", 612 | "arg": "^4.1.0", 613 | "create-require": "^1.1.0", 614 | "diff": "^4.0.1", 615 | "make-error": "^1.1.1", 616 | "v8-compile-cache-lib": "^3.0.1", 617 | "yn": "3.1.1" 618 | } 619 | }, 620 | "tspeg": { 621 | "version": "3.2.1", 622 | "resolved": "https://registry.npmjs.org/tspeg/-/tspeg-3.2.1.tgz", 623 | "integrity": "sha512-AsiI1MhH4ISQkvpISQK2i8KTEQ8n85a61V5oW/JaPjxMzf/woQS3kgPf2GgYdG/K7fAEKF8aGA25K+kbagI+lg==", 624 | "dev": true, 625 | "requires": { 626 | "yargs": "^17.1.1" 627 | } 628 | }, 629 | "typescript": { 630 | "version": "4.9.5", 631 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 632 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 633 | "dev": true 634 | }, 635 | "v8-compile-cache-lib": { 636 | "version": "3.0.1", 637 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 638 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 639 | "dev": true 640 | }, 641 | "wabt": { 642 | "version": "1.0.36", 643 | "resolved": "https://registry.npmjs.org/wabt/-/wabt-1.0.36.tgz", 644 | "integrity": "sha512-GAfEcFyvYRZ51xIZKeeCmIKytTz3ejCeEU9uevGNhEnqt9qXp3a8Q2O4ByZr6rKWcd8jV/Oj5cbDJFtmTYdchg==" 645 | }, 646 | "wrap-ansi": { 647 | "version": "7.0.0", 648 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 649 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 650 | "dev": true, 651 | "requires": { 652 | "ansi-styles": "^4.0.0", 653 | "string-width": "^4.1.0", 654 | "strip-ansi": "^6.0.0" 655 | }, 656 | "dependencies": { 657 | "ansi-styles": { 658 | "version": "4.3.0", 659 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 660 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 661 | "dev": true, 662 | "requires": { 663 | "color-convert": "^2.0.1" 664 | } 665 | }, 666 | "color-convert": { 667 | "version": "2.0.1", 668 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 669 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 670 | "dev": true, 671 | "requires": { 672 | "color-name": "~1.1.4" 673 | } 674 | }, 675 | "color-name": { 676 | "version": "1.1.4", 677 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 678 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 679 | "dev": true 680 | } 681 | } 682 | }, 683 | "y18n": { 684 | "version": "5.0.8", 685 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 686 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 687 | "dev": true 688 | }, 689 | "yargs": { 690 | "version": "17.5.1", 691 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", 692 | "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", 693 | "dev": true, 694 | "requires": { 695 | "cliui": "^7.0.2", 696 | "escalade": "^3.1.1", 697 | "get-caller-file": "^2.0.5", 698 | "require-directory": "^2.1.1", 699 | "string-width": "^4.2.3", 700 | "y18n": "^5.0.5", 701 | "yargs-parser": "^21.0.0" 702 | } 703 | }, 704 | "yargs-parser": { 705 | "version": "21.0.1", 706 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", 707 | "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", 708 | "dev": true 709 | }, 710 | "yn": { 711 | "version": "3.1.1", 712 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 713 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 714 | "dev": true 715 | } 716 | } 717 | } 718 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda2wasm", 3 | "version": "1.0.0", 4 | "author": "ENvironmentSet", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/ENvironmentSet/lambda2wasm.git" 8 | }, 9 | "dependencies": { 10 | "dedent": "^0.7.0", 11 | "wabt": "^1.0.36" 12 | }, 13 | "devDependencies": { 14 | "@types/dedent": "^0.7.0", 15 | "@types/node": "^18.0.3", 16 | "ts-node": "^10.8.2", 17 | "tspeg": "^3.2.1", 18 | "typescript": "^4.6.4" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/ENvironmentSet/lambda2wasm/issues" 22 | }, 23 | "description": "A Lambda expression compiler targeting web assembly", 24 | "homepage": "https://github.com/ENvironmentSet/lambda2wasm#readme", 25 | "license": "MIT", 26 | "scripts": { 27 | "generate-parser": "tspeg syntax.peg ./src/parser.ts", 28 | "compile": "ts-node ./src/compile.ts", 29 | "execute": "ts-node ./src/execute.ts" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /runtime.wat: -------------------------------------------------------------------------------- 1 | 2 | (memory $context_heap 1 100) 3 | (global $context_index_offset (mut i32) (i32.const 0)) 4 | 5 | (func $cal_context_addr (param $context_index i32) (result i32) 6 | (i32.mul (local.get $context_index) (i32.const 16)) 7 | ) 8 | 9 | (func $get_next_context_addr (result i32) 10 | (call $cal_context_addr (global.get $context_index_offset)) 11 | (global.set $context_index_offset (i32.add (global.get $context_index_offset) (i32.const 1))) 12 | ) 13 | 14 | (func $get_context (param $context_index i32) (result v128) 15 | (call $cal_context_addr (local.get $context_index)) 16 | (v128.load) 17 | ) 18 | 19 | (func $is_top_context (param $context v128) (result i32) 20 | (i32x4.extract_lane 3 (local.get $context)) 21 | ) 22 | 23 | (func $get_outer_context_index (param $context v128) (result i32) 24 | (i32x4.extract_lane 2 (local.get $context)) 25 | ) 26 | 27 | (func $create_context_i32 (param $value i32) (result i32) (local $new_index i32) 28 | (local.set $new_index (call $get_next_context_addr)) 29 | (local.get $new_index) 30 | (i32x4.replace_lane 0 (v128.const i32x4 0 0 0 1) (local.get $value)) 31 | (v128.store) 32 | (i32.div_u (local.get $new_index) (i32.const 16)) 33 | ) 34 | 35 | (func $create_context_i64 (param $value i64) (result i32) (local $new_index i32) 36 | (local.set $new_index (call $get_next_context_addr)) 37 | (local.get $new_index) 38 | (i64x2.replace_lane 0 (v128.const i32x4 0 0 0 1) (local.get $value)) 39 | (v128.store) 40 | (i32.div_u (local.get $new_index) (i32.const 16)) 41 | ) 42 | 43 | (func $create_context_f32 (param $value f32) (result i32) (local $new_index i32) 44 | (local.set $new_index (call $get_next_context_addr)) 45 | (local.get $new_index) 46 | (f32x4.replace_lane 0 (v128.const i32x4 0 0 0 1) (local.get $value)) 47 | (v128.store) 48 | (i32.div_u (local.get $new_index) (i32.const 16)) 49 | ) 50 | 51 | (func $create_context_f64 (param $value f64) (result i32) (local $new_index i32) 52 | (local.set $new_index (call $get_next_context_addr)) 53 | (local.get $new_index) 54 | (f64x2.replace_lane 0 (v128.const i32x4 0 0 0 1) (local.get $value)) 55 | (v128.store) 56 | (i32.div_u (local.get $new_index) (i32.const 16)) 57 | ) 58 | 59 | (func $alloc_context_var_i32 (param $value i32) (param $outer_index i32) (result i32) (local $new_context_index i32) 60 | (local.set $new_context_index (call $create_context_i32 (local.get $value))) 61 | (call $cal_context_addr (local.get $new_context_index)) 62 | (i32x4.replace_lane 2 (call $get_context (local.get $new_context_index)) (local.get $outer_index)) 63 | (v128.store) 64 | (local.get $new_context_index) 65 | ) 66 | 67 | (func $alloc_context_var_i64 (param $value i64) (param $outer_index i32) (result i32) (local $new_context_index i32) 68 | (local.set $new_context_index (call $create_context_i64 (local.get $value))) 69 | (call $cal_context_addr (local.get $new_context_index)) 70 | (i32x4.replace_lane 2 (call $get_context (local.get $new_context_index)) (local.get $outer_index)) 71 | (v128.store) 72 | (local.get $new_context_index) 73 | ) 74 | 75 | (func $read_i32_from_context (param $offset i32) (param $contextRef i32) (result i32) 76 | (if (result i32) 77 | (i32.eq (local.get $offset) (i32.const 0)) 78 | (then (i32x4.extract_lane 0 (call $get_context (local.get $contextRef)))) 79 | (else (call $read_i32_from_context (i32.sub (local.get $offset) (i32.const 1)) (call $get_outer_context_index (call $get_context (local.get $contextRef))))) 80 | ) 81 | ) 82 | 83 | (func $read_i64_from_context (param $offset i32) (param $contextRef i32) (result i64) 84 | (if (result i64) 85 | (i32.eq (local.get $offset) (i32.const 0)) 86 | (then (i64x2.extract_lane 0 (call $get_context (local.get $contextRef)))) 87 | (else (call $read_i64_from_context (i32.sub (local.get $offset) (i32.const 1)) (call $get_outer_context_index (call $get_context (local.get $contextRef))))) 88 | ) 89 | ) 90 | 91 | ;; use context heap for closure 92 | 93 | (func $create_closure (param $fref i32) (param $context_ref i32) (result i32) (local $new_index i32) 94 | (local.set $new_index (call $get_next_context_addr)) 95 | (local.get $new_index) 96 | (i32x4.replace_lane 0 (v128.const i64x2 0 0) (local.get $fref)) 97 | (i32x4.replace_lane 1 (local.get $context_ref)) 98 | (v128.store) 99 | (i32.div_u (local.get $new_index) (i32.const 16)) 100 | ) 101 | 102 | (func $get_fref_from_closure (param $closure_index i32) (result i32) 103 | (call $get_context (local.get $closure_index)) 104 | (i32x4.extract_lane 0) 105 | ) 106 | 107 | (func $get_context_from_closure (param $closure_index i32) (result i32) 108 | (call $get_context (local.get $closure_index)) 109 | (i32x4.extract_lane 1) 110 | ) 111 | -------------------------------------------------------------------------------- /src/AST.ts: -------------------------------------------------------------------------------- 1 | //@TODO: More meaningful names 2 | 3 | import { TaggedRecord } from './TaggedRecord'; 4 | 5 | export type LCMVar = TaggedRecord<'LCMVar', { id: string }>; 6 | export type LCPVar = TaggedRecord<'LCPVar', { id: string }>; 7 | export type LCUVar = TaggedRecord<'LCUVar', { id: `#${string}`, unified?: LCType, constraints: Array<(type: LCType) => boolean> }> 8 | export type LCAbsT = TaggedRecord<'LCAbsT', { param: LCType, ret: LCType }>; 9 | export type LCType = LCMVar | LCPVar | LCAbsT | LCUVar; 10 | 11 | export type LCVar = TaggedRecord<'LCVar', { id: string }>; 12 | export type LCNum = TaggedRecord<'LCNum', { val: number, type: LCMVar | LCPVar }>; 13 | export type LCAbs = TaggedRecord<'LCAbs', { pID: string, pType: LCType, exp: LCExp }>; 14 | export type LCApp = TaggedRecord<'LCApp', { f: LCExp, arg: LCExp }>; 15 | export type LCAdd = TaggedRecord<'LCAdd', { left: LCExp, right: LCExp }>; 16 | export type LCMult = TaggedRecord<'LCMult', { left: LCExp, right: LCExp }>; 17 | export type LCSub = TaggedRecord<'LCSub', { left: LCExp, right: LCExp }>; 18 | export type LCDiv = TaggedRecord<'LCDiv', { left: LCExp, right: LCExp }>; 19 | export type LCArith = LCAdd | LCMult | LCSub | LCDiv; 20 | export type LCExp = LCVar | LCNum | LCAbs | LCApp | LCArith; 21 | 22 | export type LCBind = TaggedRecord<'LCBind', { id: string, exp: LCExp }>; 23 | export type LCProc = TaggedRecord<'LCProc', { bindings: LCBind[] }>; 24 | 25 | export type LCTerm = LCExp | LCBind | LCProc 26 | export type AST = LCType | LCTerm; 27 | 28 | //@serializable. 29 | export const LCMVar = (id: string): LCMVar => ({ tag: 'LCMVar', id }); 30 | export const LCPVar = (id: string): LCPVar => ({ tag: 'LCPVar', id }); 31 | export const LCUVar = (id: string): LCUVar => ({ tag: 'LCUVar', id: `#${id}`, constraints: [] }); 32 | export const LCAbsT = (param: LCType, ret: LCType): LCAbsT => ({ tag: 'LCAbsT', param, ret }); 33 | 34 | export const LCVar = (id: string): LCVar => ({ tag: 'LCVar', id }); 35 | export const LCNum = (val: number, type: LCMVar | LCPVar): LCNum => ({ tag: 'LCNum', val, type }); 36 | export const LCAbs = (pID: string, pType: LCType, exp: LCExp): LCAbs => ({ tag: 'LCAbs', pID, pType, exp }); 37 | export const LCApp = (f: LCExp, arg: LCExp): LCApp => ({ tag: 'LCApp', f, arg }); 38 | export const LCAdd = (left: LCExp, right: LCExp): LCAdd => ({ tag: 'LCAdd', left, right }); 39 | export const LCMult = (left: LCExp, right: LCExp): LCMult => ({ tag: 'LCMult', left, right }); 40 | export const LCSub = (left: LCExp, right: LCExp): LCSub => ({ tag: 'LCSub', left, right }); 41 | export const LCDiv = (left: LCExp, right: LCExp): LCDiv => ({ tag: 'LCDiv', left, right }); 42 | 43 | export const LCBind = (id: string, exp: LCExp): LCBind => ({ tag: 'LCBind', id, exp }); 44 | export const LCProc = (bindings: LCBind[]): LCProc => ({ tag: 'LCProc', bindings }); -------------------------------------------------------------------------------- /src/TaggedRecord.ts: -------------------------------------------------------------------------------- 1 | export type TaggedRecord = { tag: tag } & fields -------------------------------------------------------------------------------- /src/absurd.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * never : dummy constant to satisfy exhaustive check 3 | */ 4 | 5 | export function absurd(x: never): T { return x } -------------------------------------------------------------------------------- /src/compile.ts: -------------------------------------------------------------------------------- 1 | import { ASTKinds, parse } from './parser'; 2 | import { refineSyntaxTree } from './refineSyntaxTree'; 3 | import { typeCheck } from './typeCheck'; 4 | import { compile } from './compiler'; 5 | 6 | import { readFileSync } from 'node:fs'; 7 | 8 | const source = String.fromCharCode(...readFileSync(process.argv[2])); 9 | const runtime = String.fromCharCode(...readFileSync(`${__dirname}/../runtime.wat`)); 10 | 11 | const ast = parse(source); 12 | 13 | if (ast.errs.length > 0 || ast?.ast?.kind !== ASTKinds.start) 14 | throw new Error(`Parse Failed! \n ${ast.errs.join('\n,')}`); 15 | 16 | const refinedAST = refineSyntaxTree(ast.ast); 17 | const typeMap = typeCheck(refinedAST); 18 | 19 | console.log(compile(refinedAST, typeMap, runtime)); -------------------------------------------------------------------------------- /src/compiler.ts: -------------------------------------------------------------------------------- 1 | import { TypeMap } from './typeCheck'; 2 | import { LCAbs, LCAbsT, LCAdd, LCApp, LCDiv, LCExp, LCMult, LCNum, LCProc, LCSub, LCType, LCVar } from './AST'; 3 | 4 | // wasm global : offset / heap control 5 | 6 | type Context = { 7 | typeMap: TypeMap; 8 | functionMap: Map; 9 | lambdaConfig: { 10 | lambdaName: string; 11 | lambdaDepth: number; 12 | }; 13 | tableOffset: number; 14 | localVars: [string, LCType][]; 15 | sigMap: Map; 16 | }; 17 | 18 | function lcType2WasmType(lcType: LCType): string { 19 | if (lcType.tag === 'LCMVar') { 20 | return lcType.id.toLowerCase(); 21 | } else if (lcType.tag === 'LCAbsT') return 'i32'; 22 | else return 'i64'; 23 | } 24 | 25 | function compileNumericLiteral(ast: LCNum): string { 26 | switch (ast.type.id) { 27 | case 'I32': 28 | return `(i32.const ${ast.val})`; 29 | case 'I64': 30 | return `(i64.const ${ast.val})`; 31 | case 'F32': 32 | return `(f32.const ${ast.val})`; 33 | case 'F64': 34 | return `(f64.const ${ast.val})`; 35 | default: 36 | throw new Error(`Unknown numeric literal : ${JSON.stringify(ast)}`); 37 | } 38 | } 39 | 40 | function compileVariable(ast: LCVar, context: Context): string { 41 | const variableOffSet = context.localVars.findIndex(([id]) => ast.id === id); 42 | 43 | if (variableOffSet === 0) { // bound variable 44 | return `(local.get $${ast.id})`; 45 | } else if (variableOffSet >= 1) { // semi-bound 46 | const opType = lcType2WasmType(context.typeMap.get(ast)!); 47 | 48 | return `(call $read_${opType}_from_context (i32.const ${variableOffSet}) (local.get $context))`; 49 | } else { // global binding 50 | return `(call $${ast.id})`; 51 | } 52 | } 53 | 54 | type LCArithmetic = LCAdd | LCSub | LCMult | LCDiv; 55 | 56 | function compileArithmetic(ast: LCArithmetic, context: Context): string { 57 | const type = lcType2WasmType(context.typeMap.get(ast)!); 58 | let op: string; 59 | 60 | switch (ast.tag) { 61 | case 'LCAdd': 62 | op = 'add'; 63 | break; 64 | case 'LCSub': 65 | op = 'sub'; 66 | break; 67 | case 'LCMult': 68 | op = 'mul'; 69 | break; 70 | case 'LCDiv': 71 | op = 'div'; 72 | break; 73 | } 74 | 75 | return `(${type}.${op} ${compileExpression(ast.left, context)} ${compileExpression(ast.right, context)})` 76 | } 77 | 78 | function compileAbstraction(ast: LCAbs, context: Context): string { 79 | const functionType = context.typeMap.get(ast)! as LCAbsT; 80 | const functionName = `${context.lambdaConfig.lambdaName}_${context.lambdaConfig.lambdaDepth}`; 81 | const paramType = lcType2WasmType(functionType.param); 82 | let contextInit = ''; 83 | 84 | context.localVars.unshift([ast.pID, functionType.param]); 85 | 86 | if (context.lambdaConfig.lambdaDepth === 0) { 87 | contextInit = ` 88 | (local.set $context (call $create_context_${lcType2WasmType(ast.pType)} (local.get $${ast.pID}))) 89 | `; 90 | } else { 91 | contextInit += ` 92 | (local.set $context (call $alloc_context_var_${lcType2WasmType(ast.pType)} (local.get $${ast.pID}) (local.get $context))) 93 | `; 94 | } 95 | 96 | context.lambdaConfig.lambdaDepth++; 97 | 98 | context.functionMap.set(functionName, { 99 | code: ` 100 | (func $${functionName} (param $${ast.pID} ${paramType}) (param $context i32) (result ${lcType2WasmType(functionType.ret)}) 101 | (local $closure_ref_tmp i32) 102 | ${contextInit} 103 | ${compileExpression(ast.exp, context)} 104 | ) 105 | `, 106 | tableIndex: context.tableOffset 107 | }); 108 | 109 | context.localVars.shift(); 110 | 111 | return `(call $create_closure (i32.const ${context.tableOffset++}) (local.get $context))`; 112 | } 113 | 114 | function getConversionKit(type: string): [string[], string[]] { 115 | if (type === 'i32') { 116 | return [['i64.extend_i32_s'], ['i32.wrap_i64']]; 117 | } else if (type === 'f32') { 118 | return [['f64.promote_f32', 'i64.reinterpret_f64'], ['f64.reinterpret_i64', 'f32.demote_f64']]; 119 | } else if(type === 'f64') { 120 | return [['i64.reinterpret_f64'], ['f64.reinterpret_i64']]; 121 | } else throw new Error(`Cannot find conversion kit for type '${type}`); 122 | } 123 | 124 | function compileApplication(ast: LCApp, context: Context): string { //@TODO: wrap monomorphic function poly-like function when call polyfunc. 125 | const signatureName = `$_sig_${context.lambdaConfig.lambdaName}_${context.sigMap.size}`; 126 | const paramType = (context.typeMap.get(ast.f) as LCAbsT).param; 127 | const retType = (context.typeMap.get(ast.f) as LCAbsT).ret; 128 | const expectedType = context.typeMap.get(ast)!; 129 | const argType = context.typeMap.get(ast.arg)!; 130 | const signature = `(type ${signatureName} (func (param ${lcType2WasmType(paramType)}) (param i32) (result ${lcType2WasmType(retType)})))`; 131 | 132 | context.sigMap.set(signatureName, signature); 133 | 134 | let argExp = compileExpression(ast.arg, context); 135 | 136 | if (paramType.tag === 'LCPVar' && argType.tag !== 'LCPVar') { 137 | const [convIn] = getConversionKit(lcType2WasmType(argType)); 138 | 139 | argExp = ` 140 | ${argExp} 141 | ${convIn.map(op => `(${op})`).join('')} 142 | `; 143 | } 144 | 145 | let callExp = `(call_indirect (type ${signatureName}) ${argExp} (local.set $closure_ref_tmp ${compileExpression(ast.f, context)}) (call $get_context_from_closure (local.get $closure_ref_tmp)) (call $get_fref_from_closure (local.get $closure_ref_tmp)))`; 146 | 147 | if (retType.tag === 'LCPVar' && expectedType.tag !== 'LCPVar') { 148 | const [_, convOut] = getConversionKit(lcType2WasmType(expectedType)); 149 | callExp = ` 150 | ${callExp} 151 | ${convOut.map(op => `(${op})`).join('')} 152 | `; 153 | } 154 | 155 | return ` 156 | ${callExp} 157 | `; 158 | } 159 | 160 | function compileExpression(ast: LCExp, context: Context): string { 161 | switch (ast.tag) { 162 | case 'LCNum': 163 | return compileNumericLiteral(ast); 164 | case 'LCVar': 165 | return compileVariable(ast, context); 166 | case 'LCAbs': 167 | return compileAbstraction(ast, context); 168 | case 'LCApp': 169 | return compileApplication(ast, context); 170 | case 'LCAdd': 171 | case 'LCSub': 172 | case 'LCMult': 173 | case 'LCDiv': 174 | return compileArithmetic(ast, context); 175 | default: 176 | throw new Error(`Unknown AST: ${JSON.stringify(ast)}`); 177 | } 178 | } 179 | 180 | export function compile(ast: LCProc, typeMap: TypeMap, runtime: string): string { 181 | const functionMap = new Map(); 182 | let tableOffset = 0; 183 | const sigMap = new Map(); 184 | //v128 cannot be passed between wasm runtime and js host. 185 | let result = ` 186 | (module 187 | ${runtime} 188 | `; 189 | 190 | for (const binding of ast.bindings) { 191 | const lambdaConfig = { 192 | lambdaName: binding.id, 193 | lambdaDepth: 0 194 | }; 195 | const localVars = [] as [string, LCType][]; 196 | const context = { typeMap, functionMap, lambdaConfig, tableOffset, localVars, sigMap }; 197 | 198 | result += ` 199 | (func $${binding.id} (result ${lcType2WasmType(typeMap.get(binding)!)}) 200 | (local $closure_ref_tmp i32) 201 | (local $context i32) 202 | ${compileExpression(binding.exp, context)} 203 | ) 204 | `; //FFI? 205 | 206 | result += `\n(export "${binding.id}" (func $${binding.id}))`; 207 | 208 | tableOffset = context.tableOffset; 209 | } 210 | 211 | result += `\n(table ${functionMap.size} funcref)`; 212 | 213 | result += `\n(elem (i32.const 0) ${[...functionMap.entries()].sort(([_1, { tableIndex: x }], [_2, { tableIndex: y }]) => x - y).map(([k]) => `$${k}`).join(' ')})`; 214 | 215 | for (const funcDef of functionMap.values()) { 216 | result += `\n${funcDef.code}`; 217 | } 218 | 219 | for (const callSig of sigMap.values()) { 220 | result += `\n${callSig}`; 221 | } 222 | 223 | result += '\n)'; 224 | return result; 225 | } -------------------------------------------------------------------------------- /src/execute.ts: -------------------------------------------------------------------------------- 1 | import { ASTKinds, parse } from './parser'; 2 | import { refineSyntaxTree } from './refineSyntaxTree'; 3 | import { typeCheck } from './typeCheck'; 4 | import { compile } from './compiler'; 5 | 6 | import { readFileSync } from 'node:fs'; 7 | import wabt from 'wabt'; 8 | 9 | wabt().then(async ({ parseWat }) => { 10 | const source = String.fromCharCode(...readFileSync(process.argv[2])); 11 | const runtime = String.fromCharCode(...readFileSync(`${__dirname}/../runtime.wat`)); 12 | 13 | const ast = parse(source); 14 | 15 | if (ast.errs.length > 0 || ast?.ast?.kind !== ASTKinds.start) 16 | throw new Error(`Parse Failed! \n ${ast.errs.join('\n,')}`); 17 | 18 | const refinedAST = refineSyntaxTree(ast.ast); 19 | const typeMap = typeCheck(refinedAST); 20 | const wat = compile(refinedAST, typeMap, runtime); 21 | const wasmModule = parseWat('main', wat); 22 | 23 | WebAssembly.instantiate(wasmModule.toBinary({}).buffer).then(results => { 24 | //@ts-ignore-next-line 25 | console.log(results.instance.exports.main()); 26 | }); 27 | }) 28 | 29 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | /* AutoGenerated Code, changes may be overwritten 2 | * INPUT GRAMMAR: 3 | * start := bindings=binding* $ 4 | * _ := '\s*' 5 | * identifier := identifier='[A-Za-z]\w*' 6 | * identifier_variable := identifier='[a-z]\w*' 7 | * type_var_mono := identifier='[A-Z]\w*' 8 | * type_var_poly := identifier='[a-z]\w*' 9 | * type_var := type_var_mono | type_var_poly 10 | * type_function_type_head := type_var | '\(' _ type=type _ '\)' 11 | * function_type := param=type_function_type_head _ '->' _ exp=function_type | type_function_type_head 12 | * type := function_type | '\(' _ type=type _ '\)' | type_var 13 | * binding := _ name=identifier_variable _ '=' _ exp=expression _ '\.' _ 14 | * expression := additive | '\(' _ exp=expression _ '\)' 15 | * additive := left=additive _ '\+' _ right=multiplicative | left=additive _ '-' _ right=multiplicative | multiplicative 16 | * multiplicative := left=multiplicative _ '\*' _ right=lambda_expression | left=multiplicative _ '/' _ right=lambda_expression | lambda_expression 17 | * lambda_expression := abstraction | application | identifier | num 18 | * abstraction := param=identifier_variable _ ':' _ paramT=type _ '\.' _ body=expression 19 | * application := head=application '\s+' arg=expression_argument | expression_head 20 | * expression_argument := '\(' _ exp=expression _ '\)' | identifier | num 21 | * expression_head := '\(' _ exp=expression _ '\)' | identifier 22 | * num := lexeme='((-?[1-9][0-9]*)|(0))(\.[0-9]+)?' _ '#' _ type=type_var 23 | * .value = number { return parseFloat(this.lexeme); } 24 | */ 25 | type Nullable = T | null; 26 | type $$RuleType = () => Nullable; 27 | export interface ASTNodeIntf { 28 | kind: ASTKinds; 29 | } 30 | export enum ASTKinds { 31 | start = "start", 32 | _ = "_", 33 | identifier = "identifier", 34 | identifier_variable = "identifier_variable", 35 | type_var_mono = "type_var_mono", 36 | type_var_poly = "type_var_poly", 37 | type_var_1 = "type_var_1", 38 | type_var_2 = "type_var_2", 39 | type_function_type_head_1 = "type_function_type_head_1", 40 | type_function_type_head_2 = "type_function_type_head_2", 41 | function_type_1 = "function_type_1", 42 | function_type_2 = "function_type_2", 43 | type_1 = "type_1", 44 | type_2 = "type_2", 45 | type_3 = "type_3", 46 | binding = "binding", 47 | expression_1 = "expression_1", 48 | expression_2 = "expression_2", 49 | additive_1 = "additive_1", 50 | additive_2 = "additive_2", 51 | additive_3 = "additive_3", 52 | multiplicative_1 = "multiplicative_1", 53 | multiplicative_2 = "multiplicative_2", 54 | multiplicative_3 = "multiplicative_3", 55 | lambda_expression_1 = "lambda_expression_1", 56 | lambda_expression_2 = "lambda_expression_2", 57 | lambda_expression_3 = "lambda_expression_3", 58 | lambda_expression_4 = "lambda_expression_4", 59 | abstraction = "abstraction", 60 | application_1 = "application_1", 61 | application_2 = "application_2", 62 | expression_argument_1 = "expression_argument_1", 63 | expression_argument_2 = "expression_argument_2", 64 | expression_argument_3 = "expression_argument_3", 65 | expression_head_1 = "expression_head_1", 66 | expression_head_2 = "expression_head_2", 67 | num = "num", 68 | $EOF = "$EOF", 69 | } 70 | export interface start { 71 | kind: ASTKinds.start; 72 | bindings: binding[]; 73 | } 74 | export type _ = string; 75 | export interface identifier { 76 | kind: ASTKinds.identifier; 77 | identifier: string; 78 | } 79 | export interface identifier_variable { 80 | kind: ASTKinds.identifier_variable; 81 | identifier: string; 82 | } 83 | export interface type_var_mono { 84 | kind: ASTKinds.type_var_mono; 85 | identifier: string; 86 | } 87 | export interface type_var_poly { 88 | kind: ASTKinds.type_var_poly; 89 | identifier: string; 90 | } 91 | export type type_var = type_var_1 | type_var_2; 92 | export type type_var_1 = type_var_mono; 93 | export type type_var_2 = type_var_poly; 94 | export type type_function_type_head = type_function_type_head_1 | type_function_type_head_2; 95 | export type type_function_type_head_1 = type_var; 96 | export interface type_function_type_head_2 { 97 | kind: ASTKinds.type_function_type_head_2; 98 | type: type; 99 | } 100 | export type function_type = function_type_1 | function_type_2; 101 | export interface function_type_1 { 102 | kind: ASTKinds.function_type_1; 103 | param: type_function_type_head; 104 | exp: function_type; 105 | } 106 | export type function_type_2 = type_function_type_head; 107 | export type type = type_1 | type_2 | type_3; 108 | export type type_1 = function_type; 109 | export interface type_2 { 110 | kind: ASTKinds.type_2; 111 | type: type; 112 | } 113 | export type type_3 = type_var; 114 | export interface binding { 115 | kind: ASTKinds.binding; 116 | name: identifier_variable; 117 | exp: expression; 118 | } 119 | export type expression = expression_1 | expression_2; 120 | export type expression_1 = additive; 121 | export interface expression_2 { 122 | kind: ASTKinds.expression_2; 123 | exp: expression; 124 | } 125 | export type additive = additive_1 | additive_2 | additive_3; 126 | export interface additive_1 { 127 | kind: ASTKinds.additive_1; 128 | left: additive; 129 | right: multiplicative; 130 | } 131 | export interface additive_2 { 132 | kind: ASTKinds.additive_2; 133 | left: additive; 134 | right: multiplicative; 135 | } 136 | export type additive_3 = multiplicative; 137 | export type multiplicative = multiplicative_1 | multiplicative_2 | multiplicative_3; 138 | export interface multiplicative_1 { 139 | kind: ASTKinds.multiplicative_1; 140 | left: multiplicative; 141 | right: lambda_expression; 142 | } 143 | export interface multiplicative_2 { 144 | kind: ASTKinds.multiplicative_2; 145 | left: multiplicative; 146 | right: lambda_expression; 147 | } 148 | export type multiplicative_3 = lambda_expression; 149 | export type lambda_expression = lambda_expression_1 | lambda_expression_2 | lambda_expression_3 | lambda_expression_4; 150 | export type lambda_expression_1 = abstraction; 151 | export type lambda_expression_2 = application; 152 | export type lambda_expression_3 = identifier; 153 | export type lambda_expression_4 = num; 154 | export interface abstraction { 155 | kind: ASTKinds.abstraction; 156 | param: identifier_variable; 157 | paramT: type; 158 | body: expression; 159 | } 160 | export type application = application_1 | application_2; 161 | export interface application_1 { 162 | kind: ASTKinds.application_1; 163 | head: application; 164 | arg: expression_argument; 165 | } 166 | export type application_2 = expression_head; 167 | export type expression_argument = expression_argument_1 | expression_argument_2 | expression_argument_3; 168 | export interface expression_argument_1 { 169 | kind: ASTKinds.expression_argument_1; 170 | exp: expression; 171 | } 172 | export type expression_argument_2 = identifier; 173 | export type expression_argument_3 = num; 174 | export type expression_head = expression_head_1 | expression_head_2; 175 | export interface expression_head_1 { 176 | kind: ASTKinds.expression_head_1; 177 | exp: expression; 178 | } 179 | export type expression_head_2 = identifier; 180 | export class num { 181 | public kind: ASTKinds.num = ASTKinds.num; 182 | public lexeme: string; 183 | public type: type_var; 184 | public value: number; 185 | constructor(lexeme: string, type: type_var){ 186 | this.lexeme = lexeme; 187 | this.type = type; 188 | this.value = ((): number => { 189 | return parseFloat(this.lexeme); 190 | })(); 191 | } 192 | } 193 | export class Parser { 194 | private readonly input: string; 195 | private pos: PosInfo; 196 | private negating: boolean = false; 197 | private memoSafe: boolean = true; 198 | constructor(input: string) { 199 | this.pos = {overallPos: 0, line: 1, offset: 0}; 200 | this.input = input; 201 | } 202 | public reset(pos: PosInfo) { 203 | this.pos = pos; 204 | } 205 | public finished(): boolean { 206 | return this.pos.overallPos === this.input.length; 207 | } 208 | public clearMemos(): void { 209 | this.$scope$additive$memo.clear(); 210 | this.$scope$multiplicative$memo.clear(); 211 | this.$scope$application$memo.clear(); 212 | } 213 | protected $scope$additive$memo: Map, PosInfo]> = new Map(); 214 | protected $scope$multiplicative$memo: Map, PosInfo]> = new Map(); 215 | protected $scope$application$memo: Map, PosInfo]> = new Map(); 216 | public matchstart($$dpth: number, $$cr?: ErrorTracker): Nullable { 217 | return this.run($$dpth, 218 | () => { 219 | let $scope$bindings: Nullable; 220 | let $$res: Nullable = null; 221 | if (true 222 | && ($scope$bindings = this.loop(() => this.matchbinding($$dpth + 1, $$cr), true)) !== null 223 | && this.match$EOF($$cr) !== null 224 | ) { 225 | $$res = {kind: ASTKinds.start, bindings: $scope$bindings}; 226 | } 227 | return $$res; 228 | }); 229 | } 230 | public match_($$dpth: number, $$cr?: ErrorTracker): Nullable<_> { 231 | return this.regexAccept(String.raw`(?:\s*)`, $$dpth + 1, $$cr); 232 | } 233 | public matchidentifier($$dpth: number, $$cr?: ErrorTracker): Nullable { 234 | return this.run($$dpth, 235 | () => { 236 | let $scope$identifier: Nullable; 237 | let $$res: Nullable = null; 238 | if (true 239 | && ($scope$identifier = this.regexAccept(String.raw`(?:[A-Za-z]\w*)`, $$dpth + 1, $$cr)) !== null 240 | ) { 241 | $$res = {kind: ASTKinds.identifier, identifier: $scope$identifier}; 242 | } 243 | return $$res; 244 | }); 245 | } 246 | public matchidentifier_variable($$dpth: number, $$cr?: ErrorTracker): Nullable { 247 | return this.run($$dpth, 248 | () => { 249 | let $scope$identifier: Nullable; 250 | let $$res: Nullable = null; 251 | if (true 252 | && ($scope$identifier = this.regexAccept(String.raw`(?:[a-z]\w*)`, $$dpth + 1, $$cr)) !== null 253 | ) { 254 | $$res = {kind: ASTKinds.identifier_variable, identifier: $scope$identifier}; 255 | } 256 | return $$res; 257 | }); 258 | } 259 | public matchtype_var_mono($$dpth: number, $$cr?: ErrorTracker): Nullable { 260 | return this.run($$dpth, 261 | () => { 262 | let $scope$identifier: Nullable; 263 | let $$res: Nullable = null; 264 | if (true 265 | && ($scope$identifier = this.regexAccept(String.raw`(?:[A-Z]\w*)`, $$dpth + 1, $$cr)) !== null 266 | ) { 267 | $$res = {kind: ASTKinds.type_var_mono, identifier: $scope$identifier}; 268 | } 269 | return $$res; 270 | }); 271 | } 272 | public matchtype_var_poly($$dpth: number, $$cr?: ErrorTracker): Nullable { 273 | return this.run($$dpth, 274 | () => { 275 | let $scope$identifier: Nullable; 276 | let $$res: Nullable = null; 277 | if (true 278 | && ($scope$identifier = this.regexAccept(String.raw`(?:[a-z]\w*)`, $$dpth + 1, $$cr)) !== null 279 | ) { 280 | $$res = {kind: ASTKinds.type_var_poly, identifier: $scope$identifier}; 281 | } 282 | return $$res; 283 | }); 284 | } 285 | public matchtype_var($$dpth: number, $$cr?: ErrorTracker): Nullable { 286 | return this.choice([ 287 | () => this.matchtype_var_1($$dpth + 1, $$cr), 288 | () => this.matchtype_var_2($$dpth + 1, $$cr), 289 | ]); 290 | } 291 | public matchtype_var_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 292 | return this.matchtype_var_mono($$dpth + 1, $$cr); 293 | } 294 | public matchtype_var_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 295 | return this.matchtype_var_poly($$dpth + 1, $$cr); 296 | } 297 | public matchtype_function_type_head($$dpth: number, $$cr?: ErrorTracker): Nullable { 298 | return this.choice([ 299 | () => this.matchtype_function_type_head_1($$dpth + 1, $$cr), 300 | () => this.matchtype_function_type_head_2($$dpth + 1, $$cr), 301 | ]); 302 | } 303 | public matchtype_function_type_head_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 304 | return this.matchtype_var($$dpth + 1, $$cr); 305 | } 306 | public matchtype_function_type_head_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 307 | return this.run($$dpth, 308 | () => { 309 | let $scope$type: Nullable; 310 | let $$res: Nullable = null; 311 | if (true 312 | && this.regexAccept(String.raw`(?:\()`, $$dpth + 1, $$cr) !== null 313 | && this.match_($$dpth + 1, $$cr) !== null 314 | && ($scope$type = this.matchtype($$dpth + 1, $$cr)) !== null 315 | && this.match_($$dpth + 1, $$cr) !== null 316 | && this.regexAccept(String.raw`(?:\))`, $$dpth + 1, $$cr) !== null 317 | ) { 318 | $$res = {kind: ASTKinds.type_function_type_head_2, type: $scope$type}; 319 | } 320 | return $$res; 321 | }); 322 | } 323 | public matchfunction_type($$dpth: number, $$cr?: ErrorTracker): Nullable { 324 | return this.choice([ 325 | () => this.matchfunction_type_1($$dpth + 1, $$cr), 326 | () => this.matchfunction_type_2($$dpth + 1, $$cr), 327 | ]); 328 | } 329 | public matchfunction_type_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 330 | return this.run($$dpth, 331 | () => { 332 | let $scope$param: Nullable; 333 | let $scope$exp: Nullable; 334 | let $$res: Nullable = null; 335 | if (true 336 | && ($scope$param = this.matchtype_function_type_head($$dpth + 1, $$cr)) !== null 337 | && this.match_($$dpth + 1, $$cr) !== null 338 | && this.regexAccept(String.raw`(?:->)`, $$dpth + 1, $$cr) !== null 339 | && this.match_($$dpth + 1, $$cr) !== null 340 | && ($scope$exp = this.matchfunction_type($$dpth + 1, $$cr)) !== null 341 | ) { 342 | $$res = {kind: ASTKinds.function_type_1, param: $scope$param, exp: $scope$exp}; 343 | } 344 | return $$res; 345 | }); 346 | } 347 | public matchfunction_type_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 348 | return this.matchtype_function_type_head($$dpth + 1, $$cr); 349 | } 350 | public matchtype($$dpth: number, $$cr?: ErrorTracker): Nullable { 351 | return this.choice([ 352 | () => this.matchtype_1($$dpth + 1, $$cr), 353 | () => this.matchtype_2($$dpth + 1, $$cr), 354 | () => this.matchtype_3($$dpth + 1, $$cr), 355 | ]); 356 | } 357 | public matchtype_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 358 | return this.matchfunction_type($$dpth + 1, $$cr); 359 | } 360 | public matchtype_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 361 | return this.run($$dpth, 362 | () => { 363 | let $scope$type: Nullable; 364 | let $$res: Nullable = null; 365 | if (true 366 | && this.regexAccept(String.raw`(?:\()`, $$dpth + 1, $$cr) !== null 367 | && this.match_($$dpth + 1, $$cr) !== null 368 | && ($scope$type = this.matchtype($$dpth + 1, $$cr)) !== null 369 | && this.match_($$dpth + 1, $$cr) !== null 370 | && this.regexAccept(String.raw`(?:\))`, $$dpth + 1, $$cr) !== null 371 | ) { 372 | $$res = {kind: ASTKinds.type_2, type: $scope$type}; 373 | } 374 | return $$res; 375 | }); 376 | } 377 | public matchtype_3($$dpth: number, $$cr?: ErrorTracker): Nullable { 378 | return this.matchtype_var($$dpth + 1, $$cr); 379 | } 380 | public matchbinding($$dpth: number, $$cr?: ErrorTracker): Nullable { 381 | return this.run($$dpth, 382 | () => { 383 | let $scope$name: Nullable; 384 | let $scope$exp: Nullable; 385 | let $$res: Nullable = null; 386 | if (true 387 | && this.match_($$dpth + 1, $$cr) !== null 388 | && ($scope$name = this.matchidentifier_variable($$dpth + 1, $$cr)) !== null 389 | && this.match_($$dpth + 1, $$cr) !== null 390 | && this.regexAccept(String.raw`(?:=)`, $$dpth + 1, $$cr) !== null 391 | && this.match_($$dpth + 1, $$cr) !== null 392 | && ($scope$exp = this.matchexpression($$dpth + 1, $$cr)) !== null 393 | && this.match_($$dpth + 1, $$cr) !== null 394 | && this.regexAccept(String.raw`(?:\.)`, $$dpth + 1, $$cr) !== null 395 | && this.match_($$dpth + 1, $$cr) !== null 396 | ) { 397 | $$res = {kind: ASTKinds.binding, name: $scope$name, exp: $scope$exp}; 398 | } 399 | return $$res; 400 | }); 401 | } 402 | public matchexpression($$dpth: number, $$cr?: ErrorTracker): Nullable { 403 | return this.choice([ 404 | () => this.matchexpression_1($$dpth + 1, $$cr), 405 | () => this.matchexpression_2($$dpth + 1, $$cr), 406 | ]); 407 | } 408 | public matchexpression_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 409 | return this.matchadditive($$dpth + 1, $$cr); 410 | } 411 | public matchexpression_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 412 | return this.run($$dpth, 413 | () => { 414 | let $scope$exp: Nullable; 415 | let $$res: Nullable = null; 416 | if (true 417 | && this.regexAccept(String.raw`(?:\()`, $$dpth + 1, $$cr) !== null 418 | && this.match_($$dpth + 1, $$cr) !== null 419 | && ($scope$exp = this.matchexpression($$dpth + 1, $$cr)) !== null 420 | && this.match_($$dpth + 1, $$cr) !== null 421 | && this.regexAccept(String.raw`(?:\))`, $$dpth + 1, $$cr) !== null 422 | ) { 423 | $$res = {kind: ASTKinds.expression_2, exp: $scope$exp}; 424 | } 425 | return $$res; 426 | }); 427 | } 428 | public matchadditive($$dpth: number, $$cr?: ErrorTracker): Nullable { 429 | const fn = () => { 430 | return this.choice([ 431 | () => this.matchadditive_1($$dpth + 1, $$cr), 432 | () => this.matchadditive_2($$dpth + 1, $$cr), 433 | () => this.matchadditive_3($$dpth + 1, $$cr), 434 | ]); 435 | }; 436 | const $scope$pos = this.mark(); 437 | const memo = this.$scope$additive$memo.get($scope$pos.overallPos); 438 | if(memo !== undefined) { 439 | this.reset(memo[1]); 440 | return memo[0]; 441 | } 442 | const $scope$oldMemoSafe = this.memoSafe; 443 | this.memoSafe = false; 444 | this.$scope$additive$memo.set($scope$pos.overallPos, [null, $scope$pos]); 445 | let lastRes: Nullable = null; 446 | let lastPos: PosInfo = $scope$pos; 447 | for(;;) { 448 | this.reset($scope$pos); 449 | const res = fn(); 450 | const end = this.mark(); 451 | if(end.overallPos <= lastPos.overallPos) 452 | break; 453 | lastRes = res; 454 | lastPos = end; 455 | this.$scope$additive$memo.set($scope$pos.overallPos, [lastRes, lastPos]); 456 | } 457 | this.reset(lastPos); 458 | this.memoSafe = $scope$oldMemoSafe; 459 | return lastRes; 460 | } 461 | public matchadditive_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 462 | return this.run($$dpth, 463 | () => { 464 | let $scope$left: Nullable; 465 | let $scope$right: Nullable; 466 | let $$res: Nullable = null; 467 | if (true 468 | && ($scope$left = this.matchadditive($$dpth + 1, $$cr)) !== null 469 | && this.match_($$dpth + 1, $$cr) !== null 470 | && this.regexAccept(String.raw`(?:\+)`, $$dpth + 1, $$cr) !== null 471 | && this.match_($$dpth + 1, $$cr) !== null 472 | && ($scope$right = this.matchmultiplicative($$dpth + 1, $$cr)) !== null 473 | ) { 474 | $$res = {kind: ASTKinds.additive_1, left: $scope$left, right: $scope$right}; 475 | } 476 | return $$res; 477 | }); 478 | } 479 | public matchadditive_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 480 | return this.run($$dpth, 481 | () => { 482 | let $scope$left: Nullable; 483 | let $scope$right: Nullable; 484 | let $$res: Nullable = null; 485 | if (true 486 | && ($scope$left = this.matchadditive($$dpth + 1, $$cr)) !== null 487 | && this.match_($$dpth + 1, $$cr) !== null 488 | && this.regexAccept(String.raw`(?:-)`, $$dpth + 1, $$cr) !== null 489 | && this.match_($$dpth + 1, $$cr) !== null 490 | && ($scope$right = this.matchmultiplicative($$dpth + 1, $$cr)) !== null 491 | ) { 492 | $$res = {kind: ASTKinds.additive_2, left: $scope$left, right: $scope$right}; 493 | } 494 | return $$res; 495 | }); 496 | } 497 | public matchadditive_3($$dpth: number, $$cr?: ErrorTracker): Nullable { 498 | return this.matchmultiplicative($$dpth + 1, $$cr); 499 | } 500 | public matchmultiplicative($$dpth: number, $$cr?: ErrorTracker): Nullable { 501 | const fn = () => { 502 | return this.choice([ 503 | () => this.matchmultiplicative_1($$dpth + 1, $$cr), 504 | () => this.matchmultiplicative_2($$dpth + 1, $$cr), 505 | () => this.matchmultiplicative_3($$dpth + 1, $$cr), 506 | ]); 507 | }; 508 | const $scope$pos = this.mark(); 509 | const memo = this.$scope$multiplicative$memo.get($scope$pos.overallPos); 510 | if(memo !== undefined) { 511 | this.reset(memo[1]); 512 | return memo[0]; 513 | } 514 | const $scope$oldMemoSafe = this.memoSafe; 515 | this.memoSafe = false; 516 | this.$scope$multiplicative$memo.set($scope$pos.overallPos, [null, $scope$pos]); 517 | let lastRes: Nullable = null; 518 | let lastPos: PosInfo = $scope$pos; 519 | for(;;) { 520 | this.reset($scope$pos); 521 | const res = fn(); 522 | const end = this.mark(); 523 | if(end.overallPos <= lastPos.overallPos) 524 | break; 525 | lastRes = res; 526 | lastPos = end; 527 | this.$scope$multiplicative$memo.set($scope$pos.overallPos, [lastRes, lastPos]); 528 | } 529 | this.reset(lastPos); 530 | this.memoSafe = $scope$oldMemoSafe; 531 | return lastRes; 532 | } 533 | public matchmultiplicative_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 534 | return this.run($$dpth, 535 | () => { 536 | let $scope$left: Nullable; 537 | let $scope$right: Nullable; 538 | let $$res: Nullable = null; 539 | if (true 540 | && ($scope$left = this.matchmultiplicative($$dpth + 1, $$cr)) !== null 541 | && this.match_($$dpth + 1, $$cr) !== null 542 | && this.regexAccept(String.raw`(?:\*)`, $$dpth + 1, $$cr) !== null 543 | && this.match_($$dpth + 1, $$cr) !== null 544 | && ($scope$right = this.matchlambda_expression($$dpth + 1, $$cr)) !== null 545 | ) { 546 | $$res = {kind: ASTKinds.multiplicative_1, left: $scope$left, right: $scope$right}; 547 | } 548 | return $$res; 549 | }); 550 | } 551 | public matchmultiplicative_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 552 | return this.run($$dpth, 553 | () => { 554 | let $scope$left: Nullable; 555 | let $scope$right: Nullable; 556 | let $$res: Nullable = null; 557 | if (true 558 | && ($scope$left = this.matchmultiplicative($$dpth + 1, $$cr)) !== null 559 | && this.match_($$dpth + 1, $$cr) !== null 560 | && this.regexAccept(String.raw`(?:/)`, $$dpth + 1, $$cr) !== null 561 | && this.match_($$dpth + 1, $$cr) !== null 562 | && ($scope$right = this.matchlambda_expression($$dpth + 1, $$cr)) !== null 563 | ) { 564 | $$res = {kind: ASTKinds.multiplicative_2, left: $scope$left, right: $scope$right}; 565 | } 566 | return $$res; 567 | }); 568 | } 569 | public matchmultiplicative_3($$dpth: number, $$cr?: ErrorTracker): Nullable { 570 | return this.matchlambda_expression($$dpth + 1, $$cr); 571 | } 572 | public matchlambda_expression($$dpth: number, $$cr?: ErrorTracker): Nullable { 573 | return this.choice([ 574 | () => this.matchlambda_expression_1($$dpth + 1, $$cr), 575 | () => this.matchlambda_expression_2($$dpth + 1, $$cr), 576 | () => this.matchlambda_expression_3($$dpth + 1, $$cr), 577 | () => this.matchlambda_expression_4($$dpth + 1, $$cr), 578 | ]); 579 | } 580 | public matchlambda_expression_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 581 | return this.matchabstraction($$dpth + 1, $$cr); 582 | } 583 | public matchlambda_expression_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 584 | return this.matchapplication($$dpth + 1, $$cr); 585 | } 586 | public matchlambda_expression_3($$dpth: number, $$cr?: ErrorTracker): Nullable { 587 | return this.matchidentifier($$dpth + 1, $$cr); 588 | } 589 | public matchlambda_expression_4($$dpth: number, $$cr?: ErrorTracker): Nullable { 590 | return this.matchnum($$dpth + 1, $$cr); 591 | } 592 | public matchabstraction($$dpth: number, $$cr?: ErrorTracker): Nullable { 593 | return this.run($$dpth, 594 | () => { 595 | let $scope$param: Nullable; 596 | let $scope$paramT: Nullable; 597 | let $scope$body: Nullable; 598 | let $$res: Nullable = null; 599 | if (true 600 | && ($scope$param = this.matchidentifier_variable($$dpth + 1, $$cr)) !== null 601 | && this.match_($$dpth + 1, $$cr) !== null 602 | && this.regexAccept(String.raw`(?::)`, $$dpth + 1, $$cr) !== null 603 | && this.match_($$dpth + 1, $$cr) !== null 604 | && ($scope$paramT = this.matchtype($$dpth + 1, $$cr)) !== null 605 | && this.match_($$dpth + 1, $$cr) !== null 606 | && this.regexAccept(String.raw`(?:\.)`, $$dpth + 1, $$cr) !== null 607 | && this.match_($$dpth + 1, $$cr) !== null 608 | && ($scope$body = this.matchexpression($$dpth + 1, $$cr)) !== null 609 | ) { 610 | $$res = {kind: ASTKinds.abstraction, param: $scope$param, paramT: $scope$paramT, body: $scope$body}; 611 | } 612 | return $$res; 613 | }); 614 | } 615 | public matchapplication($$dpth: number, $$cr?: ErrorTracker): Nullable { 616 | const fn = () => { 617 | return this.choice([ 618 | () => this.matchapplication_1($$dpth + 1, $$cr), 619 | () => this.matchapplication_2($$dpth + 1, $$cr), 620 | ]); 621 | }; 622 | const $scope$pos = this.mark(); 623 | const memo = this.$scope$application$memo.get($scope$pos.overallPos); 624 | if(memo !== undefined) { 625 | this.reset(memo[1]); 626 | return memo[0]; 627 | } 628 | const $scope$oldMemoSafe = this.memoSafe; 629 | this.memoSafe = false; 630 | this.$scope$application$memo.set($scope$pos.overallPos, [null, $scope$pos]); 631 | let lastRes: Nullable = null; 632 | let lastPos: PosInfo = $scope$pos; 633 | for(;;) { 634 | this.reset($scope$pos); 635 | const res = fn(); 636 | const end = this.mark(); 637 | if(end.overallPos <= lastPos.overallPos) 638 | break; 639 | lastRes = res; 640 | lastPos = end; 641 | this.$scope$application$memo.set($scope$pos.overallPos, [lastRes, lastPos]); 642 | } 643 | this.reset(lastPos); 644 | this.memoSafe = $scope$oldMemoSafe; 645 | return lastRes; 646 | } 647 | public matchapplication_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 648 | return this.run($$dpth, 649 | () => { 650 | let $scope$head: Nullable; 651 | let $scope$arg: Nullable; 652 | let $$res: Nullable = null; 653 | if (true 654 | && ($scope$head = this.matchapplication($$dpth + 1, $$cr)) !== null 655 | && this.regexAccept(String.raw`(?:\s+)`, $$dpth + 1, $$cr) !== null 656 | && ($scope$arg = this.matchexpression_argument($$dpth + 1, $$cr)) !== null 657 | ) { 658 | $$res = {kind: ASTKinds.application_1, head: $scope$head, arg: $scope$arg}; 659 | } 660 | return $$res; 661 | }); 662 | } 663 | public matchapplication_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 664 | return this.matchexpression_head($$dpth + 1, $$cr); 665 | } 666 | public matchexpression_argument($$dpth: number, $$cr?: ErrorTracker): Nullable { 667 | return this.choice([ 668 | () => this.matchexpression_argument_1($$dpth + 1, $$cr), 669 | () => this.matchexpression_argument_2($$dpth + 1, $$cr), 670 | () => this.matchexpression_argument_3($$dpth + 1, $$cr), 671 | ]); 672 | } 673 | public matchexpression_argument_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 674 | return this.run($$dpth, 675 | () => { 676 | let $scope$exp: Nullable; 677 | let $$res: Nullable = null; 678 | if (true 679 | && this.regexAccept(String.raw`(?:\()`, $$dpth + 1, $$cr) !== null 680 | && this.match_($$dpth + 1, $$cr) !== null 681 | && ($scope$exp = this.matchexpression($$dpth + 1, $$cr)) !== null 682 | && this.match_($$dpth + 1, $$cr) !== null 683 | && this.regexAccept(String.raw`(?:\))`, $$dpth + 1, $$cr) !== null 684 | ) { 685 | $$res = {kind: ASTKinds.expression_argument_1, exp: $scope$exp}; 686 | } 687 | return $$res; 688 | }); 689 | } 690 | public matchexpression_argument_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 691 | return this.matchidentifier($$dpth + 1, $$cr); 692 | } 693 | public matchexpression_argument_3($$dpth: number, $$cr?: ErrorTracker): Nullable { 694 | return this.matchnum($$dpth + 1, $$cr); 695 | } 696 | public matchexpression_head($$dpth: number, $$cr?: ErrorTracker): Nullable { 697 | return this.choice([ 698 | () => this.matchexpression_head_1($$dpth + 1, $$cr), 699 | () => this.matchexpression_head_2($$dpth + 1, $$cr), 700 | ]); 701 | } 702 | public matchexpression_head_1($$dpth: number, $$cr?: ErrorTracker): Nullable { 703 | return this.run($$dpth, 704 | () => { 705 | let $scope$exp: Nullable; 706 | let $$res: Nullable = null; 707 | if (true 708 | && this.regexAccept(String.raw`(?:\()`, $$dpth + 1, $$cr) !== null 709 | && this.match_($$dpth + 1, $$cr) !== null 710 | && ($scope$exp = this.matchexpression($$dpth + 1, $$cr)) !== null 711 | && this.match_($$dpth + 1, $$cr) !== null 712 | && this.regexAccept(String.raw`(?:\))`, $$dpth + 1, $$cr) !== null 713 | ) { 714 | $$res = {kind: ASTKinds.expression_head_1, exp: $scope$exp}; 715 | } 716 | return $$res; 717 | }); 718 | } 719 | public matchexpression_head_2($$dpth: number, $$cr?: ErrorTracker): Nullable { 720 | return this.matchidentifier($$dpth + 1, $$cr); 721 | } 722 | public matchnum($$dpth: number, $$cr?: ErrorTracker): Nullable { 723 | return this.run($$dpth, 724 | () => { 725 | let $scope$lexeme: Nullable; 726 | let $scope$type: Nullable; 727 | let $$res: Nullable = null; 728 | if (true 729 | && ($scope$lexeme = this.regexAccept(String.raw`(?:((-?[1-9][0-9]*)|(0))(\.[0-9]+)?)`, $$dpth + 1, $$cr)) !== null 730 | && this.match_($$dpth + 1, $$cr) !== null 731 | && this.regexAccept(String.raw`(?:#)`, $$dpth + 1, $$cr) !== null 732 | && this.match_($$dpth + 1, $$cr) !== null 733 | && ($scope$type = this.matchtype_var($$dpth + 1, $$cr)) !== null 734 | ) { 735 | $$res = new num($scope$lexeme, $scope$type); 736 | } 737 | return $$res; 738 | }); 739 | } 740 | public test(): boolean { 741 | const mrk = this.mark(); 742 | const res = this.matchstart(0); 743 | const ans = res !== null; 744 | this.reset(mrk); 745 | return ans; 746 | } 747 | public parse(): ParseResult { 748 | const mrk = this.mark(); 749 | const res = this.matchstart(0); 750 | if (res) 751 | return {ast: res, errs: []}; 752 | this.reset(mrk); 753 | const rec = new ErrorTracker(); 754 | this.clearMemos(); 755 | this.matchstart(0, rec); 756 | const err = rec.getErr() 757 | return {ast: res, errs: err !== null ? [err] : []} 758 | } 759 | public mark(): PosInfo { 760 | return this.pos; 761 | } 762 | private loop(func: $$RuleType, star: boolean = false): Nullable { 763 | const mrk = this.mark(); 764 | const res: T[] = []; 765 | for (;;) { 766 | const t = func(); 767 | if (t === null) { 768 | break; 769 | } 770 | res.push(t); 771 | } 772 | if (star || res.length > 0) { 773 | return res; 774 | } 775 | this.reset(mrk); 776 | return null; 777 | } 778 | private run($$dpth: number, fn: $$RuleType): Nullable { 779 | const mrk = this.mark(); 780 | const res = fn() 781 | if (res !== null) 782 | return res; 783 | this.reset(mrk); 784 | return null; 785 | } 786 | private choice(fns: Array<$$RuleType>): Nullable { 787 | for (const f of fns) { 788 | const res = f(); 789 | if (res !== null) { 790 | return res; 791 | } 792 | } 793 | return null; 794 | } 795 | private regexAccept(match: string, dpth: number, cr?: ErrorTracker): Nullable { 796 | return this.run(dpth, 797 | () => { 798 | const reg = new RegExp(match, "y"); 799 | const mrk = this.mark(); 800 | reg.lastIndex = mrk.overallPos; 801 | const res = this.tryConsume(reg); 802 | if(cr) { 803 | cr.record(mrk, res, { 804 | kind: "RegexMatch", 805 | // We substring from 3 to len - 1 to strip off the 806 | // non-capture group syntax added as a WebKit workaround 807 | literal: match.substring(3, match.length - 1), 808 | negated: this.negating, 809 | }); 810 | } 811 | return res; 812 | }); 813 | } 814 | private tryConsume(reg: RegExp): Nullable { 815 | const res = reg.exec(this.input); 816 | if (res) { 817 | let lineJmp = 0; 818 | let lind = -1; 819 | for (let i = 0; i < res[0].length; ++i) { 820 | if (res[0][i] === "\n") { 821 | ++lineJmp; 822 | lind = i; 823 | } 824 | } 825 | this.pos = { 826 | overallPos: reg.lastIndex, 827 | line: this.pos.line + lineJmp, 828 | offset: lind === -1 ? this.pos.offset + res[0].length : (res[0].length - lind - 1) 829 | }; 830 | return res[0]; 831 | } 832 | return null; 833 | } 834 | private noConsume(fn: $$RuleType): Nullable { 835 | const mrk = this.mark(); 836 | const res = fn(); 837 | this.reset(mrk); 838 | return res; 839 | } 840 | private negate(fn: $$RuleType): Nullable { 841 | const mrk = this.mark(); 842 | const oneg = this.negating; 843 | this.negating = !oneg; 844 | const res = fn(); 845 | this.negating = oneg; 846 | this.reset(mrk); 847 | return res === null ? true : null; 848 | } 849 | private memoise(rule: $$RuleType, memo: Map, PosInfo]>): Nullable { 850 | const $scope$pos = this.mark(); 851 | const $scope$memoRes = memo.get($scope$pos.overallPos); 852 | if(this.memoSafe && $scope$memoRes !== undefined) { 853 | this.reset($scope$memoRes[1]); 854 | return $scope$memoRes[0]; 855 | } 856 | const $scope$result = rule(); 857 | if(this.memoSafe) 858 | memo.set($scope$pos.overallPos, [$scope$result, this.mark()]); 859 | return $scope$result; 860 | } 861 | private match$EOF(et?: ErrorTracker): Nullable<{kind: ASTKinds.$EOF}> { 862 | const res: {kind: ASTKinds.$EOF} | null = this.finished() ? { kind: ASTKinds.$EOF } : null; 863 | if(et) 864 | et.record(this.mark(), res, { kind: "EOF", negated: this.negating }); 865 | return res; 866 | } 867 | } 868 | export function parse(s: string): ParseResult { 869 | const p = new Parser(s); 870 | return p.parse(); 871 | } 872 | export interface ParseResult { 873 | ast: Nullable; 874 | errs: SyntaxErr[]; 875 | } 876 | export interface PosInfo { 877 | readonly overallPos: number; 878 | readonly line: number; 879 | readonly offset: number; 880 | } 881 | export interface RegexMatch { 882 | readonly kind: "RegexMatch"; 883 | readonly negated: boolean; 884 | readonly literal: string; 885 | } 886 | export type EOFMatch = { kind: "EOF"; negated: boolean }; 887 | export type MatchAttempt = RegexMatch | EOFMatch; 888 | export class SyntaxErr { 889 | public pos: PosInfo; 890 | public expmatches: MatchAttempt[]; 891 | constructor(pos: PosInfo, expmatches: MatchAttempt[]) { 892 | this.pos = pos; 893 | this.expmatches = [...expmatches]; 894 | } 895 | public toString(): string { 896 | return `Syntax Error at line ${this.pos.line}:${this.pos.offset}. Expected one of ${this.expmatches.map(x => x.kind === "EOF" ? " EOF" : ` ${x.negated ? 'not ': ''}'${x.literal}'`)}`; 897 | } 898 | } 899 | class ErrorTracker { 900 | private mxpos: PosInfo = {overallPos: -1, line: -1, offset: -1}; 901 | private regexset: Set = new Set(); 902 | private pmatches: MatchAttempt[] = []; 903 | public record(pos: PosInfo, result: any, att: MatchAttempt) { 904 | if ((result === null) === att.negated) 905 | return; 906 | if (pos.overallPos > this.mxpos.overallPos) { 907 | this.mxpos = pos; 908 | this.pmatches = []; 909 | this.regexset.clear() 910 | } 911 | if (this.mxpos.overallPos === pos.overallPos) { 912 | if(att.kind === "RegexMatch") { 913 | if(!this.regexset.has(att.literal)) 914 | this.pmatches.push(att); 915 | this.regexset.add(att.literal); 916 | } else { 917 | this.pmatches.push(att); 918 | } 919 | } 920 | } 921 | public getErr(): SyntaxErr | null { 922 | if (this.mxpos.overallPos !== -1) 923 | return new SyntaxErr(this.mxpos, this.pmatches); 924 | return null; 925 | } 926 | } -------------------------------------------------------------------------------- /src/prettyError.ts: -------------------------------------------------------------------------------- 1 | import dedent from 'dedent'; 2 | 3 | export function prettyError(msg: string) { 4 | return new Error(dedent(msg)) 5 | } -------------------------------------------------------------------------------- /src/refineSyntaxTree.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * refineSyntaxTree : refines auto-generated PEG parser's AST into compact one 3 | * 4 | * branches may not be understood intuitively, please refer to type definition of generated PEG parser. 5 | */ 6 | 7 | import { 8 | abstraction, 9 | application_1, 10 | ASTKinds, 11 | binding, 12 | expression, 13 | function_type_1, 14 | start, 15 | type 16 | } from './parser'; 17 | import { 18 | LCAbs, 19 | LCAbsT, 20 | LCAdd, 21 | LCApp, 22 | LCBind, 23 | LCDiv, 24 | LCExp, 25 | LCMult, 26 | LCMVar, 27 | LCNum, 28 | LCProc, 29 | LCPVar, 30 | LCSub, 31 | LCType, 32 | LCVar 33 | } from './AST'; 34 | import { absurd } from './absurd'; 35 | 36 | function refineFunctionType(st: function_type_1, prefix: string): LCAbsT { 37 | return LCAbsT(refineType(st.param, prefix), refineType(st.exp, prefix)); 38 | } 39 | 40 | function refineType(st: type, prefix: string): LCType { 41 | switch (st.kind) { 42 | case ASTKinds.function_type_1: 43 | return refineFunctionType(st, prefix) 44 | case ASTKinds.type_2: 45 | return refineType(st.type, prefix) 46 | case ASTKinds.type_var_mono: 47 | return LCMVar(st.identifier) 48 | case ASTKinds.type_var_poly: 49 | return LCPVar(`${prefix}_${st.identifier}`) 50 | case ASTKinds.type_function_type_head_2: 51 | return refineType(st.type, prefix) 52 | default: 53 | return absurd(st) 54 | } 55 | } 56 | 57 | function refineAbstraction(st: abstraction, prefix: string): LCAbs { 58 | return LCAbs(st.param.identifier, refineType(st.paramT, prefix), refineExp(st.body, prefix)) 59 | } 60 | 61 | function refineApplication(st: application_1, prefix: string): LCApp { 62 | return LCApp(refineExp(st.head, prefix), refineExp(st.arg.kind === ASTKinds.expression_argument_1 ? st.arg.exp : st.arg, prefix)); 63 | } 64 | 65 | function refineExp(st: expression, prefix: string): LCExp { 66 | switch (st.kind) { 67 | case ASTKinds.expression_2: 68 | return refineExp(st.exp, prefix) 69 | case ASTKinds.additive_1: 70 | return LCAdd(refineExp(st.left, prefix), refineExp(st.right, prefix)) 71 | case ASTKinds.additive_2: 72 | return LCSub(refineExp(st.left, prefix), refineExp(st.right, prefix)) 73 | case ASTKinds.multiplicative_1: 74 | return LCMult(refineExp(st.left, prefix), refineExp(st.right, prefix)) 75 | case ASTKinds.multiplicative_2: 76 | return LCDiv(refineExp(st.left, prefix), refineExp(st.right, prefix)) 77 | case ASTKinds.abstraction: 78 | return refineAbstraction(st, prefix) 79 | case ASTKinds.application_1: 80 | return refineApplication(st, prefix) 81 | case ASTKinds.expression_head_1: 82 | return refineExp(st.exp, prefix) 83 | case ASTKinds.identifier: 84 | return LCVar(st.identifier); 85 | case ASTKinds.num: 86 | return LCNum(st.value, st.type.kind === ASTKinds.type_var_poly ? LCPVar(st.type.identifier) : LCMVar(st.type.identifier)) 87 | default: 88 | return absurd(st) 89 | } 90 | } 91 | 92 | function refineBinding(st: binding): LCBind { 93 | return LCBind(st.name.identifier, refineExp(st.exp, st.name.identifier)); 94 | } 95 | 96 | export function refineSyntaxTree(st: start): LCProc { 97 | return LCProc(st.bindings.map(refineBinding)); 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/typeCheck.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type checker 3 | * 4 | * Covers lambda expression that can be typed in monomorphic type or rank 1 polymorphic type. 5 | * 6 | * @TODO: Verify algorithm 7 | * @TODO: rank-n type inference for `$` operator and `runST`. 8 | */ 9 | 10 | import { 11 | LCAbs, 12 | LCAbsT, 13 | LCApp, 14 | LCArith, 15 | LCBind, 16 | LCExp, 17 | LCNum, 18 | LCProc, 19 | LCPVar, 20 | LCTerm, 21 | LCType, 22 | LCUVar, 23 | LCVar 24 | } from './AST'; 25 | import { prettyError } from './prettyError'; 26 | 27 | export type TypeMap = WeakMap 28 | // stack of bindings, leftmost binding has priority in resolution. 29 | type Context = [string, LCType][] 30 | type BoundTypeVariables = string[] 31 | 32 | // returns string representation of given type. 33 | function typeToString(type: LCType): string { 34 | switch (type.tag) { 35 | case 'LCMVar': 36 | return type.id 37 | case 'LCPVar': 38 | return type.id 39 | case 'LCUVar': 40 | // unification variables may not have been unified at the time of calling this function 41 | if (type.unified) return typeToString(type.unified) 42 | else return `${type.id}` 43 | case 'LCAbsT': 44 | // '->' is right associative, therefore parentheses are required to represent function type of parameter. 45 | if (type.param.tag === 'LCAbsT') return `(${typeToString(type.param)}) -> ${typeToString(type.ret)}` 46 | else return `${typeToString(type.param)} -> ${typeToString(type.ret)}` 47 | } 48 | } 49 | 50 | // returns string representation of given type. 51 | function contextToString(context: Context): string { 52 | return context.reduce((str, [name, type]) => `${str}\n(${name}, ${typeToString(type)})`, '') 53 | } 54 | 55 | /* 56 | simplify types. 57 | 58 | - remove unnecessary layer of unification variable. 59 | */ 60 | function simplifyType(type: LCType): LCType { 61 | if (type.tag === 'LCAbsT') return LCAbsT(simplifyType(type.param), simplifyType(type.ret)) 62 | if (type.tag !== 'LCUVar') return type 63 | return type.unified ?? type 64 | } 65 | 66 | // check if two types are compatible. 67 | // monomorphic types only. 68 | function compareType(t1: LCType, t2: LCType, expectation: boolean): boolean { 69 | // let types be it's simplest form. 70 | t1 = simplifyType(t1) 71 | t2 = simplifyType(t2) 72 | 73 | if (t1.tag === 'LCAbsT' || t2.tag === 'LCAbsT') { 74 | // at least, one of t1 and t2 is function type now. 75 | if (t1.tag !== t2.tag) return false 76 | 77 | // t1 and t2 are function type here. 78 | return compareType((t1 as LCAbsT).param, (t2 as LCAbsT).param, expectation) && compareType((t1 as LCAbsT).ret, (t2 as LCAbsT).ret, expectation) 79 | } else if (t1.tag === 'LCUVar' || t2.tag === 'LCUVar') { 80 | // compatible check of unification variables are delayed until one of them is resolved. 81 | if (t1.tag === 'LCUVar') { 82 | // If t1 is unified, but not compatible with t2 83 | if (t1.unified && compareType(t1.unified, t2, expectation) !== expectation) 84 | throw prettyError(` 85 | expected two types are ${expectation ? 'compatible' : 'incompatible'}, 86 | but found they are not. 87 | `) 88 | // If t1 is not unified 89 | else if (!t1.unified) { 90 | t1.unified = t2 91 | 92 | if(!t1.constraints.every(constraint => constraint(t2))) 93 | throw prettyError(` 94 | assuming ${typeToString(t1)} equals to ${typeToString(t2)} conflicts to constraints already given to. 95 | `) 96 | } 97 | } 98 | else if (t2.tag === 'LCUVar') { 99 | if (t2.unified && compareType(t2.unified, t1, expectation) !== expectation) 100 | throw prettyError(` 101 | expected two types are ${expectation ? 'compatible' : 'incompatible'}, 102 | but found they are not. 103 | `) 104 | else if (!t2.unified) { 105 | t2.unified = t1 106 | 107 | if(!t2.constraints.every(constraint => constraint(t1))) 108 | throw prettyError(` 109 | assuming ${typeToString(t1)} equals to ${typeToString(t2)} conflicts to constraints already given to. 110 | `) 111 | } 112 | } 113 | 114 | return expectation 115 | } 116 | /* 117 | two non-function types are compatible 118 | if and only if they are same kind and same name. 119 | */ 120 | else return t1.tag === t2.tag && t1.id === t2.id 121 | } 122 | 123 | /* 124 | * check whether given type represents any kinds of number type. 125 | * @TODO: Let primitive types be top level binding so that this kinds of check does not need. 126 | */ 127 | function isNumberType(type: LCType, expectation: boolean): boolean { 128 | if (type.tag === 'LCPVar' || type.tag === 'LCAbsT') return false 129 | if (type.tag === 'LCUVar') { 130 | if (type.unified) return isNumberType(type.unified, expectation) 131 | else { 132 | type.constraints.push(type => isNumberType(type, expectation) === expectation) 133 | 134 | return expectation 135 | } 136 | } 137 | 138 | return ['I32', 'I64', 'F32', 'F64'].includes(type.id) 139 | } 140 | 141 | // collect all type variables in given type, duplication will be ignored. 142 | function collectTypeVariables(type: LCType): BoundTypeVariables { 143 | if (type.tag === 'LCMVar' || type.tag === 'LCUVar') return [] 144 | else if (type.tag === 'LCPVar') return [type.id] 145 | else return collectTypeVariables(type.param).concat(collectTypeVariables(type.ret)) // Now type must be LCAbsT. 146 | } 147 | 148 | //@FIXME: resolve name collision problem in more elegant way. 149 | function terminateUnificationVariable(type: LCType, namespace: string): LCType { 150 | if (type.tag === 'LCAbsT') return LCAbsT(terminateUnificationVariable(type.param, `${namespace}_`), terminateUnificationVariable(type.ret, `${namespace}'`)) 151 | else if (type.tag !== 'LCUVar') return type 152 | else if (type.unified) return terminateUnificationVariable(type.unified, namespace) 153 | else { 154 | type.unified = LCPVar(`${namespace}_u`) 155 | 156 | if (!type.constraints.every(constraint => constraint(type))) 157 | throw prettyError(` 158 | tried to resolve unification variable '${type.id}' as ${typeToString(type.unified)} but failed. 159 | `) 160 | 161 | return type 162 | } 163 | } 164 | 165 | // resolves type of given variable's name. 166 | function resolveTypeOfVariable(target: string, context: Context): LCType { 167 | // leftmost binding(the latest one) has priority in name resolution. 168 | const searchResult = context.find(([name]) => name === target) 169 | 170 | if (!searchResult) 171 | throw prettyError(` 172 | Cannot resolve type of variable '${target}' in the context: 173 | 174 | ${contextToString(context)} 175 | `) 176 | 177 | return searchResult[1] 178 | } 179 | 180 | // type check given numeric literal. 181 | function checkNumericLiteral(ast: LCNum, typeMap: TypeMap) { 182 | function checkInt(): void { 183 | if (!Number.isInteger(ast.val)) 184 | throw prettyError(` 185 | ${ast.val} is not a integer, value of type '${typeToString(ast.type)}' expected. 186 | `) 187 | } 188 | 189 | //@TODO: check size of number. 190 | switch (ast.type.id) { 191 | case 'I32': 192 | checkInt() 193 | typeMap.set(ast, ast.type) 194 | break; 195 | case 'I64': 196 | checkInt() 197 | typeMap.set(ast, ast.type) 198 | break 199 | case 'F32': 200 | typeMap.set(ast, ast.type) 201 | break 202 | case 'F64': 203 | typeMap.set(ast, ast.type) 204 | break 205 | default: 206 | throw prettyError(`Incompatible type for numeric literal '${ast.type}' is given.`) 207 | } 208 | } 209 | 210 | // type check given variable reference. 211 | function checkVariable(ast: LCVar, context: Context, typeMap: TypeMap) { 212 | const type = resolveTypeOfVariable(ast.id, context) 213 | 214 | typeMap.set(ast, type) 215 | } 216 | 217 | //@TODO: Better error messages. 218 | function solveTypeEquation(parameterType: LCType, argumentType: LCType, context: Context, boundTypeVariables: BoundTypeVariables, solutionMap: Map): Map { 219 | if (parameterType.tag === 'LCPVar' && !boundTypeVariables.includes(parameterType.id)) { 220 | if (solutionMap.has(parameterType.id) && !compareType(solutionMap.get(parameterType.id)!, argumentType, true)) 221 | throw prettyError(`occurrence check failed`) 222 | 223 | solutionMap.set(parameterType.id, argumentType) 224 | } else if (parameterType.tag === 'LCMVar' || (parameterType.tag === 'LCPVar' && boundTypeVariables.includes(parameterType.id))) { 225 | if (!compareType(parameterType, argumentType, true)) 226 | throw prettyError(`type did not match error`) 227 | 228 | if (argumentType.tag === 'LCUVar') { 229 | if (argumentType.unified) 230 | solveTypeEquation(parameterType, argumentType.unified, context, boundTypeVariables, solutionMap) 231 | else { 232 | argumentType.unified = parameterType 233 | 234 | if (!argumentType.constraints.every(constraint => constraint(parameterType))) 235 | throw prettyError(`fail to meet constraints for unification variable`) 236 | } 237 | } 238 | } else if (parameterType.tag === 'LCAbsT') { 239 | if (argumentType.tag === 'LCUVar') { 240 | if (argumentType.unified) 241 | solveTypeEquation(parameterType, argumentType.unified, context, boundTypeVariables, solutionMap) 242 | else { 243 | const semiInstantiatedUnificationVar: LCAbsT = LCAbsT(LCUVar(`${argumentType.id}_head`), LCUVar(`${argumentType.id}_ret`)) 244 | 245 | argumentType.unified = semiInstantiatedUnificationVar 246 | 247 | if (argumentType.constraints.every(constraint => constraint(semiInstantiatedUnificationVar))) 248 | throw prettyError(`fail to apply incremental resolution to unification variable`) 249 | 250 | solveTypeEquation(parameterType, semiInstantiatedUnificationVar, context, boundTypeVariables, solutionMap) 251 | } 252 | } else if (argumentType.tag === 'LCAbsT') { 253 | solveTypeEquation(parameterType.param, argumentType.param, context, boundTypeVariables, solutionMap) 254 | solveTypeEquation(parameterType.ret, argumentType.ret, context, boundTypeVariables, solutionMap) 255 | } else throw prettyError(`type did not match error`) 256 | } else if (parameterType.tag === 'LCUVar') { 257 | if (parameterType.unified) 258 | solveTypeEquation(parameterType.unified, argumentType, context, boundTypeVariables, solutionMap) 259 | else { 260 | parameterType.unified = argumentType 261 | 262 | if (!parameterType.constraints.every(constraint => constraint(parameterType))) 263 | throw prettyError(`fail to meet constraints for unification variable`) 264 | } 265 | } 266 | 267 | return solutionMap 268 | } 269 | 270 | function instantiateType(type: LCType, boundTypeVariables: BoundTypeVariables, sols: Map): LCType { 271 | if (type.tag === 'LCPVar' && !boundTypeVariables.includes(type.id)) { 272 | if (sols.has(type.id)) return sols.get(type.id)! 273 | 274 | return type 275 | } else if (type.tag === 'LCAbsT') 276 | return LCAbsT(instantiateType(type.param, boundTypeVariables, sols), instantiateType(type.ret, boundTypeVariables, sols)) 277 | else return type 278 | } 279 | 280 | // type check application expression 281 | function checkApplication(ast: LCApp, context: Context, typeMap: TypeMap, boundTypeVariables: BoundTypeVariables) { 282 | checkExpression(ast.f, context, typeMap, boundTypeVariables) 283 | checkExpression(ast.arg, context, typeMap, boundTypeVariables) 284 | 285 | // once head/argument expression type checks, there must be mapping to type from it in TypeMap 286 | const headType = typeMap.get(ast.f)!; 287 | const argType = typeMap.get(ast.arg)!; 288 | 289 | if (headType.tag !== 'LCAbsT') 290 | throw prettyError(` 291 | head of application expression is not function 292 | `) 293 | 294 | const parameterType = headType.param 295 | const returnType = headType.ret 296 | 297 | const solutions = solveTypeEquation(parameterType, argType, context, boundTypeVariables, new Map) 298 | 299 | typeMap.set(ast, instantiateType(returnType, boundTypeVariables, solutions)) 300 | } 301 | 302 | // type check given lambda abstraction 303 | function checkAbstraction(ast: LCAbs, context: Context, typeMap: TypeMap, boundTypeVariables: BoundTypeVariables) { 304 | const collectedPVars = collectTypeVariables(ast.pType) 305 | 306 | checkExpression(ast.exp, ([[ast.pID, ast.pType]] as Context).concat(context), typeMap, collectedPVars.concat(boundTypeVariables)) 307 | 308 | // once body expression of lambda abstraction type checks, there must be mapping to type from it in TypeMap 309 | const bodyType = typeMap.get(ast.exp)! 310 | 311 | typeMap.set(ast, LCAbsT(ast.pType, bodyType)) 312 | } 313 | 314 | // type check given arithmetic expression 315 | function checkArithmeticExpression(ast: LCArith, context: Context, typeMap: TypeMap, boundTypeVariables: BoundTypeVariables) { 316 | const { left, right } = ast 317 | 318 | checkExpression(left, context, typeMap, boundTypeVariables) 319 | checkExpression(right, context, typeMap, boundTypeVariables) 320 | 321 | // once left/right operand type checks, type of both can be found in TypeMap. 322 | const lvalType = typeMap.get(left)!; 323 | const rvalType = typeMap.get(right)!; 324 | 325 | if (lvalType.tag === 'LCAbsT' || rvalType.tag === 'LCAbsT') 326 | throw prettyError(` 327 | functions can not be target of arithmetic operations. 328 | `) 329 | if (lvalType.tag === 'LCPVar' || rvalType.tag === 'LCPVar') 330 | throw prettyError(` 331 | value of polymorphic type can not be target of arithmetic operations. 332 | `) 333 | if (!compareType(lvalType, rvalType, true)) 334 | throw prettyError(` 335 | types of operand for arithmetic operation are incompatible. 336 | 337 | type of left operand is '${typeToString(lvalType)}', but type of right is '${typeToString(rvalType)}'. 338 | `) 339 | if (!isNumberType(lvalType, true)) 340 | throw prettyError(` 341 | only value of number types can be operand of arithmetic operation. 342 | `) 343 | 344 | typeMap.set(ast, lvalType) 345 | } 346 | 347 | // type check given lambda expression. 348 | function checkExpression(ast: LCExp, context: Context, typeMap: TypeMap, boundTypeVariables: BoundTypeVariables) { 349 | switch (ast.tag) { 350 | case 'LCNum': 351 | checkNumericLiteral(ast, typeMap) 352 | break 353 | case 'LCVar': 354 | checkVariable(ast, context, typeMap) 355 | break 356 | case 'LCApp': 357 | checkApplication(ast, context, typeMap, boundTypeVariables) 358 | break 359 | case 'LCAbs': 360 | checkAbstraction(ast, context, typeMap, boundTypeVariables) 361 | break 362 | // @TODO: Let arithmetic operators be normal function so that this kind of handling could be erased. 363 | case 'LCAdd': 364 | case 'LCSub': 365 | case 'LCMult': 366 | case 'LCDiv': 367 | checkArithmeticExpression(ast, context, typeMap, boundTypeVariables); 368 | break 369 | } 370 | } 371 | 372 | // type check given supercombinator 373 | function checkBinding(ast: LCBind, context: Context, typeMap: TypeMap, boundTypeVariables: BoundTypeVariables) { 374 | checkExpression(ast.exp, ([[ast.id, LCUVar(ast.id)]] as Context).concat(context), typeMap, boundTypeVariables) 375 | 376 | const expType = terminateUnificationVariable(typeMap.get(ast.exp)!, ast.id) 377 | 378 | typeMap.set(ast, simplifyType(expType)) 379 | } 380 | 381 | // type check given program 382 | export function typeCheck(ast: LCProc): TypeMap { 383 | const typeMap: TypeMap = new Map 384 | const context: Context = [] 385 | const boundTypeVariables: BoundTypeVariables = [] 386 | 387 | //@TODO: Hoisting? 388 | for (const binding of ast.bindings) { 389 | checkBinding(binding, context, typeMap, boundTypeVariables) 390 | 391 | context.push([binding.id, typeMap.get(binding)!]) 392 | } 393 | 394 | return typeMap 395 | } -------------------------------------------------------------------------------- /syntax.ebnf: -------------------------------------------------------------------------------- 1 | ::= (a | ... | Z) { any ascii character } 2 | ::= (a | ... | z) { any ascii character } 3 | 4 | = | | "(" ")" 5 | ::= "->" 6 | 7 | ::= [ ] 8 | 9 | ::= "=" 10 | 11 | ::= "(" ")" | 12 | ::= "+" | "-" | 13 | ::= | "*" | "/" 14 | ::= | | | 15 | ::= ":" "." 16 | ::= | 17 | ::= "(" ")" | | 18 | ::= "(" ")" | 19 | ::= (1 | ... | 9) { 0 | ... | 9 } [ "." { 0 | ... | 9 } ] -------------------------------------------------------------------------------- /syntax.peg: -------------------------------------------------------------------------------- 1 | start := bindings=binding* $ 2 | 3 | _ := '\s*' 4 | 5 | identifier := identifier='[A-Za-z]\w*' 6 | identifier_variable := identifier='[a-z]\w*' 7 | 8 | type_var_mono := identifier='[A-Z]\w*' 9 | type_var_poly := identifier='[a-z]\w*' 10 | type_var := type_var_mono | type_var_poly 11 | type_function_type_head := type_var | '\(' _ type=type _ '\)' 12 | function_type := param=type_function_type_head _ '->' _ exp=function_type | type_function_type_head 13 | type := function_type | '\(' _ type=type _ '\)' | type_var 14 | 15 | binding := _ name=identifier_variable _ '=' _ exp=expression _ '\.' _ 16 | expression := additive | '\(' _ exp=expression _ '\)' 17 | additive := left=additive _ '\+' _ right=multiplicative | left=additive _ '-' _ right=multiplicative | multiplicative 18 | multiplicative := left=multiplicative _ '\*' _ right=lambda_expression | left=multiplicative _ '/' _ right=lambda_expression | lambda_expression 19 | lambda_expression := abstraction | application | identifier | num 20 | abstraction := param=identifier_variable _ ':' _ paramT=type _ '\.' _ body=expression 21 | application := head=application '\s+' arg=expression_argument | expression_head 22 | expression_argument := '\(' _ exp=expression _ '\)' | identifier | num 23 | expression_head := '\(' _ exp=expression _ '\)' | identifier 24 | num := lexeme='((-?[1-9][0-9]*)|(0))(\.[0-9]+)?' _ '#' _ type=type_var 25 | .value = number { return parseFloat(this.lexeme); } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["esnext", "dom"], 4 | "module": "nodenext", 5 | "noImplicitReturns": true, 6 | "removeComments": true, 7 | "strict": true, 8 | "target": "es6", 9 | "sourceMap": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true 12 | }, 13 | "include": [ 14 | "src/**/*" 15 | ] 16 | } --------------------------------------------------------------------------------